summaryrefslogtreecommitdiffstats
path: root/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/layout_related.spec.ts
diff options
context:
space:
mode:
Diffstat (limited to 'dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/layout_related.spec.ts')
-rw-r--r--dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/layout_related.spec.ts479
1 files changed, 479 insertions, 0 deletions
diff --git a/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/layout_related.spec.ts b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/layout_related.spec.ts
new file mode 100644
index 0000000000..9c632c729c
--- /dev/null
+++ b/dom/webgpu/tests/cts/checkout/src/webgpu/api/validation/image_copy/layout_related.spec.ts
@@ -0,0 +1,479 @@
+export const description = `Validation tests for the linear data layout of linear data <-> texture copies
+
+TODO check if the tests need to be updated to support aspects of depth-stencil textures`;
+
+import { makeTestGroup } from '../../../../common/framework/test_group.js';
+import { assert } from '../../../../common/util/util.js';
+import {
+ kTextureFormatInfo,
+ kSizedTextureFormats,
+ textureDimensionAndFormatCompatible,
+ kTextureDimensions,
+} from '../../../capability_info.js';
+import { align } from '../../../util/math.js';
+import {
+ bytesInACompleteRow,
+ dataBytesForCopyOrOverestimate,
+ dataBytesForCopyOrFail,
+ kImageCopyTypes,
+} from '../../../util/texture/layout.js';
+
+import {
+ ImageCopyTest,
+ texelBlockAlignmentTestExpanderForOffset,
+ texelBlockAlignmentTestExpanderForRowsPerImage,
+ formatCopyableWithMethod,
+} from './image_copy.js';
+
+export const g = makeTestGroup(ImageCopyTest);
+
+g.test('bound_on_rows_per_image')
+ .desc(
+ `
+Test that rowsPerImage must be at least the copy height (if defined).
+- for various copy methods
+- for all texture dimensions
+- for various values of rowsPerImage including undefined
+- for various copy heights
+- for various copy depths
+`
+ )
+ .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()
+ .combine('rowsPerImage', [undefined, 0, 1, 2, 1024])
+ .combine('copyHeightInBlocks', [0, 1, 2])
+ .combine('copyDepth', [1, 3])
+ .unless(p => p.dimension === '1d' && p.copyHeightInBlocks !== 1)
+ .unless(p => p.copyDepth > p.size[2])
+ )
+ .fn(async t => {
+ const { rowsPerImage, copyHeightInBlocks, copyDepth, dimension, size, method } = t.params;
+
+ const format = 'rgba8unorm';
+ const copyHeight = copyHeightInBlocks * kTextureFormatInfo[format].blockHeight;
+
+ const texture = t.device.createTexture({
+ size,
+ dimension,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ const layout = { bytesPerRow: 1024, rowsPerImage };
+ const copySize = { width: 0, height: copyHeight, depthOrArrayLayers: copyDepth };
+ const { minDataSizeOrOverestimate, copyValid } = dataBytesForCopyOrOverestimate({
+ layout,
+ format,
+ copySize,
+ method,
+ });
+
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSizeOrOverestimate,
+ method,
+ success: copyValid,
+ });
+ });
+
+g.test('copy_end_overflows_u64')
+ .desc(
+ `
+Test an error is produced when offset+requiredBytesInCopy overflows GPUSize64.
+- for various copy methods
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .beginSubcases()
+ .combineWithParams([
+ { bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depthOrArrayLayers: 1, _success: true }, // success case
+ { bytesPerRow: 2 ** 31, rowsPerImage: 2 ** 31, depthOrArrayLayers: 16, _success: false }, // bytesPerRow * rowsPerImage * (depthOrArrayLayers - 1) overflows.
+ ])
+ )
+ .fn(async t => {
+ const { method, bytesPerRow, rowsPerImage, depthOrArrayLayers, _success } = t.params;
+
+ const texture = t.device.createTexture({
+ size: [1, 1, depthOrArrayLayers],
+ format: 'rgba8unorm',
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ t.testRun(
+ { texture },
+ { bytesPerRow, rowsPerImage },
+ { width: 1, height: 1, depthOrArrayLayers },
+ {
+ dataSize: 10000,
+ method,
+ success: _success,
+ }
+ );
+ });
+
+g.test('required_bytes_in_copy')
+ .desc(
+ `
+Test the computation of requiredBytesInCopy by computing the minimum data size for the copy and checking success/error at the boundary.
+- for various copy methods
+- for all formats
+- for all dimensions
+- for various extra bytesPerRow/rowsPerImage
+- for various copy sizes
+- for various offsets in the linear data
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combine('format', kSizedTextureFormats)
+ .filter(formatCopyableWithMethod)
+ .combine('dimension', kTextureDimensions)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combineWithParams([
+ { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 0 }, // no padding
+ { bytesPerRowPadding: 0, rowsPerImagePaddingInBlocks: 6 }, // rowsPerImage padding
+ { bytesPerRowPadding: 6, rowsPerImagePaddingInBlocks: 0 }, // bytesPerRow padding
+ { bytesPerRowPadding: 15, rowsPerImagePaddingInBlocks: 17 }, // both paddings
+ ])
+ .combineWithParams([
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 5, _offsetMultiplier: 0 }, // standard copy
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 3, _offsetMultiplier: 11 }, // standard copy, offset > 0
+ { copyWidthInBlocks: 256, copyHeightInBlocks: 3, copyDepth: 2, _offsetMultiplier: 0 }, // copyWidth is 256-aligned
+ { copyWidthInBlocks: 0, copyHeightInBlocks: 4, copyDepth: 5, _offsetMultiplier: 0 }, // empty copy because of width
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 0, copyDepth: 5, _offsetMultiplier: 0 }, // empty copy because of height
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 4, copyDepth: 0, _offsetMultiplier: 13 }, // empty copy because of depth, offset > 0
+ { copyWidthInBlocks: 1, copyHeightInBlocks: 4, copyDepth: 5, _offsetMultiplier: 0 }, // copyWidth = 1
+ { copyWidthInBlocks: 3, copyHeightInBlocks: 1, copyDepth: 5, _offsetMultiplier: 15 }, // copyHeight = 1, offset > 0
+ { copyWidthInBlocks: 5, copyHeightInBlocks: 4, copyDepth: 1, _offsetMultiplier: 0 }, // copyDepth = 1
+ { copyWidthInBlocks: 7, copyHeightInBlocks: 1, copyDepth: 1, _offsetMultiplier: 0 }, // copyHeight = 1 and copyDepth = 1
+ ])
+ // The test texture size will be rounded up from the copy size to the next valid texture size.
+ // If the format is a depth/stencil format, its copy size must equal to subresource's size.
+ // So filter out depth/stencil cases where the rounded-up texture size would be different from the copy size.
+ .filter(({ format, copyWidthInBlocks, copyHeightInBlocks, copyDepth }) => {
+ const info = kTextureFormatInfo[format];
+ return (
+ (!info.depth && !info.stencil) ||
+ (copyWidthInBlocks > 0 && copyHeightInBlocks > 0 && copyDepth > 0)
+ );
+ })
+ .unless(p => p.dimension === '1d' && (p.copyHeightInBlocks > 1 || p.copyDepth > 1))
+ .expand('offset', p => {
+ const info = kTextureFormatInfo[p.format];
+ if (info.depth || info.stencil) {
+ return [p._offsetMultiplier * 4];
+ }
+ return [p._offsetMultiplier * info.bytesPerBlock];
+ })
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const {
+ offset,
+ bytesPerRowPadding,
+ rowsPerImagePaddingInBlocks,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ format,
+ dimension,
+ method,
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ // In the CopyB2T and CopyT2B cases we need to have bytesPerRow 256-aligned,
+ // to make this happen we align the bytesInACompleteRow value and multiply
+ // bytesPerRowPadding by 256.
+ const bytesPerRowAlignment = method === 'WriteTexture' ? 1 : 256;
+ const copyWidth = copyWidthInBlocks * info.blockWidth;
+ const copyHeight = copyHeightInBlocks * info.blockHeight;
+ const rowsPerImage = copyHeight + rowsPerImagePaddingInBlocks * info.blockHeight;
+ const bytesPerRow =
+ align(bytesInACompleteRow(copyWidth, format), bytesPerRowAlignment) +
+ bytesPerRowPadding * bytesPerRowAlignment;
+ const copySize = { width: copyWidth, height: copyHeight, depthOrArrayLayers: copyDepth };
+
+ const layout = { offset, bytesPerRow, rowsPerImage };
+ const minDataSize = dataBytesForCopyOrFail({ layout, format, copySize, method });
+
+ const texture = t.createAlignedTexture(format, copySize, undefined, dimension);
+
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSize,
+ method,
+ success: true,
+ });
+
+ if (minDataSize > 0) {
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSize - 1,
+ method,
+ success: false,
+ });
+ }
+ });
+
+g.test('rows_per_image_alignment')
+ .desc(
+ `
+Test that rowsPerImage has no alignment constraints.
+- for various copy methods
+- for all sized format
+- for all dimensions
+- for various rowsPerImage
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combine('format', kSizedTextureFormats)
+ .filter(formatCopyableWithMethod)
+ .combine('dimension', kTextureDimensions)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .expand('rowsPerImage', texelBlockAlignmentTestExpanderForRowsPerImage)
+ // Copy height is info.blockHeight, so rowsPerImage must be equal or greater than it.
+ .filter(({ rowsPerImage, format }) => rowsPerImage >= kTextureFormatInfo[format].blockHeight)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const { rowsPerImage, format, method } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const size = { width: info.blockWidth, height: info.blockHeight, depthOrArrayLayers: 1 };
+ const texture = t.device.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ t.testRun({ texture }, { bytesPerRow: 256, rowsPerImage }, size, {
+ dataSize: info.bytesPerBlock,
+ method,
+ success: true,
+ });
+ });
+
+g.test('offset_alignment')
+ .desc(
+ `
+Test the alignment requirement on the linear data offset (block size, or 4 for depth-stencil).
+- for various copy methods
+- for all sized formats
+- for all dimensions
+- for various linear data offsets
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combine('format', kSizedTextureFormats)
+ .filter(formatCopyableWithMethod)
+ .combine('dimension', kTextureDimensions)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .expand('offset', texelBlockAlignmentTestExpanderForOffset)
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const { format, offset, method } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ const size = { width: info.blockWidth, height: info.blockHeight, depthOrArrayLayers: 1 };
+ const texture = t.device.createTexture({
+ size,
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ let success = false;
+ if (method === 'WriteTexture') success = true;
+ if (info.depth || info.stencil) {
+ if (offset % 4 === 0) success = true;
+ } else {
+ if (offset % info.bytesPerBlock === 0) success = true;
+ }
+
+ t.testRun({ texture }, { offset, bytesPerRow: 256 }, size, {
+ dataSize: offset + info.bytesPerBlock,
+ method,
+ success,
+ });
+ });
+
+g.test('bound_on_bytes_per_row')
+ .desc(
+ `
+Test that bytesPerRow, if specified must be big enough for a full copy row.
+- for various copy methods
+- for all sized formats
+- for all dimension
+- for various copy heights
+- for various copy depths
+- for various combinations of bytesPerRow and copy width.
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .combine('format', kSizedTextureFormats)
+ .filter(formatCopyableWithMethod)
+ .combine('dimension', kTextureDimensions)
+ .filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
+ .beginSubcases()
+ .combine('copyHeightInBlocks', [1, 2])
+ .combine('copyDepth', [1, 2])
+ .unless(p => p.dimension === '1d' && (p.copyHeightInBlocks > 1 || p.copyDepth > 1))
+ .expandWithParams(p => {
+ const info = kTextureFormatInfo[p.format];
+ // We currently have a built-in assumption that for all formats, 128 % bytesPerBlock === 0.
+ // This assumption ensures that all division below results in integers.
+ assert(128 % info.bytesPerBlock === 0);
+ return [
+ // Copying exact fit with aligned bytesPerRow should work.
+ {
+ bytesPerRow: 256,
+ widthInBlocks: 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 256 / info.bytesPerBlock,
+ _success: true,
+ },
+ // Copying into smaller texture when padding in bytesPerRow is enough should work unless
+ // it is a depth/stencil typed format.
+ {
+ bytesPerRow: 256,
+ widthInBlocks: 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 256 / info.bytesPerBlock - 1,
+ _success: !(info.stencil || info.depth),
+ },
+ // Unaligned bytesPerRow should not work unless the method is 'WriteTexture'.
+ {
+ bytesPerRow: 128,
+ widthInBlocks: 128 / info.bytesPerBlock,
+ copyWidthInBlocks: 128 / info.bytesPerBlock,
+ _success: p.method === 'WriteTexture',
+ },
+ {
+ bytesPerRow: 384,
+ widthInBlocks: 384 / info.bytesPerBlock,
+ copyWidthInBlocks: 384 / info.bytesPerBlock,
+ _success: p.method === 'WriteTexture',
+ },
+ // When bytesPerRow is smaller than bytesInLastRow copying should fail.
+ {
+ bytesPerRow: 256,
+ widthInBlocks: (2 * 256) / info.bytesPerBlock,
+ copyWidthInBlocks: (2 * 256) / info.bytesPerBlock,
+ _success: false,
+ },
+ // When copyHeightInBlocks > 1, bytesPerRow must be specified.
+ {
+ bytesPerRow: undefined,
+ widthInBlocks: 256 / info.bytesPerBlock,
+ copyWidthInBlocks: 256 / info.bytesPerBlock,
+ _success: !(p.copyHeightInBlocks > 1 || p.copyDepth > 1),
+ },
+ ];
+ })
+ )
+ .beforeAllSubcases(t => {
+ const info = kTextureFormatInfo[t.params.format];
+ t.selectDeviceOrSkipTestCase(info.feature);
+ })
+ .fn(async t => {
+ const {
+ method,
+ format,
+ bytesPerRow,
+ widthInBlocks,
+ copyWidthInBlocks,
+ copyHeightInBlocks,
+ copyDepth,
+ _success,
+ } = t.params;
+ const info = kTextureFormatInfo[format];
+
+ // We create an aligned texture using the widthInBlocks which may be different from the
+ // copyWidthInBlocks. This allows us to test scenarios where the two may be different.
+ const texture = t.createAlignedTexture(format, {
+ width: widthInBlocks * info.blockWidth,
+ height: copyHeightInBlocks * info.blockHeight,
+ depthOrArrayLayers: copyDepth,
+ });
+
+ const layout = { bytesPerRow, rowsPerImage: copyHeightInBlocks };
+ const copySize = {
+ width: copyWidthInBlocks * info.blockWidth,
+ height: copyHeightInBlocks * info.blockHeight,
+ depthOrArrayLayers: copyDepth,
+ };
+ const { minDataSizeOrOverestimate } = dataBytesForCopyOrOverestimate({
+ layout,
+ format,
+ copySize,
+ method,
+ });
+
+ t.testRun({ texture }, layout, copySize, {
+ dataSize: minDataSizeOrOverestimate,
+ method,
+ success: _success,
+ });
+ });
+
+g.test('bound_on_offset')
+ .desc(
+ `
+Test that the offset cannot be larger than the linear data size (even for an empty copy).
+- for various offsets and data sizes
+`
+ )
+ .params(u =>
+ u
+ .combine('method', kImageCopyTypes)
+ .beginSubcases()
+ .combine('offsetInBlocks', [0, 1, 2])
+ .combine('dataSizeInBlocks', [0, 1, 2])
+ )
+ .fn(async t => {
+ const { offsetInBlocks, dataSizeInBlocks, method } = t.params;
+
+ const format = 'rgba8unorm';
+ const info = kTextureFormatInfo[format];
+ const offset = offsetInBlocks * info.bytesPerBlock;
+ const dataSize = dataSizeInBlocks * info.bytesPerBlock;
+
+ const texture = t.device.createTexture({
+ size: { width: 4, height: 4, depthOrArrayLayers: 1 },
+ format,
+ usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST,
+ });
+
+ const success = offset <= dataSize;
+
+ t.testRun(
+ { texture },
+ { offset, bytesPerRow: 0 },
+ { width: 0, height: 0, depthOrArrayLayers: 0 },
+ { dataSize, method, success }
+ );
+ });