summaryrefslogtreecommitdiffstats
path: root/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js
diff options
context:
space:
mode:
Diffstat (limited to 'testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js')
-rw-r--r--testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js816
1 files changed, 816 insertions, 0 deletions
diff --git a/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js
new file mode 100644
index 0000000000..a66b9a7747
--- /dev/null
+++ b/testing/web-platform/mozilla/tests/webgpu/webgpu/api/validation/queue/copyToTexture/CopyExternalImageToTexture.spec.js
@@ -0,0 +1,816 @@
+/**
+* AUTO-GENERATED - DO NOT EDIT. Source: https://github.com/gpuweb/cts
+**/export const description = `
+copyExternalImageToTexture Validation Tests in Queue.
+Note that we don't need to add tests on the destination texture dimension as currently we require
+the destination texture should have RENDER_ATTACHMENT usage, which is only allowed to be used on 2D
+textures.
+`;import {
+ getResourcePath,
+ getCrossOriginResourcePath } from
+'../../../../../common/framework/resources.js';
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import { raceWithRejectOnTimeout, unreachable, assert } from '../../../../../common/util/util.js';
+import { kTextureUsages } from '../../../../capability_info.js';
+import {
+ kTextureFormatInfo,
+ kTextureFormats,
+ kValidTextureFormatsForCopyE2T } from
+'../../../../format_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import {
+
+ createCanvas,
+ createOnscreenCanvas,
+ createOffscreenCanvas } from
+'../../../../util/create_elements.js';
+import { ValidationTest } from '../../validation_test.js';
+
+const kDefaultBytesPerPixel = 4; // using 'bgra8unorm' or 'rgba8unorm'
+const kDefaultWidth = 32;
+const kDefaultHeight = 32;
+const kDefaultDepth = 1;
+const kDefaultMipLevelCount = 6;
+
+function computeMipMapSize(width, height, mipLevel) {
+ return {
+ mipWidth: Math.max(width >> mipLevel, 1),
+ mipHeight: Math.max(height >> mipLevel, 1)
+ };
+}
+
+
+
+
+
+
+
+
+
+// Helper function to generate copySize for src OOB test
+function generateCopySizeForSrcOOB({ srcOrigin }) {
+ // OOB origin fails even with no-op copy.
+ if (srcOrigin.x > kDefaultWidth || srcOrigin.y > kDefaultHeight) {
+ return [{ width: 0, height: 0, depthOrArrayLayers: 0 }];
+ }
+
+ const justFitCopySize = {
+ width: kDefaultWidth - srcOrigin.x,
+ height: kDefaultHeight - srcOrigin.y,
+ depthOrArrayLayers: 1
+ };
+
+ return [
+ justFitCopySize, // correct size, maybe no-op copy.
+ {
+ width: justFitCopySize.width + 1,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in width
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height + 1,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in height
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers + 1
+ } // OOB in depthOrArrayLayers
+ ];
+}
+
+// Helper function to generate dst origin value based on mipLevel.
+function generateDstOriginValue({ mipLevel }) {
+ const origin = computeMipMapSize(kDefaultWidth, kDefaultHeight, mipLevel);
+
+ return [
+ { x: 0, y: 0, z: 0 },
+ { x: origin.mipWidth - 1, y: 0, z: 0 },
+ { x: 0, y: origin.mipHeight - 1, z: 0 },
+ { x: origin.mipWidth, y: 0, z: 0 },
+ { x: 0, y: origin.mipHeight, z: 0 },
+ { x: 0, y: 0, z: kDefaultDepth },
+ { x: origin.mipWidth + 1, y: 0, z: 0 },
+ { x: 0, y: origin.mipHeight + 1, z: 0 },
+ { x: 0, y: 0, z: kDefaultDepth + 1 }];
+
+}
+
+// Helper function to generate copySize for dst OOB test
+function generateCopySizeForDstOOB({ mipLevel, dstOrigin }) {
+ const dstMipMapSize = computeMipMapSize(kDefaultWidth, kDefaultHeight, mipLevel);
+
+ // OOB origin fails even with no-op copy.
+ if (
+ dstOrigin.x > dstMipMapSize.mipWidth ||
+ dstOrigin.y > dstMipMapSize.mipHeight ||
+ dstOrigin.z > kDefaultDepth)
+ {
+ return [{ width: 0, height: 0, depthOrArrayLayers: 0 }];
+ }
+
+ const justFitCopySize = {
+ width: dstMipMapSize.mipWidth - dstOrigin.x,
+ height: dstMipMapSize.mipHeight - dstOrigin.y,
+ depthOrArrayLayers: kDefaultDepth - dstOrigin.z
+ };
+
+ return [
+ justFitCopySize,
+ {
+ width: justFitCopySize.width + 1,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in width
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height + 1,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers
+ }, // OOB in height
+ {
+ width: justFitCopySize.width,
+ height: justFitCopySize.height,
+ depthOrArrayLayers: justFitCopySize.depthOrArrayLayers + 1
+ } // OOB in depthOrArrayLayers
+ ];
+}
+
+class CopyExternalImageToTextureTest extends ValidationTest {
+ onlineCrossOriginUrl = 'https://raw.githubusercontent.com/gpuweb/gpuweb/main/logo/webgpu.png';
+
+ getImageData(width, height) {
+ if (typeof ImageData === 'undefined') {
+ this.skip('ImageData is not supported.');
+ }
+
+ const pixelSize = kDefaultBytesPerPixel * width * height;
+ const imagePixels = new Uint8ClampedArray(pixelSize);
+ return new ImageData(imagePixels, width, height);
+ }
+
+ getCanvasWithContent(
+ canvasType,
+ width,
+ height,
+ content)
+ {
+ const canvas = createCanvas(this, canvasType, 1, 1);
+ const ctx = canvas.getContext('2d');
+ switch (canvasType) {
+ case 'onscreen':
+ assert(ctx instanceof CanvasRenderingContext2D);
+ break;
+ case 'offscreen':
+ assert(ctx instanceof OffscreenCanvasRenderingContext2D);
+ break;
+ }
+ ctx.drawImage(content, 0, 0);
+
+ return canvas;
+ }
+
+ createImageBitmap(image) {
+ if (typeof createImageBitmap === 'undefined') {
+ this.skip('Creating ImageBitmaps is not supported.');
+ }
+ return createImageBitmap(image);
+ }
+
+ runTest(
+ imageBitmapCopyView,
+ textureCopyView,
+ copySize,
+ validationScopeSuccess,
+ exceptionName)
+ {
+ // copyExternalImageToTexture will generate two types of errors. One is synchronous exceptions;
+ // the other is asynchronous validation error scope errors.
+ if (exceptionName) {
+ this.shouldThrow(exceptionName, () => {
+ this.device.queue.copyExternalImageToTexture(
+ imageBitmapCopyView,
+ textureCopyView,
+ copySize
+ );
+ });
+ } else {
+ this.expectValidationError(() => {
+ this.device.queue.copyExternalImageToTexture(
+ imageBitmapCopyView,
+ textureCopyView,
+ copySize
+ );
+ }, !validationScopeSuccess);
+ }
+ }
+}
+
+export const g = makeTestGroup(CopyExternalImageToTextureTest);
+
+g.test('source_image,crossOrigin').
+desc(
+ `
+ Test contents of source image is [clean, cross-origin].
+
+ Load crossOrigin image or same origin image and init the source
+ images.
+
+ Check whether 'SecurityError' is generated when source image is not origin clean.
+ `
+).
+params((u) =>
+u //
+.combine('sourceImage', ['canvas', 'offscreenCanvas', 'imageBitmap']).
+combine('isOriginClean', [true, false]).
+beginSubcases().
+combine('contentFrom', ['image', 'imageBitmap', 'canvas', 'offscreenCanvas']).
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { sourceImage, isOriginClean, contentFrom, copySize } = t.params;
+ if (typeof document === 'undefined') {
+ t.skip('DOM is not available to create an image element.');
+ }
+
+ const crossOriginUrl = getCrossOriginResourcePath('webgpu.png', t.onlineCrossOriginUrl);
+ const originCleanUrl = getResourcePath('webgpu.png');
+ const img = document.createElement('img');
+ img.src = isOriginClean ? originCleanUrl : crossOriginUrl;
+
+ // Load image
+ const timeout_ms = 5000;
+ try {
+ await raceWithRejectOnTimeout(img.decode(), timeout_ms, 'load image timeout');
+ } catch (e) {
+ if (isOriginClean) {
+ throw e;
+ } else {
+ t.skip('Cannot load cross origin image in time');
+ return;
+ }
+ }
+
+ // The externalImage contents can be updated by:
+ // - decoded image element
+ // - canvas/offscreenCanvas with image draw on it.
+ // - imageBitmap created with the image.
+ // Test covers all of these cases to ensure origin clean checks works.
+ let source;
+ switch (contentFrom) {
+ case 'image':{
+ source = img;
+ break;
+ }
+ case 'imageBitmap':{
+ source = await t.createImageBitmap(img);
+ break;
+ }
+ case 'canvas':
+ case 'offscreenCanvas':{
+ const canvasType = contentFrom === 'offscreenCanvas' ? 'offscreen' : 'onscreen';
+ source = t.getCanvasWithContent(canvasType, 1, 1, img);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ // Update the externalImage content with source.
+ let externalImage;
+ switch (sourceImage) {
+ case 'imageBitmap':{
+ externalImage = await t.createImageBitmap(source);
+ break;
+ }
+ case 'canvas':
+ case 'offscreenCanvas':{
+ const canvasType = contentFrom === 'offscreenCanvas' ? 'offscreen' : 'onscreen';
+ externalImage = t.getCanvasWithContent(canvasType, 1, 1, source);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.runTest(
+ { source: externalImage },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ isOriginClean ? '' : 'SecurityError'
+ );
+});
+
+g.test('source_imageBitmap,state').
+desc(
+ `
+ Test ImageBitmap as source image in state [valid, closed].
+
+ Call imageBitmap.close() to transfer the imageBitmap into
+ 'closed' state.
+
+ Check whether 'InvalidStateError' is generated when ImageBitmap is
+ closed.
+ `
+).
+params((u) =>
+u //
+.combine('closed', [false, true]).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { closed, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ if (closed) imageBitmap.close();
+
+ t.runTest(
+ { source: imageBitmap },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ closed ? 'InvalidStateError' : ''
+ );
+});
+
+g.test('source_canvas,state').
+desc(
+ `
+ Test HTMLCanvasElement as source image in state
+ [nocontext, 'placeholder-nocontext', 'placeholder-hascontext', valid].
+
+ Nocontext means using a canvas without any context as copy param.
+
+ Call 'transferControlToOffscreen' on HTMLCanvasElement will cause the
+ canvas control right transfer. And this canvas is in state 'placeholder'
+ Whether getContext in new generated offscreenCanvas won't affect the origin
+ canvas state.
+
+
+ Check whether 'OperationError' is generated when HTMLCanvasElement has no
+ context.
+
+ Check whether 'InvalidStateError' is generated when HTMLCanvasElement is
+ in 'placeholder' state.
+ `
+).
+params((u) =>
+u //
+.combine('state', ['nocontext', 'placeholder-nocontext', 'placeholder-hascontext', 'valid']).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn((t) => {
+ const { state, copySize } = t.params;
+ const canvas = createOnscreenCanvas(t, 1, 1);
+ if (typeof canvas.transferControlToOffscreen === 'undefined') {
+ t.skip("Browser doesn't support HTMLCanvasElement.transferControlToOffscreen");
+ return;
+ }
+
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let exceptionName = '';
+
+ switch (state) {
+ case 'nocontext':{
+ exceptionName = 'OperationError';
+ break;
+ }
+ case 'placeholder-nocontext':{
+ canvas.transferControlToOffscreen();
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'placeholder-hascontext':{
+ const offscreenCanvas = canvas.transferControlToOffscreen();
+ t.tryTrackForCleanup(offscreenCanvas.getContext('webgl'));
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'valid':{
+ assert(canvas.getContext('2d') !== null);
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ t.runTest(
+ { source: canvas },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ exceptionName
+ );
+});
+
+g.test('source_offscreenCanvas,state').
+desc(
+ `
+ Test OffscreenCanvas as source image in state [valid, detached].
+
+ Nocontext means using a canvas without any context as copy param.
+
+ Transfer OffscreenCanvas with MessageChannel will detach the OffscreenCanvas.
+
+ Check whether 'OperationError' is generated when HTMLCanvasElement has no
+ context.
+
+ Check whether 'InvalidStateError' is generated when OffscreenCanvas is
+ detached.
+ `
+).
+params((u) =>
+u //
+.combine('state', ['nocontext', 'detached-nocontext', 'detached-hascontext', 'valid']).
+beginSubcases().
+combine('getContextInOffscreenCanvas', [false, true]).
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { state, copySize } = t.params;
+ const offscreenCanvas = createOffscreenCanvas(t, 1, 1);
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let exceptionName = '';
+ switch (state) {
+ case 'nocontext':{
+ exceptionName = 'OperationError';
+ break;
+ }
+ case 'detached-nocontext':{
+ const messageChannel = new MessageChannel();
+ messageChannel.port1.postMessage(offscreenCanvas, [offscreenCanvas]);
+
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'detached-hascontext':{
+ const messageChannel = new MessageChannel();
+ const port2FirstMessage = new Promise((resolve) => {
+ messageChannel.port2.onmessage = (m) => resolve(m);
+ });
+
+ messageChannel.port1.postMessage(offscreenCanvas, [offscreenCanvas]);
+
+ const receivedOffscreenCanvas = await port2FirstMessage;
+ t.tryTrackForCleanup(receivedOffscreenCanvas.data.getContext('webgl'));
+
+ exceptionName = 'InvalidStateError';
+ break;
+ }
+ case 'valid':{
+ offscreenCanvas.getContext('webgl');
+ break;
+ }
+ default:
+ unreachable();
+ }
+
+ t.runTest(
+ { source: offscreenCanvas },
+ { texture: dstTexture },
+ copySize,
+ true, // No validation errors.
+ exceptionName
+ );
+});
+
+g.test('destination_texture,state').
+desc(
+ `
+ Test dst texture is [valid, invalid, destroyed].
+
+ Check that an error is generated when texture is an error texture.
+ Check that an error is generated when texture is in destroyed state.
+ `
+).
+params((u) =>
+u //
+.combine('state', kResourceStates).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { state, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.createTextureWithState(state);
+
+ t.runTest({ source: imageBitmap }, { texture: dstTexture }, copySize, state === 'valid');
+});
+
+g.test('destination_texture,device_mismatch').
+desc(
+ 'Tests copyExternalImageToTexture cannot be called with a destination texture created from another device'
+).
+paramsSubcasesOnly((u) => u.combine('mismatched', [true, false])).
+beforeAllSubcases((t) => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+}).
+fn(async (t) => {
+ const { mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+ const copySize = { width: 1, height: 1, depthOrArrayLayers: 1 };
+
+ const texture = sourceDevice.createTexture({
+ size: copySize,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+
+ t.runTest({ source: imageBitmap }, { texture }, copySize, !mismatched);
+});
+
+g.test('destination_texture,usage').
+desc(
+ `
+ Test dst texture usages
+
+ Check that an error is generated when texture is created without usage COPY_DST | RENDER_ATTACHMENT.
+ `
+).
+params((u) =>
+u //
+.combine('usage', kTextureUsages).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { usage, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage
+ });
+
+ t.runTest(
+ { source: imageBitmap },
+ { texture: dstTexture },
+ copySize,
+ !!(usage & GPUTextureUsage.COPY_DST && usage & GPUTextureUsage.RENDER_ATTACHMENT)
+ );
+});
+
+g.test('destination_texture,sample_count').
+desc(
+ `
+ Test dst texture sample count.
+
+ Check that an error is generated when sample count it not 1.
+ `
+).
+params((u) =>
+u //
+.combine('sampleCount', [1, 4]).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { sampleCount, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ sampleCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.runTest({ source: imageBitmap }, { texture: dstTexture }, copySize, sampleCount === 1);
+});
+
+g.test('destination_texture,mipLevel').
+desc(
+ `
+ Test dst mipLevel.
+
+ Check that an error is generated when mipLevel is too large.
+ `
+).
+params((u) =>
+u //
+.combine('mipLevel', [0, kDefaultMipLevelCount - 1, kDefaultMipLevelCount]).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+fn(async (t) => {
+ const { mipLevel, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+ const dstTexture = t.device.createTexture({
+ size: { width: kDefaultWidth, height: kDefaultHeight, depthOrArrayLayers: kDefaultDepth },
+ mipLevelCount: kDefaultMipLevelCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ t.runTest(
+ { source: imageBitmap },
+ { texture: dstTexture, mipLevel },
+ copySize,
+ mipLevel < kDefaultMipLevelCount
+ );
+});
+
+g.test('destination_texture,format').
+desc(
+ `
+ Test dst texture format.
+
+ Check that an error is generated when texture format is not valid.
+ `
+).
+params((u) =>
+u.
+combine('format', kTextureFormats).
+beginSubcases().
+combine('copySize', [
+{ width: 0, height: 0, depthOrArrayLayers: 0 },
+{ width: 1, height: 1, depthOrArrayLayers: 1 }]
+)
+).
+beforeAllSubcases((t) => {
+ const { format } = t.params;
+ t.skipIfTextureFormatNotSupported(format);
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+}).
+fn(async (t) => {
+ const { format, copySize } = t.params;
+
+ const imageBitmap = await t.createImageBitmap(t.getImageData(1, 1));
+
+ // createTexture with all possible texture format may have validation error when using
+ // compressed texture format.
+ t.device.pushErrorScope('validation');
+ const dstTexture = t.device.createTexture({
+ size: { width: 1, height: 1, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+ void t.device.popErrorScope();
+
+ const success = kValidTextureFormatsForCopyE2T.includes(format);
+
+ t.runTest({ source: imageBitmap }, { texture: dstTexture }, copySize, success);
+});
+
+g.test('OOB,source').
+desc(
+ `
+ Test source image origin and copy size
+
+ Check that an error is generated when source.externalImage.origin + copySize is too large.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('srcOrigin', [
+{ x: 0, y: 0 }, // origin is on top-left
+{ x: kDefaultWidth - 1, y: 0 }, // x near the border
+{ x: 0, y: kDefaultHeight - 1 }, // y is near the border
+{ x: kDefaultWidth, y: kDefaultHeight }, // origin is on bottom-right
+{ x: kDefaultWidth + 1, y: 0 }, // x is too large
+{ x: 0, y: kDefaultHeight + 1 } // y is too large
+]).
+expand('copySize', generateCopySizeForSrcOOB)
+).
+fn(async (t) => {
+ const { srcOrigin, copySize } = t.params;
+ const imageBitmap = await t.createImageBitmap(t.getImageData(kDefaultWidth, kDefaultHeight));
+ const dstTexture = t.device.createTexture({
+ size: {
+ width: kDefaultWidth + 1,
+ height: kDefaultHeight + 1,
+ depthOrArrayLayers: kDefaultDepth
+ },
+ mipLevelCount: kDefaultMipLevelCount,
+ format: 'bgra8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let success = true;
+
+ if (
+ srcOrigin.x + copySize.width > kDefaultWidth ||
+ srcOrigin.y + copySize.height > kDefaultHeight ||
+ copySize.depthOrArrayLayers > 1)
+ {
+ success = false;
+ }
+
+ t.runTest(
+ { source: imageBitmap, origin: srcOrigin },
+ { texture: dstTexture },
+ copySize,
+ true,
+ success ? '' : 'OperationError'
+ );
+});
+
+g.test('OOB,destination').
+desc(
+ `
+ Test dst texture copy origin and copy size
+
+ Check that an error is generated when destination.texture.origin + copySize is too large.
+ Check that 'OperationError' is generated when copySize.depth is larger than 1.
+ `
+).
+paramsSubcasesOnly((u) =>
+u.
+combine('mipLevel', [0, 1, kDefaultMipLevelCount - 2]).
+expand('dstOrigin', generateDstOriginValue).
+expand('copySize', generateCopySizeForDstOOB)
+).
+fn(async (t) => {
+ const { mipLevel, dstOrigin, copySize } = t.params;
+
+ const imageBitmap = await t.createImageBitmap(
+ t.getImageData(kDefaultWidth + 1, kDefaultHeight + 1)
+ );
+ const dstTexture = t.device.createTexture({
+ size: {
+ width: kDefaultWidth,
+ height: kDefaultHeight,
+ depthOrArrayLayers: kDefaultDepth
+ },
+ format: 'bgra8unorm',
+ mipLevelCount: kDefaultMipLevelCount,
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT
+ });
+
+ let success = true;
+ let hasOperationError = false;
+ const dstMipMapSize = computeMipMapSize(kDefaultWidth, kDefaultHeight, mipLevel);
+
+ if (
+ copySize.depthOrArrayLayers > 1 ||
+ dstOrigin.x + copySize.width > dstMipMapSize.mipWidth ||
+ dstOrigin.y + copySize.height > dstMipMapSize.mipHeight ||
+ dstOrigin.z + copySize.depthOrArrayLayers > kDefaultDepth)
+ {
+ success = false;
+ }
+ if (copySize.depthOrArrayLayers > 1) {
+ hasOperationError = true;
+ }
+
+ t.runTest(
+ { source: imageBitmap },
+ {
+ texture: dstTexture,
+ mipLevel,
+ origin: dstOrigin
+ },
+ copySize,
+ success,
+ hasOperationError ? 'OperationError' : ''
+ );
+}); \ No newline at end of file