Skip to content

Commit

Permalink
Generalize validation,image_copy,buffer_related over aspects
Browse files Browse the repository at this point in the history
- Test a few depth/stencil formats that weren't in kSizedTextureFormats
  (depth24plus-stencil8 stencil, depth32float-stencil8 depth+stencil)
- Parameterize over aspect (depth-only/stencil-only/all)
- Reduce some overparameterization to make up for it (net ~3x faster)

Removes a use of the deprecated .bytesPerBlock.
  • Loading branch information
kainino0x committed Mar 12, 2024
1 parent c5a2ecf commit ca16d20
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 30 deletions.
65 changes: 41 additions & 24 deletions src/webgpu/api/validation/image_copy/buffer_related.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,14 @@ import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { kTextureDimensions } from '../../../capability_info.js';
import { GPUConst } from '../../../constants.js';
import {
kSizedTextureFormats,
aspectParamsForTextureFormat,
kTextureFormatInfo,
textureDimensionAndFormatCompatible,
} from '../../../format_info.js';
import { kResourceStates } from '../../../gpu_test.js';
import { kImageCopyTypes } from '../../../util/texture/layout.js';

import { ImageCopyTest, formatCopyableWithMethod } from './image_copy.js';
import { ImageCopyTest, aspectCopyableWithMethod, kReducedFormatsList } from './image_copy.js';

export const g = makeTestGroup(ImageCopyTest);

Expand Down Expand Up @@ -50,7 +50,7 @@ Test that the buffer must be valid and not destroyed.

