summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts876
1 files changed, 876 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
new file mode 100644
index 0000000000..9d01055e6d
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/encoding/cmds/copyTextureToTexture.spec.ts
@@ -0,0 +1,876 @@
+export const description = `
+copyTextureToTexture tests.
+`;
+
+import { makeTestGroup } from '../../../../../common/framework/test_group.js';
+import {
+ kTextureFormatInfo,
+ kTextureFormats,
+ kCompressedTextureFormats,
+ kDepthStencilFormats,
+ kTextureUsages,
+ textureDimensionAndFormatCompatible,
+ kTextureDimensions,
+ kFeaturesForFormats,
+ filterFormatsByFeature,
+} from '../../../../capability_info.js';
+import { kResourceStates } from '../../../../gpu_test.js';
+import { align, lcm } from '../../../../util/math.js';
+import { ValidationTest } from '../../validation_test.js';
+
+class F extends ValidationTest {
+ TestCopyTextureToTexture(
+ source: GPUImageCopyTexture,
+ destination: GPUImageCopyTexture,
+ copySize: GPUExtent3D,
+ expectation: 'Success' | 'FinishError' | 'SubmitError'
+ ): void {
+ const commandEncoder = this.device.createCommandEncoder();
+ commandEncoder.copyTextureToTexture(source, destination, copySize);
+
+ if (expectation === 'FinishError') {
+ this.expectValidationError(() => {
+ commandEncoder.finish();
+ });
+ } else {
+ const cmd = commandEncoder.finish();
+ this.expectValidationError(() => {
+ this.device.queue.submit([cmd]);
+ }, expectation === 'SubmitError');
+ }
+ }
+
+ GetPhysicalSubresourceSize(
+ dimension: GPUTextureDimension,
+ textureSize: Required<GPUExtent3DDict>,
+ format: GPUTextureFormat,
+ mipLevel: number
+ ): Required<GPUExtent3DDict> {
+ const virtualWidthAtLevel = Math.max(textureSize.width >> mipLevel, 1);
+ const virtualHeightAtLevel = Math.max(textureSize.height >> mipLevel, 1);
+ const physicalWidthAtLevel = align(virtualWidthAtLevel, kTextureFormatInfo[format].blockWidth);
+ const physicalHeightAtLevel = align(
+ virtualHeightAtLevel,
+ kTextureFormatInfo[format].blockHeight
+ );
+
+ switch (dimension) {
+ case '1d':
+ return { width: physicalWidthAtLevel, height: 1, depthOrArrayLayers: 1 };
+ case '2d':
+ return {
+ width: physicalWidthAtLevel,
+ height: physicalHeightAtLevel,
+ depthOrArrayLayers: textureSize.depthOrArrayLayers,
+ };
+ case '3d':
+ return {
+ width: physicalWidthAtLevel,
+ height: physicalHeightAtLevel,
+ depthOrArrayLayers: Math.max(textureSize.depthOrArrayLayers >> mipLevel, 1),
+ };
+ }
+ }
+}
+
+export const g = makeTestGroup(F);
+
+g.test('copy_with_invalid_or_destroyed_texture')
+ .desc('Test copyTextureToTexture is an error when one of the textures is invalid or destroyed.')
+ .paramsSubcasesOnly(u =>
+ u //
+ .combine('srcState', kResourceStates)
+ .combine('dstState', kResourceStates)
+ )
+ .fn(async t => {
+ const { srcState, dstState } = t.params;
+
+ const textureDesc: GPUTextureDescriptor = {
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ };
+
+ const srcTexture = t.createTextureWithState(srcState, textureDesc);
+ const dstTexture = t.createTextureWithState(dstState, textureDesc);
+
+ const isSubmitSuccess = srcState === 'valid' && dstState === 'valid';
+ const isFinishSuccess = srcState !== 'invalid' && dstState !== 'invalid';
+ const expectation = isFinishSuccess
+ ? isSubmitSuccess
+ ? 'Success'
+ : 'SubmitError'
+ : 'FinishError';
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ expectation
+ );
+ });
+
+g.test('texture,device_mismatch')
+ .desc(
+ 'Tests copyTextureToTexture cannot be called with src texture or dst texture created from another device.'
+ )
+ .paramsSubcasesOnly([
+ { srcMismatched: false, dstMismatched: false }, // control case
+ { srcMismatched: true, dstMismatched: false },
+ { srcMismatched: false, dstMismatched: true },
+ ] as const)
+ .beforeAllSubcases(t => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+ })
+ .fn(async t => {
+ const { srcMismatched, dstMismatched } = t.params;
+
+ const size = { width: 4, height: 4, depthOrArrayLayers: 1 };
+ const format = 'rgba8unorm';
+
+ const srcTextureDevice = srcMismatched ? t.mismatchedDevice : t.device;
+ const srcTexture = srcTextureDevice.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ t.trackForCleanup(srcTexture);
+
+ const dstTextureDevice = dstMismatched ? t.mismatchedDevice : t.device;
+ const dstTexture = dstTextureDevice.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+ t.trackForCleanup(dstTexture);
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ srcMismatched || dstMismatched ? 'FinishError' : 'Success'
+ );
+ });
+
+g.test('mipmap_level')
+ .desc(
+ `
+Test copyTextureToTexture must specify mipLevels that are in range.
+- for various dimensions
+- for various mip level count in the texture
+- for various copy target mip level (in range and not in range)
+`
+ )
+ .params(u =>
+ u //
+ .combine('dimension', kTextureDimensions)
+ .beginSubcases()
+ .combineWithParams([
+ { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 0 },
+ { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 1, dstCopyLevel: 0 },
+ { srcLevelCount: 1, dstLevelCount: 1, srcCopyLevel: 0, dstCopyLevel: 1 },
+ { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 0 },
+ { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 2, dstCopyLevel: 0 },
+ { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 3, dstCopyLevel: 0 },
+ { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 2 },
+ { srcLevelCount: 3, dstLevelCount: 3, srcCopyLevel: 0, dstCopyLevel: 3 },
+ ] as const)
+ .unless(p => p.dimension === '1d' && (p.srcLevelCount !== 1 || p.dstLevelCount !== 1))
+ )
+
+ .fn(async t => {
+ const { srcLevelCount, dstLevelCount, srcCopyLevel, dstCopyLevel, dimension } = t.params;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: 32, height: 1, depthOrArrayLayers: 1 },
+ dimension,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC,
+ mipLevelCount: srcLevelCount,
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: 32, height: 1, depthOrArrayLayers: 1 },
+ dimension,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST,
+ mipLevelCount: dstLevelCount,
+ });
+
+ const isSuccess = srcCopyLevel < srcLevelCount && dstCopyLevel < dstLevelCount;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, mipLevel: srcCopyLevel },
+ { texture: dstTexture, mipLevel: dstCopyLevel },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('texture_usage')
+ .desc(
+ `
+Test that copyTextureToTexture source/destination need COPY_SRC/COPY_DST usages.
+- for all possible source texture usages
+- for all possible destination texture usages
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u //
+ .combine('srcUsage', kTextureUsages)
+ .combine('dstUsage', kTextureUsages)
+ )
+ .fn(async t => {
+ const { srcUsage, dstUsage } = t.params;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: srcUsage,
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: dstUsage,
+ });
+
+ const isSuccess =
+ srcUsage === GPUTextureUsage.COPY_SRC && dstUsage === GPUTextureUsage.COPY_DST;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 1, height: 1, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('sample_count')
+ .desc(
+ `
+Test that textures in copyTextureToTexture must have the same sample count.
+- for various source texture sample count
+- for various destination texture sample count
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u //
+ .combine('srcSampleCount', [1, 4])
+ .combine('dstSampleCount', [1, 4])
+ )
+ .fn(async t => {
+ const { srcSampleCount, dstSampleCount } = t.params;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: srcSampleCount,
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: dstSampleCount,
+ });
+
+ const isSuccess = srcSampleCount === dstSampleCount;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ { width: 4, height: 4, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('multisampled_copy_restrictions')
+ .desc(
+ `
+Test that copyTextureToTexture of multisampled texture must copy a whole subresource to a whole subresource.
+- for various origin for the source and destination of the copies.
+
+Note: this is only tested for 2D textures as it is the only dimension compatible with multisampling.
+TODO: Check the source and destination constraints separately.
+`
+ )
+ .paramsSubcasesOnly(u =>
+ u //
+ .combine('srcCopyOrigin', [
+ { x: 0, y: 0, z: 0 },
+ { x: 1, y: 0, z: 0 },
+ { x: 0, y: 1, z: 0 },
+ { x: 1, y: 1, z: 0 },
+ ])
+ .combine('dstCopyOrigin', [
+ { x: 0, y: 0, z: 0 },
+ { x: 1, y: 0, z: 0 },
+ { x: 0, y: 1, z: 0 },
+ { x: 1, y: 1, z: 0 },
+ ])
+ .expand('copyWidth', p => [32 - Math.max(p.srcCopyOrigin.x, p.dstCopyOrigin.x), 16])
+ .expand('copyHeight', p => [16 - Math.max(p.srcCopyOrigin.y, p.dstCopyOrigin.y), 8])
+ )
+ .fn(async t => {
+ const { srcCopyOrigin, dstCopyOrigin, copyWidth, copyHeight } = t.params;
+
+ const kWidth = 32;
+ const kHeight = 16;
+
+ // Currently we don't support multisampled 2D array textures and the mipmap level count of the
+ // multisampled textures must be 1.
+ const srcTexture = t.device.createTexture({
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4,
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: kWidth, height: kHeight, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_DST | GPUTextureUsage.RENDER_ATTACHMENT,
+ sampleCount: 4,
+ });
+
+ const isSuccess = copyWidth === kWidth && copyHeight === kHeight;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: srcCopyOrigin },
+ { texture: dstTexture, origin: dstCopyOrigin },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('texture_format_compatibility')
+ .desc(
+ `
+Test the formats of textures in copyTextureToTexture must be copy-compatible.
+- for all source texture formats
+- for all destination texture formats
+`
+ )
+ .params(u =>
+ u
+ .combine('srcFormatFeature', kFeaturesForFormats)
+ .combine('dstFormatFeature', kFeaturesForFormats)
+ .beginSubcases()
+ .expand('srcFormat', ({ srcFormatFeature }) =>
+ filterFormatsByFeature(srcFormatFeature, kTextureFormats)
+ )
+ .expand('dstFormat', ({ dstFormatFeature }) =>
+ filterFormatsByFeature(dstFormatFeature, kTextureFormats)
+ )
+ )
+ .beforeAllSubcases(t => {
+ const { srcFormatFeature, dstFormatFeature } = t.params;
+ t.selectDeviceOrSkipTestCase([srcFormatFeature, dstFormatFeature]);
+ })
+ .fn(async t => {
+ const { srcFormat, dstFormat } = t.params;
+ const srcFormatInfo = kTextureFormatInfo[srcFormat];
+ const dstFormatInfo = kTextureFormatInfo[dstFormat];
+
+ const textureSize = {
+ width: lcm(srcFormatInfo.blockWidth, dstFormatInfo.blockWidth),
+ height: lcm(srcFormatInfo.blockHeight, dstFormatInfo.blockHeight),
+ depthOrArrayLayers: 1,
+ };
+
+ const srcTexture = t.device.createTexture({
+ size: textureSize,
+ format: srcFormat,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+
+ const dstTexture = t.device.createTexture({
+ size: textureSize,
+ format: dstFormat,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+
+ // Allow copy between compatible format textures.
+ const srcBaseFormat = kTextureFormatInfo[srcFormat].baseFormat ?? srcFormat;
+ const dstBaseFormat = kTextureFormatInfo[dstFormat].baseFormat ?? dstFormat;
+ const isSuccess = srcBaseFormat === dstBaseFormat;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture },
+ { texture: dstTexture },
+ textureSize,
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('depth_stencil_copy_restrictions')
+ .desc(
+ `
+Test that depth textures subresources must be entirely copied in copyTextureToTexture
+- for various depth-stencil formats
+- for various copy origin and size offsets
+- for various source and destination texture sizes
+- for various source and destination mip levels
+
+Note: this is only tested for 2D textures as it is the only dimension compatible with depth-stencil.
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kDepthStencilFormats)
+ .beginSubcases()
+ .combine('copyBoxOffsets', [
+ { x: 0, y: 0, width: 0, height: 0 },
+ { x: 1, y: 0, width: 0, height: 0 },
+ { x: 0, y: 1, width: 0, height: 0 },
+ { x: 0, y: 0, width: -1, height: 0 },
+ { x: 0, y: 0, width: 0, height: -1 },
+ ])
+ .combine('srcTextureSize', [
+ { width: 64, height: 64, depthOrArrayLayers: 1 },
+ { width: 64, height: 32, depthOrArrayLayers: 1 },
+ { width: 32, height: 32, depthOrArrayLayers: 1 },
+ ])
+ .combine('dstTextureSize', [
+ { width: 64, height: 64, depthOrArrayLayers: 1 },
+ { width: 64, height: 32, depthOrArrayLayers: 1 },
+ { width: 32, height: 32, depthOrArrayLayers: 1 },
+ ])
+ .combine('srcCopyLevel', [1, 2])
+ .combine('dstCopyLevel', [0, 1])
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+ })
+ .fn(async t => {
+ const {
+ format,
+ copyBoxOffsets,
+ srcTextureSize,
+ dstTextureSize,
+ srcCopyLevel,
+ dstCopyLevel,
+ } = t.params;
+ const kMipLevelCount = 3;
+
+ const srcTexture = t.device.createTexture({
+ size: { width: srcTextureSize.width, height: srcTextureSize.height, depthOrArrayLayers: 1 },
+ format,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ const dstTexture = t.device.createTexture({
+ size: { width: dstTextureSize.width, height: dstTextureSize.height, depthOrArrayLayers: 1 },
+ format,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+
+ const srcSizeAtLevel = t.GetPhysicalSubresourceSize('2d', srcTextureSize, format, srcCopyLevel);
+ const dstSizeAtLevel = t.GetPhysicalSubresourceSize('2d', dstTextureSize, format, dstCopyLevel);
+
+ const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: 0 };
+
+ const copyWidth =
+ Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x;
+ const copyHeight =
+ Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y;
+
+ // Depth/stencil copies must copy whole subresources.
+ const isSuccess =
+ copyOrigin.x === 0 &&
+ copyOrigin.y === 0 &&
+ copyWidth === srcSizeAtLevel.width &&
+ copyHeight === srcSizeAtLevel.height &&
+ copyWidth === dstSizeAtLevel.width &&
+ copyHeight === dstSizeAtLevel.height;
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: 1 },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('copy_ranges')
+ .desc(
+ `
+Test that copyTextureToTexture copy boxes must be in range of the subresource.
+- for various dimensions
+- for various offsets to a full copy for the copy origin/size
+- for various copy mip levels
+`
+ )
+ .params(u =>
+ u
+ .combine('dimension', kTextureDimensions)
+ //.beginSubcases()
+ .combine('copyBoxOffsets', [
+ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 1, y: 0, z: 0, width: -1, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 1, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 1, z: 0, width: 0, height: -1, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 1, width: 0, height: 1, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 2, width: 0, height: 1, depthOrArrayLayers: 0 },
+ { x: 0, y: 0, z: 0, width: 1, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: 0, height: 1, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 1 },
+ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 0 },
+ { x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 },
+ { x: 0, y: 0, z: 2, width: 0, height: 0, depthOrArrayLayers: -1 },
+ ])
+ .unless(
+ p =>
+ p.dimension === '1d' &&
+ (p.copyBoxOffsets.y !== 0 ||
+ p.copyBoxOffsets.z !== 0 ||
+ p.copyBoxOffsets.height !== 0 ||
+ p.copyBoxOffsets.depthOrArrayLayers !== 0)
+ )
+ .combine('srcCopyLevel', [0, 1, 3])
+ .combine('dstCopyLevel', [0, 1, 3])
+ .unless(p => p.dimension === '1d' && (p.srcCopyLevel !== 0 || p.dstCopyLevel !== 0))
+ )
+ .fn(async t => {
+ const { dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params;
+
+ const textureSize = { width: 16, height: 8, depthOrArrayLayers: 3 };
+ let mipLevelCount = 4;
+ if (dimension === '1d') {
+ mipLevelCount = 1;
+ textureSize.height = 1;
+ textureSize.depthOrArrayLayers = 1;
+ }
+ const kFormat = 'rgba8unorm';
+
+ const srcTexture = t.device.createTexture({
+ size: textureSize,
+ format: kFormat,
+ dimension,
+ mipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ const dstTexture = t.device.createTexture({
+ size: textureSize,
+ format: kFormat,
+ dimension,
+ mipLevelCount,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+
+ const srcSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ textureSize,
+ kFormat,
+ srcCopyLevel
+ );
+ const dstSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ textureSize,
+ kFormat,
+ dstCopyLevel
+ );
+
+ const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z };
+
+ const copyWidth = Math.max(
+ Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x,
+ 0
+ );
+ const copyHeight = Math.max(
+ Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y,
+ 0
+ );
+ const copyDepth =
+ textureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z;
+
+ {
+ let isSuccess =
+ copyWidth <= srcSizeAtLevel.width &&
+ copyHeight <= srcSizeAtLevel.height &&
+ copyOrigin.x + copyWidth <= dstSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= dstSizeAtLevel.height;
+
+ if (dimension === '3d') {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= srcSizeAtLevel.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= dstSizeAtLevel.depthOrArrayLayers;
+ } else {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= textureSize.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers;
+ }
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+
+ {
+ let isSuccess =
+ copyOrigin.x + copyWidth <= srcSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= srcSizeAtLevel.height &&
+ copyWidth <= dstSizeAtLevel.width &&
+ copyHeight <= dstSizeAtLevel.height;
+
+ if (dimension === '3d') {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= dstSizeAtLevel.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= srcSizeAtLevel.depthOrArrayLayers;
+ } else {
+ isSuccess =
+ isSuccess &&
+ copyDepth <= textureSize.depthOrArrayLayers &&
+ copyOrigin.z + copyDepth <= textureSize.depthOrArrayLayers;
+ }
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+ });
+
+g.test('copy_within_same_texture')
+ .desc(
+ `
+Test that it is an error to use copyTextureToTexture from one subresource to itself.
+- for various starting source/destination array layers.
+- for various copy sizes in number of array layers
+
+TODO: Extend to check the copy is allowed between different mip levels.
+TODO: Extend to 1D and 3D textures.`
+ )
+ .paramsSubcasesOnly(u =>
+ u //
+ .combine('srcCopyOriginZ', [0, 2, 4])
+ .combine('dstCopyOriginZ', [0, 2, 4])
+ .combine('copyExtentDepth', [1, 2, 3])
+ )
+ .fn(async t => {
+ const { srcCopyOriginZ, dstCopyOriginZ, copyExtentDepth } = t.params;
+
+ const kArrayLayerCount = 7;
+
+ const testTexture = t.device.createTexture({
+ size: { width: 16, height: 16, depthOrArrayLayers: kArrayLayerCount },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ const isSuccess =
+ Math.min(srcCopyOriginZ, dstCopyOriginZ) + copyExtentDepth <=
+ Math.max(srcCopyOriginZ, dstCopyOriginZ);
+ t.TestCopyTextureToTexture(
+ { texture: testTexture, origin: { x: 0, y: 0, z: srcCopyOriginZ } },
+ { texture: testTexture, origin: { x: 0, y: 0, z: dstCopyOriginZ } },
+ { width: 16, height: 16, depthOrArrayLayers: copyExtentDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('copy_aspects')
+ .desc(
+ `
+Test the validations on the member 'aspect' of GPUImageCopyTexture in CopyTextureToTexture().
+- for all the color and depth-stencil formats: the texture copy aspects must be both 'all'.
+- for all the depth-only formats: the texture copy aspects must be either 'all' or 'depth-only'.
+- for all the stencil-only formats: the texture copy aspects must be either 'all' or 'stencil-only'.
+`
+ )
+ .params(u =>
+ u
+ .combine('format', ['rgba8unorm', ...kDepthStencilFormats] as const)
+ .beginSubcases()
+ .combine('sourceAspect', ['all', 'depth-only', 'stencil-only'] as const)
+ .combine('destinationAspect', ['all', 'depth-only', 'stencil-only'] as const)
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+ })
+ .fn(async t => {
+ const { format, sourceAspect, destinationAspect } = t.params;
+
+ const kTextureSize = { width: 16, height: 8, depthOrArrayLayers: 1 };
+
+ const srcTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ const dstTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+
+ // MAINTENANCE_TODO: get the valid aspects from capability_info.ts.
+ const kValidAspectsForFormat = {
+ rgba8unorm: ['all'],
+
+ // kUnsizedDepthStencilFormats
+ depth24plus: ['all', 'depth-only'],
+ 'depth24plus-stencil8': ['all'],
+ 'depth32float-stencil8': ['all'],
+
+ // kSizedDepthStencilFormats
+ depth32float: ['all', 'depth-only'],
+ stencil8: ['all', 'stencil-only'],
+ depth16unorm: ['all', 'depth-only'],
+ };
+
+ const isSourceAspectValid = kValidAspectsForFormat[format].includes(sourceAspect);
+ const isDestinationAspectValid = kValidAspectsForFormat[format].includes(destinationAspect);
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, aspect: sourceAspect },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, aspect: destinationAspect },
+ kTextureSize,
+ isSourceAspectValid && isDestinationAspectValid ? 'Success' : 'FinishError'
+ );
+ });
+
+g.test('copy_ranges_with_compressed_texture_formats')
+ .desc(
+ `
+Test that copyTextureToTexture copy boxes must be in range of the subresource and aligned to the block size
+- for various dimensions
+- for various offsets to a full copy for the copy origin/size
+- for various copy mip levels
+
+TODO: Express the offsets in "block size" so as to be able to test non-4x4 compressed formats
+`
+ )
+ .params(u =>
+ u
+ .combine('format', kCompressedTextureFormats)
+ .combine('dimension', kTextureDimensions)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combine('copyBoxOffsets', [
+ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 1, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 4, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: -1, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: -4, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 1, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 4, z: 0, width: 0, height: 0, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: 0, height: -1, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: 0, height: -4, depthOrArrayLayers: -2 },
+ { x: 0, y: 0, z: 0, width: 0, height: 0, depthOrArrayLayers: 0 },
+ { x: 0, y: 0, z: 1, width: 0, height: 0, depthOrArrayLayers: -1 },
+ ])
+ .combine('srcCopyLevel', [0, 1, 2])
+ .combine('dstCopyLevel', [0, 1, 2])
+ )
+ .beforeAllSubcases(t => {
+ const { format } = t.params;
+ t.selectDeviceOrSkipTestCase(kTextureFormatInfo[format].feature);
+ })
+ .fn(async t => {
+ const { format, dimension, copyBoxOffsets, srcCopyLevel, dstCopyLevel } = t.params;
+ const { blockWidth, blockHeight } = kTextureFormatInfo[format];
+
+ const kTextureSize = {
+ width: 15 * blockWidth,
+ height: 12 * blockHeight,
+ depthOrArrayLayers: 3,
+ };
+ const kMipLevelCount = 4;
+
+ const srcTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ dimension,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_SRC,
+ });
+ const dstTexture = t.device.createTexture({
+ size: kTextureSize,
+ format,
+ dimension,
+ mipLevelCount: kMipLevelCount,
+ usage: GPUTextureUsage.COPY_DST,
+ });
+
+ const srcSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ kTextureSize,
+ format,
+ srcCopyLevel
+ );
+ const dstSizeAtLevel = t.GetPhysicalSubresourceSize(
+ dimension,
+ kTextureSize,
+ format,
+ dstCopyLevel
+ );
+
+ const copyOrigin = { x: copyBoxOffsets.x, y: copyBoxOffsets.y, z: copyBoxOffsets.z };
+
+ const copyWidth = Math.max(
+ Math.min(srcSizeAtLevel.width, dstSizeAtLevel.width) + copyBoxOffsets.width - copyOrigin.x,
+ 0
+ );
+ const copyHeight = Math.max(
+ Math.min(srcSizeAtLevel.height, dstSizeAtLevel.height) + copyBoxOffsets.height - copyOrigin.y,
+ 0
+ );
+ const copyDepth =
+ kTextureSize.depthOrArrayLayers + copyBoxOffsets.depthOrArrayLayers - copyOrigin.z;
+
+ const texelBlockWidth = kTextureFormatInfo[format].blockWidth;
+ const texelBlockHeight = kTextureFormatInfo[format].blockHeight;
+
+ const isSuccessForCompressedFormats =
+ copyOrigin.x % texelBlockWidth === 0 &&
+ copyOrigin.y % texelBlockHeight === 0 &&
+ copyWidth % texelBlockWidth === 0 &&
+ copyHeight % texelBlockHeight === 0;
+
+ {
+ const isSuccess =
+ isSuccessForCompressedFormats &&
+ copyWidth <= srcSizeAtLevel.width &&
+ copyHeight <= srcSizeAtLevel.height &&
+ copyOrigin.x + copyWidth <= dstSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= dstSizeAtLevel.height &&
+ copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: copyOrigin, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+
+ {
+ const isSuccess =
+ isSuccessForCompressedFormats &&
+ copyOrigin.x + copyWidth <= srcSizeAtLevel.width &&
+ copyOrigin.y + copyHeight <= srcSizeAtLevel.height &&
+ copyWidth <= dstSizeAtLevel.width &&
+ copyHeight <= dstSizeAtLevel.height &&
+ copyOrigin.z + copyDepth <= kTextureSize.depthOrArrayLayers;
+
+ t.TestCopyTextureToTexture(
+ { texture: srcTexture, origin: copyOrigin, mipLevel: srcCopyLevel },
+ { texture: dstTexture, origin: { x: 0, y: 0, z: 0 }, mipLevel: dstCopyLevel },
+ { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth },
+ isSuccess ? 'Success' : 'FinishError'
+ );
+ }
+ });