summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts581
1 files changed, 581 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts
new file mode 100644
index 0000000000..825a3c706b
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/web_platform/copyToTexture/ImageBitmap.spec.ts
@@ -0,0 +1,581 @@
+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 {
+ kTextureFormatInfo,
+ kValidTextureFormatsForCopyE2T,
+ EncodableTextureFormat,
+} from '../../capability_info.js';
+import { CopyToTextureUtils, kCopySubrectInfo } from '../../util/copy_to_texture.js';
+import { PerTexelComponent } from '../../util/texture/texel_data.js';
+import { TexelView } from '../../util/texture/texel_view.js';
+
+type TestColor = PerTexelComponent<number>;
+// None of the dst texture format is 'uint' or 'sint', so we can always use float value.
+const kColors = {
+ 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 },
+ Black: { R: 0.0, G: 0.0, B: 0.0, A: 1.0 },
+ White: { R: 1.0, G: 1.0, B: 1.0, A: 1.0 },
+ SemitransparentWhite: { R: 1.0, G: 1.0, B: 1.0, A: 0.6 },
+} as const;
+const kTestColorsOpaque = [
+ kColors.Red,
+ kColors.Green,
+ kColors.Blue,
+ kColors.Black,
+ kColors.White,
+] as const;
+const kTestColorsAll = [...kTestColorsOpaque, kColors.SemitransparentWhite] as const;
+
+function makeTestColorsTexelView({
+ testColors,
+ format,
+ width,
+ height,
+ premultiplied,
+ flipY,
+}: {
+ testColors: readonly TestColor[];
+ format: EncodableTextureFormat;
+ width: number;
+ height: number;
+ premultiplied: boolean;
+ flipY: boolean;
+}) {
+ return TexelView.fromTexelsAsColors(format, coords => {
+ const y = flipY ? height - coords.y - 1 : coords.y;
+ const pixelPos = y * width + coords.x;
+ const currentPixel = testColors[pixelPos % testColors.length];
+
+ if (premultiplied && currentPixel.A !== 1.0) {
+ return {
+ R: currentPixel.R! * currentPixel.A!,
+ G: currentPixel.G! * currentPixel.A!,
+ B: currentPixel.B! * currentPixel.A!,
+ A: currentPixel.A,
+ };
+ } else {
+ return currentPixel;
+ }
+ });
+}
+
+export const g = makeTestGroup(CopyToTextureUtils);
+
+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 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' 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('srcDoFlipYDuringCopy', [true, false])
+ .combine('dstColorFormat', kValidTextureFormatsForCopyE2T)
+ .combine('dstPremultiplied', [true, false])
+ .beginSubcases()
+ .combine('width', [1, 2, 4, 15, 255, 256])
+ .combine('height', [1, 2, 4, 15, 255, 256])
+ )
+ .fn(async t => {
+ const {
+ width,
+ height,
+ alpha,
+ orientation,
+ dstColorFormat,
+ dstPremultiplied,
+ srcDoFlipYDuringCopy,
+ } = t.params;
+
+ const testColors = kTestColorsAll;
+
+ // Generate correct expected values
+ const texelViewSource = makeTestColorsTexelView({
+ testColors,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ 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,
+ });
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const expFormat = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat;
+ 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,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied,
+ },
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied,
+ },
+ texelViewExpected,
+ { width, height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { 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 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' is set to 'true', copy will ensure the result
+ is flipped.
+
+ The tests covers:
+ - Valid 2D canvas
+ - Valid dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+
+ And the expected results are all passed.
+ `
+ )
+ .params(u =>
+ u
+ .combine('orientation', ['none', 'flipY'] as const)
+ .combine('srcDoFlipYDuringCopy', [true, false])
+ .combine('dstColorFormat', kValidTextureFormatsForCopyE2T)
+ .combine('dstPremultiplied', [true, false])
+ .beginSubcases()
+ .combine('width', [1, 2, 4, 15, 255, 256])
+ .combine('height', [1, 2, 4, 15, 255, 256])
+ )
+ .fn(async t => {
+ const {
+ width,
+ height,
+ orientation,
+ dstColorFormat,
+ dstPremultiplied,
+ srcDoFlipYDuringCopy,
+ } = t.params;
+
+ // CTS sometimes runs on worker threads, where document is not available.
+ // In this case, OffscreenCanvas can be used instead of <canvas>.
+ // But some browsers don't support OffscreenCanvas, and some don't
+ // support '2d' contexts on OffscreenCanvas.
+ // In this situation, the case will be skipped.
+ 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');
+ if (imageCanvasContext === null) {
+ t.skip('OffscreenCanvas "2d" context not available');
+ return;
+ }
+
+ // Generate non-transparent pixel data to avoid canvas
+ // different opt behaviour on putImageData()
+ // from browsers.
+ const texelViewSource = makeTestColorsTexelView({
+ testColors: kTestColorsOpaque,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width,
+ height,
+ flipY: false,
+ premultiplied: false,
+ });
+ // Generate correct expected values
+ const imageData = new ImageData(width, height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: width * 4,
+ rowsPerImage: height,
+ subrectOrigin: [0, 0],
+ subrectSize: { width, height },
+ });
+
+ // Use putImageData to prevent color space conversion.
+ imageCanvasContext.putImageData(imageData, 0, 0);
+
+ // MAINTENANCE_TODO: Workaround for @types/offscreencanvas missing an overload of
+ // `createImageBitmap` that takes `ImageBitmapOptions`.
+ const imageBitmap = await createImageBitmap(imageCanvas as HTMLCanvasElement, {
+ premultiplyAlpha: 'premultiply',
+ imageOrientation: orientation,
+ });
+
+ const dst = t.device.createTexture({
+ size: { width, height },
+ format: dstColorFormat,
+ usage:
+ GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const expFormat = kTextureFormatInfo[dstColorFormat].baseFormat ?? dstColorFormat;
+ 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,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied,
+ },
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy },
+ {
+ texture: dst,
+ origin: { x: 0, y: 0 },
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied,
+ },
+ texelViewExpected,
+ { width, height, depthOrArrayLayers: 1 },
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { 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 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' 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 dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' 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('srcDoFlipYDuringCopy', [true, false])
+ .combine('dstPremultiplied', [true, false])
+ .beginSubcases()
+ .combine('copySubRectInfo', kCopySubrectInfo)
+ )
+ .fn(async t => {
+ const {
+ copySubRectInfo,
+ alpha,
+ orientation,
+ dstPremultiplied,
+ srcDoFlipYDuringCopy,
+ } = t.params;
+
+ const testColors = kTestColorsAll;
+ const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
+ const kColorFormat = 'rgba8unorm';
+
+ // Generate correct expected values
+ const texelViewSource = makeTestColorsTexelView({
+ testColors,
+ format: kColorFormat, // ImageData is always in rgba8unorm format.
+ 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,
+ });
+
+ const dst = t.device.createTexture({
+ 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,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied,
+ },
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: srcOrigin, flipY: srcDoFlipYDuringCopy },
+ {
+ texture: dst,
+ origin: dstOrigin,
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied,
+ },
+ texelViewExpected,
+ copyExtent,
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { 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 'GPUImageCopyTextureTagged'
+ is set to 'true' and do unpremultiply alpha if it is set to 'false'.
+
+ If 'flipY' in 'GPUImageCopyExternalImage' 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 dstColorFormat of copyExternalImageToTexture()
+ - Valid source image alphaMode
+ - Valid dest alphaMode
+ - Valid 'flipY' config in 'GPUImageCopyExternalImage' (named 'srcDoFlipYDuringCopy' in cases)
+ - Valid subrect copies.
+
+ And the expected results are all passed.
+ `
+ )
+ .params(u =>
+ u
+ .combine('orientation', ['none', 'flipY'] as const)
+ .combine('srcDoFlipYDuringCopy', [true, false])
+ .combine('dstPremultiplied', [true, false])
+ .beginSubcases()
+ .combine('copySubRectInfo', kCopySubrectInfo)
+ )
+ .fn(async t => {
+ const { copySubRectInfo, orientation, dstPremultiplied, srcDoFlipYDuringCopy } = t.params;
+
+ const { srcOrigin, dstOrigin, srcSize, dstSize, copyExtent } = copySubRectInfo;
+ const kColorFormat = 'rgba8unorm';
+
+ // CTS sometimes runs on worker threads, where document is not available.
+ // In this case, OffscreenCanvas can be used instead of <canvas>.
+ // But some browsers don't support OffscreenCanvas, and some don't
+ // support '2d' contexts on OffscreenCanvas.
+ // In this situation, the case will be skipped.
+ 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');
+ if (imageCanvasContext === null) {
+ t.skip('OffscreenCanvas "2d" context not available');
+ return;
+ }
+
+ // Generate non-transparent pixel data to avoid canvas
+ // different opt behaviour on putImageData()
+ // from browsers.
+ const texelViewSource = makeTestColorsTexelView({
+ testColors: kTestColorsOpaque,
+ format: 'rgba8unorm', // ImageData is always in rgba8unorm format.
+ width: srcSize.width,
+ height: srcSize.height,
+ flipY: false,
+ premultiplied: false,
+ });
+ // Generate correct expected values
+ const imageData = new ImageData(srcSize.width, srcSize.height);
+ texelViewSource.writeTextureData(imageData.data, {
+ bytesPerRow: srcSize.width * 4,
+ rowsPerImage: srcSize.height,
+ subrectOrigin: [0, 0],
+ subrectSize: srcSize,
+ });
+
+ // Use putImageData to prevent color space conversion.
+ imageCanvasContext.putImageData(imageData, 0, 0);
+
+ // MAINTENANCE_TODO: Workaround for @types/offscreencanvas missing an overload of
+ // `createImageBitmap` that takes `ImageBitmapOptions`.
+ const imageBitmap = await createImageBitmap(imageCanvas as HTMLCanvasElement, {
+ premultiplyAlpha: 'premultiply',
+ imageOrientation: orientation,
+ });
+
+ const dst = t.device.createTexture({
+ 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,
+ conversion: {
+ srcPremultiplied: false,
+ dstPremultiplied,
+ },
+ });
+
+ t.doTestAndCheckResult(
+ { source: imageBitmap, origin: srcOrigin, flipY: srcDoFlipYDuringCopy },
+ {
+ texture: dst,
+ origin: dstOrigin,
+ colorSpace: 'srgb',
+ premultipliedAlpha: dstPremultiplied,
+ },
+ texelViewExpected,
+ copyExtent,
+ // 1.0 and 0.6 are representable precisely by all formats except rgb10a2unorm, but
+ // allow diffs of 1ULP since that's the generally-appropriate threshold.
+ { maxDiffULPsForFloatFormat: 1, maxDiffULPsForNormFormat: 1 }
+ );
+ });