diff options
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.ts | 876 |
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' + ); + } + }); |