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