summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts538
1 files changed, 538 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
new file mode 100644
index 0000000000..7d40b2d490
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/texture_related.spec.ts
@@ -0,0 +1,538 @@
+export const description = `Texture related validation tests for B2T copy and T2B copy and writeTexture.`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import {
+ kColorTextureFormats,
+ kSizedTextureFormats,
+ kTextureDimensions,
+ kTextureFormatInfo,
+ kTextureUsages,
+ textureDimensionAndFormatCompatible,
+} from '../../../capability_info.js';
+import { GPUConst } from '../../../constants.js';
+import { kResourceStates } from '../../../gpu_test.js';
+import { align } from '../../../util/math.js';
+import { virtualMipSize } from '../../../util/texture/base.js';
+import { kImageCopyTypes } from '../../../util/texture/layout.js';
+
+import {
+ ImageCopyTest,
+ texelBlockAlignmentTestExpanderForValueToCoordinate,
+ formatCopyableWithMethod,
+ getACopyableAspectWithMethod,
+} from './image_copy.js';
+
+export const g = makeTestGroup(ImageCopyTest);
+
+g.test('valid')
+ .desc(
+ `
+Test that the texture must be valid and not destroyed.
+- for all copy methods
+- for all texture states
+- for various dimensions
+`
+ )
+ .params(u =>
+ u //
+ .combine('method', kImageCopyTypes)
+ .combine('textureState', kResourceStates)
+ .combineWithParams([
+ { dimension: '1d', size: [4, 1, 1] },
+ { dimension: '2d', size: [4, 4, 1] },
+ { dimension: '2d', size: [4, 4, 3] },
+ { dimension: '3d', size: [4, 4, 3] },
+ ] as const)
+ )
+ .fn(async t => {
+ const { method, textureState, size, dimension } = t.params;
+
+ const texture = t.createTextureWithState(textureState, {
+ size,
+ dimension,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ const success = textureState === 'valid';
+ const submit = textureState !== 'invalid';
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success, submit }
+ );
+ });
+
+g.test('texture,device_mismatch')
+ .desc('Tests the image copies cannot be called with a texture created from another device')
+ .paramsSubcasesOnly(u =>
+ u.combine('method', kImageCopyTypes).combine('mismatched', [true, false])
+ )
+ .beforeAllSubcases(t => {
+ t.selectMismatchedDeviceOrSkipTestCase(undefined);
+ })
+ .fn(async t => {
+ const { method, mismatched } = t.params;
+ const sourceDevice = mismatched ? t.mismatchedDevice : t.device;
+
+ const texture = sourceDevice.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success: !mismatched }
+ );
+ });
+
+g.test('usage')
+ .desc(
+ `
+The texture must have the appropriate COPY_SRC/COPY_DST usage.
+- for various copy methods
+- for various dimensions
+- for various usages
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combineWithParams([
+ { dimension: '1d', size: [4, 1, 1] },
+ { dimension: '2d', size: [4, 4, 1] },
+ { dimension: '2d', size: [4, 4, 3] },
+ { dimension: '3d', size: [4, 4, 3] },
+ ] as const)
+ .beginSubcases()
+ // If usage0 and usage1 are the same, the usage being test is a single usage. Otherwise, it's
+ // a combined usage.
+ .combine('usage0', kTextureUsages)
+ .combine('usage1', kTextureUsages)
+ // RENDER_ATTACHMENT is not valid with 1d and 3d textures.
+ .unless(
+ ({ usage0, usage1, dimension }) =>
+ ((usage0 | usage1) & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 &&
+ (dimension === '1d' || dimension === '3d')
+ )
+ )
+ .fn(async t => {
+ const { usage0, usage1, method, size, dimension } = t.params;
+
+ const usage = usage0 | usage1;
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format: 'rgba8unorm',
+ usage,
+ });
+
+ const success =
+ method === 'CopyT2B'
+ ? (usage & GPUTextureUsage.COPY_SRC) !== 0
+ : (usage & GPUTextureUsage.COPY_DST) !== 0;
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success }
+ );
+ });
+
+g.test('sample_count')
+ .desc(
+ `
+Test that multisampled textures cannot be copied.
+- for various copy methods
+- multisampled or not
+
+Note: we don't test 1D, 2D array and 3D textures because multisample is not supported them.
+`
+ )
+ .params(u =>
+ u //
+ .combine('method', kImageCopyTypes)
+ .beginSubcases()
+ .combine('sampleCount', [1, 4])
+ )
+ .fn(async t => {
+ const { sampleCount, method } = t.params;
+
+ const texture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ sampleCount,
+ format: 'rgba8unorm',
+ usage:
+ GPUTextureUsage.COPY_SRC |
+ GPUTextureUsage.COPY_DST |
+ GPUTextureUsage.TEXTURE_BINDING |
+ GPUTextureUsage.RENDER_ATTACHMENT,
+ });
+
+ const success = sampleCount === 1;
+
+ t.testRun(
+ { texture },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success }
+ );
+ });
+
+g.test('mip_level')
+ .desc(
+ `
+Test that the mipLevel of the copy must be in range of the texture.
+- for various copy methods
+- for various dimensions
+- for several mipLevelCounts
+- for several target/source mipLevels`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combineWithParams([
+ { dimension: '1d', size: [32, 1, 1] },
+ { dimension: '2d', size: [32, 32, 1] },
+ { dimension: '2d', size: [32, 32, 3] },
+ { dimension: '3d', size: [32, 32, 3] },
+ ] as const)
+ .beginSubcases()
+ .combine('mipLevelCount', [1, 3, 5])
+ .unless(p => p.dimension === '1d' && p.mipLevelCount !== 1)
+ .combine('mipLevel', [0, 1, 3, 4])
+ )
+ .fn(async t => {
+ const { mipLevelCount, mipLevel, method, size, dimension } = t.params;
+
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ mipLevelCount,
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ const success = mipLevel < mipLevelCount;
+
+ t.testRun(
+ { texture, mipLevel },
+ { bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize: 1, method, success }
+ );
+ });
+
+g.test('format')
+ .desc(
+ `
+Test the copy must be a full subresource if the texture's format is depth/stencil format.
+- for various copy methods
+- for various dimensions
+- for all sized formats
+- for a couple target/source mipLevels
+- for some modifier (or not) for the full copy size
+`
+ )
+ .params(u =>
+ u //
+ .combine('method', kImageCopyTypes)
+ .combineWithParams([
+ { depthOrArrayLayers: 1, dimension: '1d' },
+ { depthOrArrayLayers: 1, dimension: '2d' },
+ { depthOrArrayLayers: 3, dimension: '2d' },
+ { depthOrArrayLayers: 32, dimension: '3d' },
+ ] as const)
+ .combine('format', kSizedTextureFormats)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .filter(formatCopyableWithMethod)
+ .beginSubcases()
+ .combine('mipLevel', [0, 2])
+ .unless(p => p.dimension === '1d' && p.mipLevel !== 0)
+ .combine('copyWidthModifier', [0, -1])
+ .combine('copyHeightModifier', [0, -1])
+ // If the texture has multiple depth/array slices and it is not a 3D texture, which means it is an array texture,
+ // depthModifier is not needed upon the third dimension. Because different layers are different subresources in
+ // an array texture. Whether it is a full copy or non-full copy doesn't make sense across different subresources.
+ // However, different depth slices on the same mip level are within the same subresource for a 3d texture. So we
+ // need to examine depth dimension via copyDepthModifier to determine whether it is a full copy for a 3D texture.
+ .expand('copyDepthModifier', ({ dimension: d }) => (d === '3d' ? [0, -1] : [0]))
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const {
+ method,
+ depthOrArrayLayers,
+ dimension,
+ format,
+ mipLevel,
+ copyWidthModifier,
+ copyHeightModifier,
+ copyDepthModifier,
+ } = t.params;
+
+ const info = kTextureFormatInfo[format];
+ const size = { width: 32 * info.blockWidth, height: 32 * info.blockHeight, depthOrArrayLayers };
+ if (dimension === '1d') {
+ size.height = 1;
+ }
+
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format,
+ mipLevelCount: dimension === '1d' ? 1 : 5,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ let success = true;
+ if (
+ (info.depth || info.stencil) &&
+ (copyWidthModifier !== 0 || copyHeightModifier !== 0 || copyDepthModifier !== 0)
+ ) {
+ success = false;
+ }
+
+ const levelSize = virtualMipSize(
+ dimension,
+ [size.width, size.height, size.depthOrArrayLayers],
+ mipLevel
+ );
+ const copySize = [
+ levelSize[0] + copyWidthModifier * info.blockWidth,
+ levelSize[1] + copyHeightModifier * info.blockHeight,
+ // Note that compressed format is not supported for 3D textures yet, so there is no info.blockDepth.
+ levelSize[2] + copyDepthModifier,
+ ];
+
+ t.testRun(
+ { texture, mipLevel, aspect: getACopyableAspectWithMethod({ format, method }) },
+ { bytesPerRow: 512, rowsPerImage: 32 },
+ copySize,
+ {
+ dataSize: 512 * 32 * 32,
+ method,
+ success,
+ }
+ );
+ });
+
+g.test('origin_alignment')
+ .desc(
+ `
+Test that the texture copy origin must be aligned to the format's block size.
+- for various copy methods
+- for all color formats (depth stencil formats require a full copy)
+- for X, Y and Z coordinates
+- for various values for that coordinate depending on the block size
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ // No need to test depth/stencil formats because its copy origin must be [0, 0, 0], which is already aligned with block size.
+ .combine('format', kColorTextureFormats)
+ .filter(formatCopyableWithMethod)
+ .combineWithParams([
+ { depthOrArrayLayers: 1, dimension: '1d' },
+ { depthOrArrayLayers: 1, dimension: '2d' },
+ { depthOrArrayLayers: 3, dimension: '2d' },
+ { depthOrArrayLayers: 3, dimension: '3d' },
+ ] as const)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combine('coordinateToTest', ['x', 'y', 'z'] as const)
+ .unless(p => p.dimension === '1d' && p.coordinateToTest !== 'x')
+ .expand('valueToCoordinate', texelBlockAlignmentTestExpanderForValueToCoordinate)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const {
+ valueToCoordinate,
+ coordinateToTest,
+ format,
+ method,
+ depthOrArrayLayers,
+ dimension,
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+ const size = { width: 0, height: 0, depthOrArrayLayers };
+ const origin = { x: 0, y: 0, z: 0 };
+ let success = true;
+
+ origin[coordinateToTest] = valueToCoordinate;
+ switch (coordinateToTest) {
+ case 'x': {
+ success = origin.x % info.blockWidth === 0;
+ break;
+ }
+ case 'y': {
+ success = origin.y % info.blockHeight === 0;
+ break;
+ }
+ }
+
+ const texture = t.createAlignedTexture(format, size, origin, dimension);
+
+ t.testRun({ texture, origin }, { bytesPerRow: 0, rowsPerImage: 0 }, size, {
+ dataSize: 1,
+ method,
+ success,
+ });
+ });
+
+g.test('size_alignment')
+ .desc(
+ `
+Test that the copy size must be aligned to the texture's format's block size.
+- for various copy methods
+- for all formats (depth-stencil formats require a full copy)
+- for all texture dimensions
+- for the size's parameters to test (width / height / depth)
+- for various values for that copy size parameters, depending on the block size
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ // No need to test depth/stencil formats because its copy size must be subresource's size, which is already aligned with block size.
+ .combine('format', kColorTextureFormats)
+ .filter(formatCopyableWithMethod)
+ .combine('dimension', kTextureDimensions)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combine('coordinateToTest', ['width', 'height', 'depthOrArrayLayers'] as const)
+ .unless(p => p.dimension === '1d' && p.coordinateToTest !== 'width')
+ .expand('valueToCoordinate', texelBlockAlignmentTestExpanderForValueToCoordinate)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const { valueToCoordinate, coordinateToTest, dimension, format, method } = t.params;
+ const info = kTextureFormatInfo[format];
+ const size = { width: 0, height: 0, depthOrArrayLayers: 0 };
+ const origin = { x: 0, y: 0, z: 0 };
+ let success = true;
+
+ size[coordinateToTest] = valueToCoordinate;
+ switch (coordinateToTest) {
+ case 'width': {
+ success = size.width % info.blockWidth === 0;
+ break;
+ }
+ case 'height': {
+ success = size.height % info.blockHeight === 0;
+ break;
+ }
+ }
+
+ const texture = t.createAlignedTexture(format, size, origin, dimension);
+
+ const bytesPerRow = align(
+ Math.max(1, Math.ceil(size.width / info.blockWidth)) * info.bytesPerBlock,
+ 256
+ );
+ const rowsPerImage = Math.ceil(size.height / info.blockHeight);
+ t.testRun({ texture, origin }, { bytesPerRow, rowsPerImage }, size, {
+ dataSize: 1,
+ method,
+ success,
+ });
+ });
+
+g.test('copy_rectangle')
+ .desc(
+ `
+Test that the max corner of the copy rectangle (origin+copySize) must be inside the texture.
+- for various copy methods
+- for all dimensions
+- for the X, Y and Z dimensions
+- for various origin and copy size values (and texture sizes)
+- for various mip levels
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combine('dimension', kTextureDimensions)
+ .beginSubcases()
+ .combine('originValue', [7, 8])
+ .combine('copySizeValue', [7, 8])
+ .combine('textureSizeValue', [14, 15])
+ .combine('mipLevel', [0, 2])
+ .combine('coordinateToTest', [0, 1, 2] as const)
+ .unless(p => p.dimension === '1d' && (p.coordinateToTest !== 0 || p.mipLevel !== 0))
+ )
+ .fn(async t => {
+ const {
+ originValue,
+ copySizeValue,
+ textureSizeValue,
+ mipLevel,
+ coordinateToTest,
+ method,
+ dimension,
+ } = t.params;
+ const format = 'rgba8unorm';
+ const info = kTextureFormatInfo[format];
+
+ const origin = [0, 0, 0];
+ const copySize = [0, 0, 0];
+ const textureSize = { width: 16 << mipLevel, height: 16 << mipLevel, depthOrArrayLayers: 16 };
+ if (dimension === '1d') {
+ textureSize.height = 1;
+ textureSize.depthOrArrayLayers = 1;
+ }
+ const success = originValue + copySizeValue <= textureSizeValue;
+
+ origin[coordinateToTest] = originValue;
+ copySize[coordinateToTest] = copySizeValue;
+ switch (coordinateToTest) {
+ case 0: {
+ textureSize.width = textureSizeValue << mipLevel;
+ break;
+ }
+ case 1: {
+ textureSize.height = textureSizeValue << mipLevel;
+ break;
+ }
+ case 2: {
+ textureSize.depthOrArrayLayers =
+ dimension === '3d' ? textureSizeValue << mipLevel : textureSizeValue;
+ break;
+ }
+ }
+
+ const texture = t.device.createTexture({
+ size: textureSize,
+ dimension,
+ mipLevelCount: dimension === '1d' ? 1 : 3,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ assert(copySize[0] % info.blockWidth === 0);
+ const bytesPerRow = align(copySize[0] / info.blockWidth, 256);
+ assert(copySize[1] % info.blockHeight === 0);
+ const rowsPerImage = copySize[1] / info.blockHeight;
+ t.testRun({ texture, origin, mipLevel }, { bytesPerRow, rowsPerImage }, copySize, {
+ dataSize: 1,
+ method,
+ success,
+ });
+ });