export const description = `
copyToTexture with HTMLCanvasElement and OffscreenCanvas sources.
`;
import { makeTestGroup } from '../../../common/framework/test_group.js';
import { skipTestCase } from '../../../common/util/util.js';
import { kCanvasAlphaModes } from '../../capability_info.js';
import {
getBaseFormatForRegularTextureFormat,
isTextureFormatPossiblyUsableWithCopyExternalImageToTexture,
kRegularTextureFormats,
RegularTextureFormat,
} from '../../format_info.js';
import { TextureUploadingUtils } from '../../util/copy_to_texture.js';
import { CanvasType, kAllCanvasTypes, createCanvas } from '../../util/create_elements.js';
import { TexelCompareOptions } from '../../util/texture/texture_ok.js';
class F extends TextureUploadingUtils {
init2DCanvasContentWithColorSpace({
width,
height,
colorSpace,
}: {
width: number;
height: number;
colorSpace: 'srgb' | 'display-p3';
}): {
canvas: HTMLCanvasElement | OffscreenCanvas;
expectedSourceData: Uint8ClampedArray;
} {
const canvas = createCanvas(this, 'onscreen', width, height);
let canvasContext = null;
canvasContext = canvas.getContext('2d', { colorSpace });
if (canvasContext === null) {
this.skip('onscreen canvas 2d context not available');
}
if (
typeof canvasContext.getContextAttributes === 'undefined' ||
typeof canvasContext.getContextAttributes().colorSpace === 'undefined'
) {
this.skip('color space attr is not supported for canvas 2d context');
}
const SOURCE_PIXEL_BYTES = 4;
const imagePixels = new Uint8ClampedArray(SOURCE_PIXEL_BYTES * width * height);
const rectWidth = Math.floor(width / 2);
const rectHeight = Math.floor(height / 2);
const alphaValue = 153;
let pixelStartPos = 0;
for (let i = 0; i < rectHeight; ++i) {
for (let j = 0; j < rectWidth; ++j) {
pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
imagePixels[pixelStartPos] = 255;
imagePixels[pixelStartPos + 1] = 0;
imagePixels[pixelStartPos + 2] = 0;
imagePixels[pixelStartPos + 3] = alphaValue;
}
}
for (let i = 0; i < rectHeight; ++i) {
for (let j = rectWidth; j < width; ++j) {
pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
imagePixels[pixelStartPos] = 0;
imagePixels[pixelStartPos + 1] = 255;
imagePixels[pixelStartPos + 2] = 0;
imagePixels[pixelStartPos + 3] = alphaValue;
}
}
for (let i = rectHeight; i < height; ++i) {
for (let j = 0; j < rectWidth; ++j) {
pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
imagePixels[pixelStartPos] = 0;
imagePixels[pixelStartPos + 1] = 0;
imagePixels[pixelStartPos + 2] = 255;
imagePixels[pixelStartPos + 3] = alphaValue;
}
}
for (let i = rectHeight; i < height; ++i) {
for (let j = rectWidth; j < width; ++j) {
pixelStartPos = (i * width + j) * SOURCE_PIXEL_BYTES;
imagePixels[pixelStartPos] = 255;
imagePixels[pixelStartPos + 1] = 0;
imagePixels[pixelStartPos + 2] = 255;
imagePixels[pixelStartPos + 3] = alphaValue;
}
}
const imageData = new ImageData(imagePixels, width, height, { colorSpace });
if (typeof imageData.colorSpace === 'undefined') {
this.skip('color space attr is not supported for ImageData');
}
const ctx = canvasContext as CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D;
ctx.putImageData(imageData, 0, 0);
return {
canvas,
expectedSourceData: this.getExpectedReadbackFor2DCanvas(canvasContext, width, height),
};
}
init2DCanvasContent({
canvasType,
width,
height,
}: {
canvasType: CanvasType;
width: number;
height: number;
}): {
canvas: HTMLCanvasElement | OffscreenCanvas;
expectedSourceData: Uint8ClampedArray;
} {
const canvas = createCanvas(this, canvasType, width, height);
let canvasContext = null;
canvasContext = canvas.getContext('2d') as CanvasRenderingContext2D;
if (canvasContext === null) {
this.skip(canvasType + ' canvas 2d context not available');
}
const ctx = canvasContext;
this.paint2DCanvas(ctx, width, height, 0.6);
return {
canvas,
expectedSourceData: this.getExpectedReadbackFor2DCanvas(canvasContext, width, height),
};
}
private paint2DCanvas(
ctx: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
width: number,
height: number,
alphaValue: number
) {
const rectWidth = Math.floor(width / 2);
const rectHeight = Math.floor(height / 2);
ctx.fillStyle = `rgba(255, 0, 0, ${alphaValue})`;
ctx.fillRect(0, 0, rectWidth, rectHeight);
ctx.fillStyle = `rgba(0, 255, 0, ${alphaValue})`;
ctx.fillRect(rectWidth, 0, width - rectWidth, rectHeight);
ctx.fillStyle = `rgba(0, 0, 255, ${alphaValue})`;
ctx.fillRect(0, rectHeight, rectWidth, height - rectHeight);
ctx.fillStyle = `rgba(255, 0, 255, ${alphaValue})`;
ctx.fillRect(rectWidth, rectHeight, width - rectWidth, height - rectHeight);
}
initGLCanvasContent({
canvasType,
contextName,
width,
height,
premultiplied,
}: {
canvasType: CanvasType;
contextName: 'webgl' | 'webgl2';
width: number;
height: number;
premultiplied: boolean;
}): {
canvas: HTMLCanvasElement | OffscreenCanvas;
expectedSourceData: Uint8ClampedArray;
} {
const canvas = createCanvas(this, canvasType, width, height);
const gl = (canvas as HTMLCanvasElement).getContext(contextName, {
premultipliedAlpha: premultiplied,
}) as WebGLRenderingContext | WebGL2RenderingContext | null;
if (gl === null) {
this.skip(canvasType + ' canvas ' + contextName + ' context not available');
}
this.trackForCleanup(gl);
const rectWidth = Math.floor(width / 2);
const rectHeight = Math.floor(height / 2);
const alphaValue = 0.6;
const colorValue = premultiplied ? alphaValue : 1.0;
gl.enable(gl.SCISSOR_TEST);
gl.scissor(0, 0, rectWidth, rectHeight);
gl.clearColor(colorValue, 0.0, 0.0, alphaValue);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(rectWidth, 0, width - rectWidth, rectHeight);
gl.clearColor(0.0, colorValue, 0.0, alphaValue);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(0, rectHeight, rectWidth, height - rectHeight);
gl.clearColor(0.0, 0.0, colorValue, alphaValue);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.scissor(rectWidth, rectHeight, width - rectWidth, height - rectHeight);
gl.clearColor(colorValue, colorValue, colorValue, alphaValue);
gl.clear(gl.COLOR_BUFFER_BIT);
return {
canvas,
expectedSourceData: this.getExpectedReadbackForWebGLCanvas(gl, width, height),
};
}
private getDataToInitSourceWebGPUCanvas(
width: number,
height: number,
alphaMode: GPUCanvasAlphaMode
): Uint8ClampedArray {
const rectWidth = Math.floor(width / 2);
const rectHeight = Math.floor(height / 2);
const alphaValue = 153;
const colorValue = alphaValue;
const initialData = new Uint8ClampedArray(4 * width * height);
const maxRectHeightIndex = width * rectHeight;
for (let pixelIndex = 0; pixelIndex < initialData.length / 4; ++pixelIndex) {
const index = pixelIndex * 4;
if (pixelIndex < maxRectHeightIndex) {
if (pixelIndex % width < rectWidth) {
initialData[index] = colorValue;
initialData[index + 1] = 0;
initialData[index + 2] = 0;
initialData[index + 3] = alphaValue;
} else {
initialData[index] = 0;
initialData[index + 1] = colorValue;
initialData[index + 2] = 0;
initialData[index + 3] = alphaValue;
}
} else {
if (pixelIndex % width < rectWidth) {
initialData[index] = 0;
initialData[index + 1] = 0;
initialData[index + 2] = colorValue;
initialData[index + 3] = alphaValue;
} else {
initialData[index] = colorValue;
initialData[index + 1] = colorValue;
initialData[index + 2] = colorValue;
initialData[index + 3] = alphaValue;
}
}
}
return initialData;
}
initSourceWebGPUCanvas({
device,
canvasType,
width,
height,
alphaMode,
}: {
device: GPUDevice;
canvasType: CanvasType;
width: number;
height: number;
alphaMode: GPUCanvasAlphaMode;
}): {
canvas: HTMLCanvasElement | OffscreenCanvas;
expectedSourceData: Uint8ClampedArray;
} {
const canvas = createCanvas(this, canvasType, width, height);
const gpuContext = canvas.getContext('webgpu');
if (!(gpuContext instanceof GPUCanvasContext)) {
this.skip(canvasType + ' canvas webgpu context not available');
}
gpuContext.configure({
device,
format: 'bgra8unorm',
usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC,
alphaMode,
});
const initialData = this.getDataToInitSourceWebGPUCanvas(width, height, alphaMode);
const canvasTexture = gpuContext.getCurrentTexture();
device.queue.writeTexture(
{ texture: canvasTexture },
initialData,
{
bytesPerRow: width * 4,
rowsPerImage: height,
},
{
width,
height,
depthOrArrayLayers: 1,
}
);
return {
canvas,
expectedSourceData: this.getExpectedReadbackForWebGPUCanvas(width, height, alphaMode),
};
}
private getExpectedReadbackFor2DCanvas(
context: CanvasRenderingContext2D | OffscreenCanvasRenderingContext2D,
width: number,
height: number
): Uint8ClampedArray {
return context.getImageData(0, 0, width, height).data;
}
private getExpectedReadbackForWebGLCanvas(
gl: WebGLRenderingContext | WebGL2RenderingContext,
width: number,
height: number
): Uint8ClampedArray {
const bytesPerPixel = 4;
const sourcePixels = new Uint8ClampedArray(width * height * bytesPerPixel);
gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, sourcePixels);
return this.doFlipY(sourcePixels, width, height, bytesPerPixel);
}
private getExpectedReadbackForWebGPUCanvas(
width: number,
height: number,
alphaMode: GPUCanvasAlphaMode
): Uint8ClampedArray {
const bytesPerPixel = 4;
const rgbaPixels = this.getDataToInitSourceWebGPUCanvas(width, height, alphaMode);
for (let i = 0; i < height; ++i) {
for (let j = 0; j < width; ++j) {
const pixelPos = i * width + j;
const r = rgbaPixels[pixelPos * bytesPerPixel + 2];
if (alphaMode === 'opaque') {
rgbaPixels[pixelPos * bytesPerPixel + 3] = 255;
}
rgbaPixels[pixelPos * bytesPerPixel + 2] = rgbaPixels[pixelPos * bytesPerPixel];
rgbaPixels[pixelPos * bytesPerPixel] = r;
}
}
return rgbaPixels;
}
doCopyContentsTest(
source: HTMLCanvasElement | OffscreenCanvas,
expectedSourceImage: Uint8ClampedArray,
p: {
width: number;
height: number;
dstColorFormat: RegularTextureFormat;
srcDoFlipYDuringCopy: boolean;
srcPremultiplied: boolean;
dstPremultiplied: boolean;
}
) {
const dst = this.createTextureTracked({
size: {
width: p.width,
height: p.height,
depthOrArrayLayers: 1,
},
format: p.dstColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const baseFormat = getBaseFormatForRegularTextureFormat(p.dstColorFormat);
const expFormat = baseFormat ?? p.dstColorFormat;
const expectedDestinationImage = this.getExpectedDstPixelsFromSrcPixels({
srcPixels: expectedSourceImage,
srcOrigin: [0, 0],
srcSize: [p.width, p.height],
dstOrigin: [0, 0],
dstSize: [p.width, p.height],
subRectSize: [p.width, p.height],
format: expFormat,
flipSrcBeforeCopy: false,
srcDoFlipYDuringCopy: p.srcDoFlipYDuringCopy,
conversion: {
srcPremultiplied: p.srcPremultiplied,
dstPremultiplied: p.dstPremultiplied,
},
});
this.doTestAndCheckResult(
{ source, origin: { x: 0, y: 0 }, flipY: p.srcDoFlipYDuringCopy },
{
texture: dst,
origin: { x: 0, y: 0 },
colorSpace: 'srgb',
premultipliedAlpha: p.dstPremultiplied,
},
expectedDestinationImage,
{ width: p.width, height: p.height, depthOrArrayLayers: 1 },
{ maxDiffULPsForNormFormat: 1, maxDiffULPsForFloatFormat: 1 }
);
}
}
export const g = makeTestGroup(F);
g.test('copy_contents_from_2d_context_canvas')
.desc(
`
Test HTMLCanvasElement and OffscreenCanvas with 2d context
can be copied to WebGPU texture correctly.
It creates HTMLCanvasElement/OffscreenCanvas with '2d'.
Use fillRect(2d context) to render red rect for top-left,
green rect for top-right, blue rect for bottom-left and white for bottom-right.
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the canvas contents.
Provide premultiplied input if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and unpremultiplied input 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 canvas type
- Valid 2d context type
- Valid dstColorFormat of copyExternalImageToTexture()
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- TODO(#913): color space tests need to be added
And the expected results are all passed.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('dstColorFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstColorFormat))
.combine('dstAlphaMode', kCanvasAlphaModes)
.combine('srcDoFlipYDuringCopy', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15])
.combine('height', [1, 2, 4, 15])
)
.fn(t => {
const { width, height, canvasType, dstAlphaMode, dstColorFormat } = t.params;
t.skipIfTextureFormatNotSupported(dstColorFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstColorFormat);
const { canvas, expectedSourceData } = t.init2DCanvasContent({
canvasType,
width,
height,
});
t.doCopyContentsTest(canvas, expectedSourceData, {
srcPremultiplied: false,
dstPremultiplied: dstAlphaMode === 'premultiplied',
...t.params,
});
});
g.test('copy_contents_from_gl_context_canvas')
.desc(
`
Test HTMLCanvasElement and OffscreenCanvas with webgl/webgl2 context
can be copied to WebGPU texture correctly.
It creates HTMLCanvasElement/OffscreenCanvas with webgl'/'webgl2'.
Use scissor + clear to render red rect for top-left, green rect
for top-right, blue rect for bottom-left and white for bottom-right.
And do premultiply alpha in advance if the webgl/webgl2 context is created
with premultipliedAlpha : true.
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the canvas contents.
Provide premultiplied input if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and unpremultiplied input 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 canvas type
- Valid webgl/webgl2 context type
- Valid dstColorFormat of copyExternalImageToTexture()
- Valid source image alphaMode
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo'(named 'srcDoFlipYDuringCopy' in cases)
- TODO: color space tests need to be added
And the expected results are all passed.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('contextName', ['webgl', 'webgl2'] as const)
.combine('dstColorFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstColorFormat))
.combine('srcPremultiplied', [true, false])
.combine('dstAlphaMode', kCanvasAlphaModes)
.combine('srcDoFlipYDuringCopy', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15])
.combine('height', [1, 2, 4, 15])
)
.fn(t => {
const {
width,
height,
canvasType,
contextName,
srcPremultiplied,
dstAlphaMode,
dstColorFormat,
} = t.params;
t.skipIfTextureFormatNotSupported(dstColorFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstColorFormat);
const { canvas, expectedSourceData } = t.initGLCanvasContent({
canvasType,
contextName,
width,
height,
premultiplied: srcPremultiplied,
});
t.doCopyContentsTest(canvas, expectedSourceData, {
dstPremultiplied: dstAlphaMode === 'premultiplied',
...t.params,
});
});
g.test('copy_contents_from_gpu_context_canvas')
.desc(
`
Test HTMLCanvasElement and OffscreenCanvas with webgpu context
can be copied to WebGPU texture correctly.
It creates HTMLCanvasElement/OffscreenCanvas with 'webgpu'.
Use writeTexture to copy pixels to back buffer. The results are:
red rect for top-left, green rect for top-right, blue rect for bottom-left
and white for bottom-right.
TODO: Actually test alphaMode = opaque.
And do premultiply alpha in advance if the webgpu context is created
with alphaMode="premultiplied".
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the canvas contents.
Provide premultiplied input if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and unpremultiplied input 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 canvas type
- Source WebGPU Canvas lives in the same GPUDevice or different GPUDevice as test
- Valid dstColorFormat of copyExternalImageToTexture()
- TODO: test more source image alphaMode
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo'(named 'srcDoFlipYDuringCopy' in cases)
- TODO: color space tests need to be added
And the expected results are all passed.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('srcAndDstInSameGPUDevice', [true, false])
.combine('dstColorFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstColorFormat))
.combine('srcAlphaMode', ['premultiplied'] as const)
.combine('dstAlphaMode', kCanvasAlphaModes)
.combine('srcDoFlipYDuringCopy', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15])
.combine('height', [1, 2, 4, 15])
)
.beforeAllSubcases(t => {
t.usesMismatchedDevice();
})
.fn(t => {
const {
width,
height,
canvasType,
srcAndDstInSameGPUDevice,
srcAlphaMode,
dstAlphaMode,
dstColorFormat,
} = t.params;
t.skipIfTextureFormatNotSupported(dstColorFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstColorFormat);
const device = srcAndDstInSameGPUDevice ? t.device : t.mismatchedDevice;
const { canvas: source, expectedSourceData } = t.initSourceWebGPUCanvas({
device,
canvasType,
width,
height,
alphaMode: srcAlphaMode,
});
t.doCopyContentsTest(source, expectedSourceData, {
srcPremultiplied: srcAlphaMode === 'premultiplied',
dstPremultiplied: dstAlphaMode === 'premultiplied',
...t.params,
});
});
g.test('copy_contents_from_bitmaprenderer_context_canvas')
.desc(
`
Test HTMLCanvasElement and OffscreenCanvas with ImageBitmapRenderingContext
can be copied to WebGPU texture correctly.
It creates HTMLCanvasElement/OffscreenCanvas with 'bitmaprenderer'.
First, use fillRect(2d context) to render red rect for top-left,
green rect for top-right, blue rect for bottom-left and white for bottom-right on a
2d context canvas and create imageBitmap with that canvas. Use transferFromImageBitmap()
to render the imageBitmap to source canvas.
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the canvas contents.
Provide premultiplied input if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and unpremultiplied input 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 canvas type
- Valid ImageBitmapRendering context type
- Valid dstColorFormat of copyExternalImageToTexture()
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- TODO(#913): color space tests need to be added
And the expected results are all passed.
`
)
.params(u =>
u
.combine('canvasType', kAllCanvasTypes)
.combine('dstColorFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstColorFormat))
.combine('dstAlphaMode', kCanvasAlphaModes)
.combine('srcDoFlipYDuringCopy', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15])
.combine('height', [1, 2, 4, 15])
)
.fn(async t => {
const { width, height, canvasType, dstAlphaMode, dstColorFormat } = t.params;
t.skipIfTextureFormatNotSupported(dstColorFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstColorFormat);
const canvas = createCanvas(t, canvasType, width, height);
const imageBitmapRenderingContext = canvas.getContext('bitmaprenderer');
if (!(imageBitmapRenderingContext instanceof ImageBitmapRenderingContext)) {
skipTestCase(canvasType + ' canvas imageBitmap rendering context not available');
}
const { canvas: sourceContentCanvas, expectedSourceData } = t.init2DCanvasContent({
canvasType,
width,
height,
});
const imageBitmap = await createImageBitmap(sourceContentCanvas, { premultiplyAlpha: 'none' });
imageBitmapRenderingContext.transferFromImageBitmap(imageBitmap);
t.doCopyContentsTest(canvas, expectedSourceData, {
srcPremultiplied: false,
dstPremultiplied: dstAlphaMode === 'premultiplied',
...t.params,
});
});
g.test('color_space_conversion')
.desc(
`
Test HTMLCanvasElement with 2d context can created with 'colorSpace' attribute.
Using CopyExternalImageToTexture to copy from such type of canvas needs
to do color space converting correctly.
It creates HTMLCanvasElement/OffscreenCanvas with '2d' and 'colorSpace' attributes.
Use fillRect(2d context) to render red rect for top-left,
green rect for top-right, blue rect for bottom-left and white for bottom-right.
Then call copyExternalImageToTexture() to do a full copy to the 0 mipLevel
of dst texture, and read the contents out to compare with the canvas contents.
Provide premultiplied input if 'premultipliedAlpha' in 'GPUCopyExternalImageDestInfo'
is set to 'true' and unpremultiplied input if it is set to 'false'.
If 'flipY' in 'GPUCopyExternalImageSourceInfo' is set to 'true', copy will ensure the result
is flipped.
If color space from source input and user defined dstTexture color space are different, the
result must convert the content to user defined color space
The tests covers:
- Valid dstColorFormat of copyExternalImageToTexture()
- Valid dest alphaMode
- Valid 'flipY' config in 'GPUCopyExternalImageSourceInfo' (named 'srcDoFlipYDuringCopy' in cases)
- Valid 'colorSpace' config in 'dstColorSpace'
And the expected results are all passed.
TODO: Enhance test data with colors that aren't always opaque and fully saturated.
TODO: Consider refactoring src data setup with TexelView.writeTextureData.
`
)
.params(u =>
u
.combine('srcColorSpace', ['srgb', 'display-p3'] as const)
.combine('dstColorSpace', ['srgb', 'display-p3'] as const)
.combine('dstColorFormat', kRegularTextureFormats)
.filter(t => isTextureFormatPossiblyUsableWithCopyExternalImageToTexture(t.dstColorFormat))
.combine('dstPremultiplied', [true, false])
.combine('srcDoFlipYDuringCopy', [true, false])
.beginSubcases()
.combine('width', [1, 2, 4, 15, 255, 256])
.combine('height', [1, 2, 4, 15, 255, 256])
)
.fn(t => {
const {
width,
height,
srcColorSpace,
dstColorSpace,
dstColorFormat,
dstPremultiplied,
srcDoFlipYDuringCopy,
} = t.params;
t.skipIfTextureFormatNotSupported(dstColorFormat);
t.skipIfTextureFormatPossiblyNotUsableWithCopyExternalImageToTexture(dstColorFormat);
const { canvas, expectedSourceData } = t.init2DCanvasContentWithColorSpace({
width,
height,
colorSpace: srcColorSpace,
});
const dst = t.createTextureTracked({
size: { width, height },
format: dstColorFormat,
usage:
GPUTextureUsage.COPY_DST | GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
});
const expectedDestinationImage = t.getExpectedDstPixelsFromSrcPixels({
srcPixels: expectedSourceData,
srcOrigin: [0, 0],
srcSize: [width, height],
dstOrigin: [0, 0],
dstSize: [width, height],
subRectSize: [width, height],
format: getBaseFormatForRegularTextureFormat(dstColorFormat) ?? dstColorFormat,
flipSrcBeforeCopy: false,
srcDoFlipYDuringCopy,
conversion: {
srcPremultiplied: false,
dstPremultiplied,
srcColorSpace,
dstColorSpace,
},
});
const texelCompareOptions: TexelCompareOptions = {
maxFractionalDiff: 0,
maxDiffULPsForNormFormat: 1,
};
if (srcColorSpace !== dstColorSpace) {
if (dstColorFormat.endsWith('32float')) {
texelCompareOptions.maxFractionalDiff = 0.0003;
} else if (dstColorFormat.endsWith('16float')) {
texelCompareOptions.maxFractionalDiff = 0.0007;
} else if (dstColorFormat === 'rg11b10ufloat') {
texelCompareOptions.maxFractionalDiff = 0.015;
} else {
texelCompareOptions.maxFractionalDiff = 0.001;
}
} else {
texelCompareOptions.maxDiffULPsForFloatFormat = 1;
}
t.doTestAndCheckResult(
{ source: canvas, origin: { x: 0, y: 0 }, flipY: srcDoFlipYDuringCopy },
{
texture: dst,
origin: { x: 0, y: 0 },
colorSpace: dstColorSpace,
premultipliedAlpha: dstPremultiplied,
},
expectedDestinationImage,
{ width, height, depthOrArrayLayers: 1 },
texelCompareOptions
);
});