t.testBuffer(
buffer,
texture,
{ texture },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depthOrArrayLayers: 0 },
{ dataSize: 16, method, success, submit }
Expand Down Expand Up @@ -86,7 +86,7 @@ g.test('buffer,device_mismatch')
// Expect success in both finish and submit, or validation error in finish
t.testBuffer(
buffer,
texture,
{ texture },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depthOrArrayLayers: 0 },
{ dataSize: 16, method, success, submit: success }
Expand Down Expand Up @@ -135,7 +135,7 @@ TODO update such that it tests
// Expect success in both finish and submit, or validation error in finish
t.testBuffer(
buffer,
texture,
{ texture },
{ bytesPerRow: 0 },
{ width: 0, height: 0, depthOrArrayLayers: 0 },
{ dataSize: 16, method, success, submit: success }
Expand All @@ -148,38 +148,48 @@ g.test('bytes_per_row_alignment')
Test that bytesPerRow must be a multiple of 256 for CopyB2T and CopyT2B if it is required.
- for all copy methods between linear data and textures
- for all texture dimensions
- for all sized formats.
- for various bytesPerRow aligned to 256 or not
- for various number of blocks rows copied
- for all sized (format,aspect) pairs, except for a bunch of redundant "regular" color formats
- for various bytesPerRow (aligned to 256 or not)
- for various number of blocks rows copied (0 or not)
`
)
.params(u =>
u //
.combine('method', kImageCopyTypes)
.combine('format', kSizedTextureFormats)
.filter(formatCopyableWithMethod)
.combine('dimension', kTextureDimensions)
.combine('format', kReducedFormatsList)
.expandWithParams(p => aspectParamsForTextureFormat(p.format))
.filter(p => aspectCopyableWithMethod(p.format, p._aspectKey, p.method))
.filter(({ dimension, format }) => textureDimensionAndFormatCompatible(dimension, format))
.beginSubcases()
.combine('bytesPerRow', [undefined, 0, 1, 255, 256, 257, 512])
.combine('copyHeightInBlocks', [0, 1, 2, 3])
.combineWithParams([
// valid: height<=1
{ copyHeightInBlocks: 0, bytesPerRow: 1 },
{ copyHeightInBlocks: 0, bytesPerRow: undefined },
{ copyHeightInBlocks: 1, bytesPerRow: 1 },
{ copyHeightInBlocks: 1, bytesPerRow: undefined },
// valid: multiples of 256
{ copyHeightInBlocks: 2, bytesPerRow: 0 },
{ copyHeightInBlocks: 2, bytesPerRow: 512 },
{ copyHeightInBlocks: 2, bytesPerRow: 256 },
// valid only for writeTexture
{ copyHeightInBlocks: 2, bytesPerRow: 128 },
{ copyHeightInBlocks: 2, bytesPerRow: 1 }, // with 1-byte formats
{ copyHeightInBlocks: 2, bytesPerRow: 2 }, // with <=2-byte formats
{ copyHeightInBlocks: 2, bytesPerRow: 8 }, // with <=8-byte formats
{ copyHeightInBlocks: 2, bytesPerRow: 32 }, // with <=32-byte formats
])
.expand('_textureHeightInBlocks', p => [
p.copyHeightInBlocks === 0 ? 1 : p.copyHeightInBlocks,
])
.unless(p => p.dimension === '1d' && p.copyHeightInBlocks > 1)
// Depth/stencil format copies must copy the whole subresource.
.unless(p => {
const info = kTextureFormatInfo[p.format];
return (
(!!info.depth || !!info.stencil) && p.copyHeightInBlocks !== p._textureHeightInBlocks
);
})
.unless(p => p._aspectKey !== 'color' && p.copyHeightInBlocks !== p._textureHeightInBlocks)
// bytesPerRow must be specified and it must be equal or greater than the bytes size of each row if we are copying multiple rows.
// Note that we are copying one single block on each row in this test.
.filter(
({ format, bytesPerRow, copyHeightInBlocks }) =>
(bytesPerRow === undefined && copyHeightInBlocks <= 1) ||
(bytesPerRow !== undefined && bytesPerRow >= kTextureFormatInfo[format].bytesPerBlock)
({ format, _aspectKey, bytesPerRow }) =>
bytesPerRow === undefined || bytesPerRow >= kTextureFormatInfo[format][_aspectKey]!.bytes!
)
)
.beforeAllSubcases(t => {
Expand All @@ -188,8 +198,15 @@ Test that bytesPerRow must be a multiple of 256 for CopyB2T and CopyT2B if it is
t.selectDeviceOrSkipTestCase(info.feature);
})
.fn(t => {
const { method, dimension, format, bytesPerRow, copyHeightInBlocks, _textureHeightInBlocks } =
t.params;
const {
method,
dimension,
format,
aspect,
bytesPerRow,
copyHeightInBlocks,
_textureHeightInBlocks,
} = t.params;

const info = kTextureFormatInfo[format];

Expand Down Expand Up @@ -217,7 +234,7 @@ Test that bytesPerRow must be a multiple of 256 for CopyB2T and CopyT2B if it is
const copySize = [info.blockWidth, copyHeightInBlocks * info.blockHeight, 1];

// Expect success in both finish and submit, or validation error in finish
t.testBuffer(buffer, texture, { bytesPerRow }, copySize, {
t.testBuffer(buffer, { texture, aspect }, { bytesPerRow }, copySize, {
dataSize: 512 * 8 * 16,
method,
success,
Expand Down
48 changes: 42 additions & 6 deletions src/webgpu/api/validation/image_copy/image_copy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,29 @@ import {
SizedTextureFormat,
kTextureFormatInfo,
isCompressedTextureFormat,
resolvePerAspectFormat,
kCompressedTextureFormats,
kDepthStencilFormats,
} from '../../../format_info.js';
import { align } from '../../../util/math.js';
import { ImageCopyType } from '../../../util/texture/layout.js';
import { ValidationTest } from '../validation_test.js';

export const kReducedFormatsList = [
'r8unorm',
'rg8uint',
'rgba8snorm',
'r16sint',
'rg16float',
'rgba16float',
'r32float',
'rg32float',
'rgba32float',
'rgb9e5ufloat',
...kCompressedTextureFormats,
...kDepthStencilFormats,
] as const;

export class ImageCopyTest extends ValidationTest {
testRun(
textureCopyView: GPUImageCopyTexture,
Expand Down Expand Up @@ -123,7 +141,7 @@ export class ImageCopyTest extends ValidationTest {

testBuffer(
buffer: GPUBuffer,
texture: GPUTexture,
texture: GPUImageCopyTexture,
textureDataLayout: GPUImageDataLayout,
size: GPUExtent3D,
{
Expand All @@ -145,14 +163,14 @@ export class ImageCopyTest extends ValidationTest {
const data = new Uint8Array(dataSize);

this.expectValidationError(() => {
this.device.queue.writeTexture({ texture }, data, textureDataLayout, size);
this.device.queue.writeTexture(texture, data, textureDataLayout, size);
}, !success);

break;
}
case 'CopyB2T': {
const { encoder, validateFinish, validateFinishAndSubmit } = this.createEncoder('non-pass');
encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, { texture }, size);
encoder.copyBufferToTexture({ buffer, ...textureDataLayout }, texture, size);

if (submit) {
// validation error is expected to come from the submit and encoding should succeed
Expand All @@ -165,13 +183,13 @@ export class ImageCopyTest extends ValidationTest {
break;
}
case 'CopyT2B': {
if (this.isCompatibility && isCompressedTextureFormat(texture.format)) {
if (this.isCompatibility && isCompressedTextureFormat(texture.texture.format)) {
this.skip(
'copyTextureToBuffer is not supported for compressed texture formats in compatibility mode.'
);
}
const { encoder, validateFinish, validateFinishAndSubmit } = this.createEncoder('non-pass');
encoder.copyTextureToBuffer({ texture }, { buffer, ...textureDataLayout }, size);
encoder.copyTextureToBuffer(texture, { buffer, ...textureDataLayout }, size);

if (submit) {
// validation error is expected to come from the submit and encoding should succeed
Expand Down Expand Up @@ -244,7 +262,11 @@ export function texelBlockAlignmentTestExpanderForValueToCoordinate({
}
}

// This is a helper function used for filtering test parameters
/**
* This is a helper function used for filtering test parameters.
*
* @deprecated Prefer `aspectCopyableWithMethod` if possible.
*/
export function formatCopyableWithMethod({ format, method }: WithFormatAndMethod): boolean {
const info = kTextureFormatInfo[format];
if (info.depth || info.stencil) {
Expand All @@ -261,6 +283,20 @@ export function formatCopyableWithMethod({ format, method }: WithFormatAndMethod
}
}

/** True iff the (format,aspect) pair can be copied with this method. */
export function aspectCopyableWithMethod(
format: GPUTextureFormat,
aspect: 'color' | 'depth' | 'stencil',
method: ImageCopyType
) {
const info = kTextureFormatInfo[format][aspect]!;
if (method === 'CopyT2B') {
return info.copySrc;
} else {
return info.copyDst;
}
}

// This is a helper function used for filtering test parameters
export function getACopyableAspectWithMethod({
format,
Expand Down
25 changes: 25 additions & 0 deletions src/webgpu/format_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1558,6 +1558,31 @@ export const kValidTextureFormatsForCopyE2T = [
// Other related stuff
//

/**
* Pass this to `expandWithParams` to expand a format list into a format+aspect list.
*
* In some cases, `aspect` will be unset, invoking the default (which is always `"all"`).
*/
export function* aspectParamsForTextureFormat(format: GPUTextureFormat): Generator<{
/** `GPUTextureAspect | undefined` to pass to WebGPU. */
aspect?: 'depth-only' | 'stencil-only';
/** Key for the aspect in the info table: `kTextureFormatInfo[format][_aspectKey]`. */
_aspectKey: 'color' | 'depth' | 'stencil';
}> {
const info = kTextureFormatInfo[format];
if (info.color?.bytes) {
yield { _aspectKey: 'color' } as const;
}
if (info.depth?.bytes) {
yield { aspect: 'depth-only', _aspectKey: 'depth' } as const;
if (!info.stencil) yield { _aspectKey: 'depth' } as const;
}
if (info.stencil?.bytes) {
yield { aspect: 'stencil-only', _aspectKey: 'stencil' } as const;
if (!info.depth) yield { _aspectKey: 'stencil' } as const;
}
}

const kDepthStencilFormatCapabilityInBufferTextureCopy = {
// kUnsizedDepthStencilFormats
depth24plus: {
Expand Down

0 comments on commit ca16d20

Please sign in to comment.