Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CopyEI2T: Cases To Cover Rotated Images #4111

Merged
merged 13 commits into from
Jan 7, 2025
13 changes: 13 additions & 0 deletions src/resources/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,3 +91,16 @@ ffmpeg -display_vflip -i temp.mp4 -c copy four-colors-vp9-bt601-vflip.mp4
rm temp.mp4

```

The test jpg files were generated with by exiftool cmds below:
```
// Generate jpg picture with 90 cw rotation metadata
exiftool -Orientation#=6 four-colors.jpg -o .\four-colors-rotate-90-cw.jpg

// Generate jpg picture with 180 cw rotation metadata
exiftool -Orientation#=3 four-colors.jpg -o .\four-colors-rotate-180-cw.jpg

// Generate jpg picture with 270 cw rotation metadata
exiftool -Orientation#=8 four-colors.jpg -o .\four-colors-rotate-270-cw.jpg

```
shaoboyan091 marked this conversation as resolved.
Show resolved Hide resolved
Binary file added src/resources/four-colors-rotate-180-cw.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/four-colors-rotate-270-cw.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/four-colors-rotate-90-cw.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added src/resources/four-colors.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
126 changes: 126 additions & 0 deletions src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,17 @@ TODO: Test ImageBitmap generated from all possible ImageBitmapSource, relevant I
TODO: Test zero-sized copies from all sources (just make sure params cover it) (e.g. 0x0, 0x4, 4x0).
`;

import { getResourcePath } from '../../../common/framework/resources.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { raceWithRejectOnTimeout } from '../../../common/util/util.js';
import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js';
import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
import {
convertToUnorm8,
kImageNames,
kImageInfo,
kImageExpectedColors,
} from '../../web_platform/util.js';

import { kTestColorsAll, kTestColorsOpaque, makeTestColorsTexelView } from './util.js';

Expand Down Expand Up @@ -541,3 +549,121 @@ g.test('copy_subrect_from_2D_Canvas')
{ maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
);
});

