export const description = `
copyExternalImageToTexture from ImageBitmaps created from various sources.
TODO: Test ImageBitmap generated from all possible ImageBitmapSource, relevant ImageBitmapOptions
(https://html.spec.whatwg.org/multipage/imagebitmap-and-animations.html#images-2)
and various source filetypes and metadata (weird dimensions, EXIF orientations, video rotations
and visible/crop rectangles, etc. (In theory these things are handled inside createImageBitmap,
but in theory could affect the internal representation of the ImageBitmap.)
TODO: Test zero-sized copies from all sources (just make sure params cover it) (e.g. 0x0, 0x4, 4x0).
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import {
getBaseFormatForRegularTextureFormat,
isTextureFormatPossiblyUsableWithCopyExternalImageToTexture,
kRegularTextureFormats,
} from '../../format_info.js';
import { TextureUploadingUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
import { kTestColorsAll, kTestColorsOpaque, makeTestColorsTexelView } from './util.js';
export const g = makeTestGroup(TextureUploadingUtils);
g.test('from_ImageData')
.desc(
`
Test ImageBitmap generated from ImageData can be copied to WebGPU
texture correctly. These imageBitmaps are highly possible living
in CPU back resource.
It generates pixels in ImageData one by one based on a color list:
[Red, Green, Blue, Black, White, SemitransparentWhite].
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the ImageBitmap contents.
Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and do unpremultiply alpha if it is set to 'false'.
If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped.
The tests covers:
- Valid dstFormat of copyExternalImageToTexture()
- Valid source image alphaMode
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcFlipYInCopy' in cases)
And the expected results are all passed.
`
)
.params(u =>
u
.combine('alpha', ['none', 'premultiply'] as const)
.combine('orientation', ['none', 'flipY'] as const)
.combine('colorSpaceConversion', ['none', 'default'] as const)
.combine('srcFlipYInCopy', [true, false])
.combine('dstFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstFormat))
.combine('dstPremultiplied', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15, 255, 256])
.combine('height', [1, 2, 4, 15, 255, 256])
)
.beforeAllSubcases(t => {
t.skipIf(typeof ImageData === 'undefined', 'ImageData does not exist in this environment');
})
.fn(async t => {
const {
width,
height,
alpha,
orientation,
colorSpaceConversion,
dstFormat,
dstPremultiplied,
srcFlipYInCopy,
} = t.params;
t.skipIfTextureFormatNotSupported(dstFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstFormat);
const testColors = kTestColorsAll;
const texelViewSource = makeTestColorsTexelView({
testColors,
format: 'rgba8unorm',
width,
height,
flipY: false,
premultiplied: false,
});
const imageData = new ImageData(width, height);
texelViewSource.writeTextureData(imageData.data, {
bytesPerRow: width * 4,
rowsPerImage: height,
subrectOrigin: [0, 0],
subrectSize: { width, height },
});
const imageBitmap = await createImageBitmap(imageData, {
premultiplyAlpha: alpha,
imageOrientation: orientation,
colorSpaceConversion,
});
const dst = t.createTextureTracked({
size: { width, height },
format: dstFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const expFormat = getBaseFormatForRegularTextureFormat(dstFormat) ?? dstFormat;
const flipSrcBeforeCopy = orientation === 'flipY';
const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
srcPixels: imageData.data,
srcOrigin: [0, 0],
srcSize: [width, height],
dstOrigin: [0, 0],
dstSize: [width, height],
subRectSize: [width, height],
format: expFormat,
flipSrcBeforeCopy,
srcDoFlipYDuringCopy: srcFlipYInCopy,
conversion: {
srcPremultiplied: false,
dstPremultiplied,
},
});
t.doTestAndCheckResult(
{ source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcFlipYInCopy },
{
texture: dst,
origin: { x: 0, y: 0 },
colorSpace: 'srgb',
premultipliedAlpha: dstPremultiplied,
},
texelViewExpected,
{ width, height, depthOrArrayLayers: 1 },
{ maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
);
});
g.test('from_canvas')
.desc(
`
Test ImageBitmap generated from canvas/offscreenCanvas can be copied to WebGPU
texture correctly. These imageBitmaps are highly possible living in GPU back resource.
It generates pixels in ImageData one by one based on a color list:
[Red, Green, Blue, Black, White].
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the ImageBitmap contents.
Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and do unpremultiply alpha if it is set to 'false'.
If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped.
The tests covers:
- Valid 2D canvas
- Valid dstFormat of copyExternalImageToTexture()
- Valid source image alphaMode
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcFlipYInCopy' in cases)
And the expected results are all passed.
`
)
.params(u =>
u
.combine('orientation', ['none', 'flipY'] as const)
.combine('colorSpaceConversion', ['none', 'default'] as const)
.combine('srcFlipYInCopy', [true, false])
.combine('dstFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstFormat))
.combine('dstPremultiplied', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15, 255, 256])
.combine('height', [1, 2, 4, 15, 255, 256])
)
.beforeAllSubcases(t => {
t.skipIf(typeof ImageData === 'undefined', 'ImageData does not exist in this environment');
})
.fn(async t => {
const {
width,
height,
orientation,
colorSpaceConversion,
dstFormat,
dstPremultiplied,
srcFlipYInCopy,
} = t.params;
t.skipIfTextureFormatNotSupported(dstFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstFormat);
let imageCanvas;
if (typeof document !== 'undefined') {
imageCanvas = document.createElement('canvas');
imageCanvas.width = width;
imageCanvas.height = height;
} else if (typeof OffscreenCanvas === 'undefined') {
t.skip('OffscreenCanvas is not supported');
return;
} else {
imageCanvas = new OffscreenCanvas(width, height);
}
const imageCanvasContext = imageCanvas.getContext('2d') as OffscreenCanvasRenderingContext2D;
if (imageCanvasContext === null) {
t.skip('OffscreenCanvas "2d" context not available');
return;
}
const texelViewSource = makeTestColorsTexelView({
testColors: kTestColorsOpaque,
format: 'rgba8unorm',
width,
height,
flipY: false,
premultiplied: false,
});
const imageData = new ImageData(width, height);
texelViewSource.writeTextureData(imageData.data, {
bytesPerRow: width * 4,
rowsPerImage: height,
subrectOrigin: [0, 0],
subrectSize: { width, height },
});
imageCanvasContext.putImageData(imageData, 0, 0);
const imageBitmap = await createImageBitmap(imageCanvas as HTMLCanvasElement, {
premultiplyAlpha: 'premultiply',
imageOrientation: orientation,
colorSpaceConversion,
});
const dst = t.createTextureTracked({
size: { width, height },
format: dstFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const expFormat = getBaseFormatForRegularTextureFormat(dstFormat) ?? dstFormat;
const flipSrcBeforeCopy = orientation === 'flipY';
const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
srcPixels: imageData.data,
srcOrigin: [0, 0],
srcSize: [width, height],
dstOrigin: [0, 0],
dstSize: [width, height],
subRectSize: [width, height],
format: expFormat,
flipSrcBeforeCopy,
srcDoFlipYDuringCopy: srcFlipYInCopy,
conversion: {
srcPremultiplied: false,
dstPremultiplied,
},
});
t.doTestAndCheckResult(
{ source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcFlipYInCopy },
{
texture: dst,
origin: { x: 0, y: 0 },
colorSpace: 'srgb',
premultipliedAlpha: dstPremultiplied,
},
texelViewExpected,
{ width, height, depthOrArrayLayers: 1 },
{ maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
);
});
g.test('copy_subrect_from_ImageData')
.desc(
`
Test ImageBitmap generated from ImageData can be copied to WebGPU
texture correctly. These imageBitmaps are highly possible living in CPU back resource.
It generates pixels in ImageData one by one based on a color list:
[Red, Green, Blue, Black, White].
Then call copyExternalImageToTexture() to do a subrect copy, based on a predefined copy
rect info list, to the 0 mipLevel of dst texture, and read the contents out to compare
with the ImageBitmap contents.
Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and do unpremultiply alpha if it is set to 'false'.
If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped, and origin is top-left consistantly.
The tests covers:
- Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
- Valid dstFormat of copyExternalImageToTexture()
- Valid source image alphaMode
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcFlipYInCopy' in cases)
- Valid subrect copies.
And the expected results are all passed.
`
)
.params(u =>
u
.combine('alpha', ['none', 'premultiply'] as const)
.combine('orientation', ['none', 'flipY'] as const)
.combine('colorSpaceConversion', ['none', 'default'] as const)
.combine('srcFlipYInCopy', [true, false])
.combine('dstPremultiplied', [true, false])
.beginSubcases()
.combine('copySubRectInfo', kCopySubrectInfo)
)
.beforeAllSubcases(t => {
t.skipIf(typeof ImageData === 'undefined', 'ImageData does not exist in this environment');
})
.fn(async t => {
const {
copySubRectInfo,
alpha,
orientation,
colorSpaceConversion,
dstPremultiplied,
srcFlipYInCopy,
} = t.params;
const testColors = kTestColorsAll;
const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
const kColorFormat = 'rgba8unorm';
const texelViewSource = makeTestColorsTexelView({
testColors,
format: kColorFormat,
width: srcSize.width,
height: srcSize.height,
flipY: false,
premultiplied: false,
});
const imageData = new ImageData(srcSize.width, srcSize.height);
texelViewSource.writeTextureData(imageData.data, {
bytesPerRow: srcSize.width * 4,
rowsPerImage: srcSize.height,
subrectOrigin: [0, 0],
subrectSize: srcSize,
});
const imageBitmap = await createImageBitmap(imageData, {
premultiplyAlpha: alpha,
imageOrientation: orientation,
colorSpaceConversion,
});
const dst = t.createTextureTracked({
size: dstSize,
format: kColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const flipSrcBeforeCopy = orientation === 'flipY';
const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
srcPixels: imageData.data,
srcOrigin,
srcSize,
dstOrigin,
dstSize,
subRectSize: copyExtent,
format: kColorFormat,
flipSrcBeforeCopy,
srcDoFlipYDuringCopy: srcFlipYInCopy,
conversion: {
srcPremultiplied: false,
dstPremultiplied,
},
});
t.doTestAndCheckResult(
{ source: imageBitmap, origin: srcOrigin, flipY: srcFlipYInCopy },
{
texture: dst,
origin: dstOrigin,
colorSpace: 'srgb',
premultipliedAlpha: dstPremultiplied,
},
texelViewExpected,
copyExtent,
{ maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
);
});
g.test('copy_subrect_from_2D_Canvas')
.desc(
`
Test ImageBitmap generated from canvas/offscreenCanvas can be copied to WebGPU
texture correctly. These imageBitmaps are highly possible living in GPU back resource.
It generates pixels in ImageData one by one based on a color list:
[Red, Green, Blue, Black, White].
Then call copyExternalImageToTexture() to do a subrect copy, based on a predefined copy
rect info list, to the 0 mipLevel of dst texture, and read the contents out to compare
with the ImageBitmap contents.
Do premultiply alpha during copy if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and do unpremultiply alpha if it is set to 'false'.
If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped, and origin is top-left consistantly.
The tests covers:
- Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
- Valid dstFormat of copyExternalImageToTexture()
- Valid source image alphaMode
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcFlipYInCopy' in cases)
- Valid subrect copies.
And the expected results are all passed.
`
)
.params(u =>
u
.combine('orientation', ['none', 'flipY'] as const)
.combine('colorSpaceConversion', ['none', 'default'] as const)
.combine('srcFlipYInCopy', [true, false])
.combine('dstPremultiplied', [true, false])
.beginSubcases()
.combine('copySubRectInfo', kCopySubrectInfo)
)
.fn(async t => {
const { copySubRectInfo, orientation, colorSpaceConversion, dstPremultiplied, srcFlipYInCopy } =
t.params;
const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
const kColorFormat = 'rgba8unorm';
let imageCanvas;
if (typeof document !== 'undefined') {
imageCanvas = document.createElement('canvas');
imageCanvas.width = srcSize.width;
imageCanvas.height = srcSize.height;
} else if (typeof OffscreenCanvas === 'undefined') {
t.skip('OffscreenCanvas is not supported');
return;
} else {
imageCanvas = new OffscreenCanvas(srcSize.width, srcSize.height);
}
const imageCanvasContext = imageCanvas.getContext('2d') as CanvasRenderingContext2D;
if (imageCanvasContext === null) {
t.skip('OffscreenCanvas "2d" context not available');
return;
}
const texelViewSource = makeTestColorsTexelView({
testColors: kTestColorsOpaque,
format: 'rgba8unorm',
width: srcSize.width,
height: srcSize.height,
flipY: false,
premultiplied: false,
});
const imageData = new ImageData(srcSize.width, srcSize.height);
texelViewSource.writeTextureData(imageData.data, {
bytesPerRow: srcSize.width * 4,
rowsPerImage: srcSize.height,
subrectOrigin: [0, 0],
subrectSize: srcSize,
});
imageCanvasContext.putImageData(imageData, 0, 0);
const imageBitmap = await createImageBitmap(imageCanvas as HTMLCanvasElement, {
premultiplyAlpha: 'premultiply',
imageOrientation: orientation,
colorSpaceConversion,
});
const dst = t.createTextureTracked({
size: dstSize,
format: kColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const flipSrcBeforeCopy = orientation === 'flipY';
const texelViewExpected = t.getExpectedDstPixelsFromSrcPixels({
srcPixels: imageData.data,
srcOrigin,
srcSize,
dstOrigin,
dstSize,
subRectSize: copyExtent,
format: kColorFormat,
flipSrcBeforeCopy,
srcDoFlipYDuringCopy: srcFlipYInCopy,
conversion: {
srcPremultiplied: false,
dstPremultiplied,
},
});
t.doTestAndCheckResult(
{ source: imageBitmap, origin: srcOrigin, flipY: srcFlipYInCopy },
{
texture: dst,
origin: dstOrigin,
colorSpace: 'srgb',
premultipliedAlpha: dstPremultiplied,
},
texelViewExpected,
copyExtent,
{ maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
);
});