g.test('from_rotated_image')
.desc(
`
Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly.

It creates ImageBitmap with images under Resource folder, with orientation flag set to
'from-image'.

Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read one pixel out to compare with the manually documented expected color.

If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped.

The tests covers:
- Image with rotation metadata
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- TODO: partial copy tests should be added
- TODO: all valid dstColorFormat tests should be added.
- TODO: all valid imageOrientation flags tests should be added
TODO: all valid dstColorFormat tests should be added.
`
)
.params(u =>
u //
.combine('imageName', kImageNames)
.combine('srcDoFlipYDuringCopy', [true, false])
)
.fn(async t => {
const { imageName, srcDoFlipYDuringCopy } = t.params;
const kColorFormat = 'rgba8unorm';

// Load image.
const image = new Image();
const imageUrl = getResourcePath(imageName);
image.src = imageUrl;
await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout');

const imageBitmap = await createImageBitmap(image, {
imageOrientation: 'from-image',
});

const width = imageBitmap.width;
const height = imageBitmap.height;

const dstTexture = t.createTextureTracked({
size: { width, height },
format: kColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});

t.device.queue.copyExternalImageToTexture(
{
source: imageBitmap,
flipY: srcDoFlipYDuringCopy,
},
{
texture: dstTexture,
},
{
width,
height,
}
);

const expect = kImageInfo[imageName].display;
const presentColors = kImageExpectedColors.srgb;

if (srcDoFlipYDuringCopy) {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
// Flipped top-left.
{
coord: { x: width * 0.25, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
// Flipped top-right.
{
coord: { x: width * 0.75, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
// Flipped bottom-left.
{
coord: { x: width * 0.25, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
},
// Flipped bottom-right.
{
coord: { x: width * 0.75, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.topRightColor]),
},
]);
} else {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
// Top-left.
{
coord: { x: width * 0.25, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
},
// Top-right.
{
coord: { x: width * 0.75, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.topRightColor]),
},
// Bottom-left.
{
coord: { x: width * 0.25, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
// Bottom-right.
{
coord: { x: width * 0.75, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
]);
}
});
117 changes: 117 additions & 0 deletions src/webgpu/web_platform/copyToTexture/image.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,17 @@ export const description = `
copyExternalImageToTexture from HTMLImageElement source.
`;

import { getResourcePath } from '../../../common/framework/resources.js';
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { raceWithRejectOnTimeout } from '../../../common/util/util.js';
import { kTextureFormatInfo, kValidTextureFormatsForCopyE2T } from '../../format_info.js';
import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
import {
convertToUnorm8,
kImageNames,
kImageInfo,
kImageExpectedColors,
} from '../../web_platform/util.js';

import { kTestColorsOpaque, makeTestColorsTexelView } from './util.js';

Expand Down Expand Up @@ -339,3 +346,113 @@ g.test('copy_subrect_from_2D_Canvas')
{ maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
);
});

g.test('from_rotated_image')
.desc(
`
Test HTMLImageElements with rotation metadata can be copied to WebGPU texture correctly.

It creates Images with images under Resource folder.

Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read one pixel out to compare with the manually documented expected color.

If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped.

The tests covers:
- Image with rotation metadata
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- TODO: partial copy tests should be added
- TODO: all valid dstColorFormat tests should be added.
`
)
.params(u =>
u //
.combine('imageName', kImageNames)
.combine('srcDoFlipYDuringCopy', [true, false])
)
.fn(async t => {
const { imageName, srcDoFlipYDuringCopy } = t.params;
const kColorFormat = 'rgba8unorm';

// Load image.
const image = new Image();
const imageUrl = getResourcePath(imageName);
image.src = imageUrl;
await raceWithRejectOnTimeout(image.decode(), 5000, 'decode image timeout');
const width = image.width;
const height = image.height;

const dstTexture = t.createTextureTracked({
size: { width, height },
format: kColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});

t.device.queue.copyExternalImageToTexture(
{
source: image,
shaoboyan091 marked this conversation as resolved.
Show resolved Hide resolved
flipY: srcDoFlipYDuringCopy,
},
{
texture: dstTexture,
},
{
width,
height,
}
);

const expect = kImageInfo[imageName].display;
const presentColors = kImageExpectedColors.srgb;

if (srcDoFlipYDuringCopy) {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
// Flipped top-left.
{
coord: { x: width * 0.25, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
// Flipped top-right.
{
coord: { x: width * 0.75, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
// Flipped bottom-left.
{
coord: { x: width * 0.25, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
},
// Flipped bottom-right.
{
coord: { x: width * 0.75, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.topRightColor]),
},
]);
} else {
t.expectSinglePixelComparisonsAreOkInTexture({ texture: dstTexture }, [
// Top-left.
{
coord: { x: width * 0.25, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.topLeftColor]),
},
// Top-right.
{
coord: { x: width * 0.75, y: height * 0.25 },
exp: convertToUnorm8(presentColors[expect.topRightColor]),
},
// Bottom-left.
{
coord: { x: width * 0.25, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.bottomLeftColor]),
},
// Bottom-right.
{
coord: { x: width * 0.75, y: height * 0.75 },
exp: convertToUnorm8(presentColors[expect.bottomRightColor]),
},
]);
}
});
50 changes: 50 additions & 0 deletions src/webgpu/web_platform/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,15 @@ export const kVideoExpectedColors = makeTable({
},
} as const);

export const kImageExpectedColors = {
srgb: {
red: { R: 1.0, G: 0.0, B: 0.0, A: 1.0 },
green: { R: 0.0, G: 1.0, B: 0.0, A: 1.0 },
blue: { R: 0.0, G: 0.0, B: 1.0, A: 1.0 },
yellow: { R: 1.0, G: 1.0, B: 0.0, A: 1.0 },
},
} as const;

// MAINTENANCE_TODO: Add BT.2020 video in table.
// Video container and codec defines several transform ops to apply to raw decoded frame to display.
// Our test cases covers 'visible rect' and 'rotation'.
Expand Down Expand Up @@ -350,6 +359,47 @@ type VideoName = keyof typeof kVideoInfo;
export const kVideoNames: readonly VideoName[] = keysOf(kVideoInfo);

export const kPredefinedColorSpace = ['display-p3', 'srgb'] as const;

export const kImageInfo = makeTable({
table: {
'four-colors.jpg': {
display: {
topLeftColor: 'yellow',
topRightColor: 'red',
bottomLeftColor: 'blue',
bottomRightColor: 'green',
},
},
'four-colors-rotate-90-cw.jpg': {
display: {
topLeftColor: 'blue',
topRightColor: 'yellow',
bottomLeftColor: 'green',
bottomRightColor: 'red',
},
},
'four-colors-rotate-180-cw.jpg': {
display: {
topLeftColor: 'green',
topRightColor: 'blue',
bottomLeftColor: 'red',
bottomRightColor: 'yellow',
},
},
'four-colors-rotate-270-cw.jpg': {
display: {
topLeftColor: 'red',
topRightColor: 'green',
bottomLeftColor: 'yellow',
bottomRightColor: 'blue',
},
},
},
} as const);

type ImageName = keyof typeof kImageInfo;
export const kImageNames: readonly ImageName[] = keysOf(kImageInfo);

/**
* Starts playing a video and waits for it to be consumable.
* Returns a promise which resolves after `callback` (which may be async) completes.
Expand Down