From 149e02ab793f6a323a5ea4a3d2f52547aefc1434 Mon Sep 17 00:00:00 2001 From: Greggman Date: Tue, 7 Nov 2023 00:45:53 -0800 Subject: [PATCH] Compat: Test textureBindingViewDimension validation (#3098) Compat: Test textureBindingViewDimension validation Tests that if textureBindingViewDimension is incompatible with a texture's dimension in createTexture a validation error is generated Tests that if a texture view's dimension does not match the texture's textureBindingViewDimension when passed to createBindGroup a validation error is generated. --- .../api/validation/createBindGroup.spec.ts | 176 ++++++++++++++++++ .../validation/texture/createTexture.spec.ts | 31 +++ src/webgpu/listing_meta.json | 2 + src/webgpu/util/texture/base.ts | 44 ++++- 4 files changed, 246 insertions(+), 7 deletions(-) create mode 100644 src/webgpu/compat/api/validation/createBindGroup.spec.ts diff --git a/src/webgpu/compat/api/validation/createBindGroup.spec.ts b/src/webgpu/compat/api/validation/createBindGroup.spec.ts new file mode 100644 index 000000000000..c27d8091f0ab --- /dev/null +++ b/src/webgpu/compat/api/validation/createBindGroup.spec.ts @@ -0,0 +1,176 @@ +export const description = ` +Tests that, in compat mode, the dimension of a view is compatible with a texture's textureBindingViewDimension. +`; + +import { makeTestGroup } from '../../../../common/framework/test_group.js'; +import { kTextureDimensions, kTextureViewDimensions } from '../../../capability_info.js'; +import { + effectiveViewDimensionForTexture, + getTextureDimensionFromView, +} from '../../../util/texture/base.js'; +import { CompatibilityTest } from '../../compatibility_test.js'; + +export const g = makeTestGroup(CompatibilityTest); + +function isTextureBindingViewDimensionCompatibleWithDimension( + dimension: GPUTextureDimension = '2d', + textureBindingViewDimension: GPUTextureViewDimension = '2d' +) { + return getTextureDimensionFromView(textureBindingViewDimension) === dimension; +} + +function isValidViewDimensionForDimension( + dimension: GPUTextureDimension | undefined, + depthOrArrayLayers: number, + viewDimension: GPUTextureViewDimension | undefined +) { + if (viewDimension === undefined) { + return true; + } + + switch (dimension) { + case '1d': + return viewDimension === '1d'; + case '2d': + case undefined: + switch (viewDimension) { + case undefined: + case '2d': + case '2d-array': + return true; + case 'cube': + return depthOrArrayLayers === 6; + case 'cube-array': + return depthOrArrayLayers % 6 === 0; + default: + return false; + } + break; + case '3d': + return viewDimension === '3d'; + } +} + +function isValidDimensionForDepthOrArrayLayers( + dimension: GPUTextureDimension | undefined, + depthOrArrayLayers: number +) { + switch (dimension) { + case '1d': + return depthOrArrayLayers === 1; + default: + return true; + } +} + +function isValidViewDimensionForDepthOrArrayLayers( + viewDimension: GPUTextureViewDimension | undefined, + depthOrArrayLayers: number +) { + switch (viewDimension) { + case 'cube': + return depthOrArrayLayers === 6; + case 'cube-array': + return depthOrArrayLayers % 6 === 0; + default: + return true; + } + return viewDimension === 'cube'; +} + +function getEffectiveTextureBindingViewDimension( + dimension: GPUTextureDimension | undefined, + depthOrArrayLayers: number, + textureBindingViewDimension: GPUTextureViewDimension | undefined +) { + if (textureBindingViewDimension) { + return textureBindingViewDimension; + } + + switch (dimension) { + case '1d': + return '1d'; + case '2d': + case undefined: + return depthOrArrayLayers > 1 ? '2d-array' : '2d'; + break; + case '3d': + return '3d'; + } +} + +g.test('viewDimension_matches_textureBindingViewDimension') + .desc( + ` + Tests that, in compat mode, the dimension of a view is compatible with a texture's textureBindingViewDimension + when used as a TEXTURE_BINDING. + ` + ) + .params(u => + u // + .combine('dimension', [...kTextureDimensions, undefined]) + .combine('textureBindingViewDimension', [...kTextureViewDimensions, undefined]) + .combine('viewDimension', [...kTextureViewDimensions, undefined]) + .combine('depthOrArrayLayers', [1, 2, 6]) + .filter( + ({ dimension, textureBindingViewDimension, depthOrArrayLayers, viewDimension }) => + textureBindingViewDimension !== 'cube-array' && + viewDimension !== 'cube-array' && + isTextureBindingViewDimensionCompatibleWithDimension( + dimension, + textureBindingViewDimension + ) && + isValidViewDimensionForDimension(dimension, depthOrArrayLayers, viewDimension) && + isValidViewDimensionForDepthOrArrayLayers( + textureBindingViewDimension, + depthOrArrayLayers + ) && + isValidDimensionForDepthOrArrayLayers(dimension, depthOrArrayLayers) + ) + ) + .fn(t => { + const { dimension, textureBindingViewDimension, viewDimension, depthOrArrayLayers } = t.params; + + const texture = t.device.createTexture({ + size: [1, 1, depthOrArrayLayers], + format: 'rgba8unorm', + usage: GPUTextureUsage.TEXTURE_BINDING, + ...(dimension && { dimension }), + ...(textureBindingViewDimension && { textureBindingViewDimension }), + } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL + t.trackForCleanup(texture); + + const effectiveTextureBindingViewDimension = getEffectiveTextureBindingViewDimension( + dimension, + texture.depthOrArrayLayers, + textureBindingViewDimension + ); + + const effectiveViewDimension = getEffectiveTextureBindingViewDimension( + dimension, + texture.depthOrArrayLayers, + viewDimension + ); + + const layout = t.device.createBindGroupLayout({ + entries: [ + { + binding: 0, + visibility: GPUShaderStage.COMPUTE, + texture: { + viewDimension: effectiveViewDimensionForTexture(texture, viewDimension), + }, + }, + ], + }); + + const resource = texture.createView({ dimension: viewDimension }); + const shouldError = effectiveTextureBindingViewDimension !== effectiveViewDimension; + + t.expectValidationError(() => { + t.device.createBindGroup({ + layout, + entries: [{ binding: 0, resource }], + }); + }, shouldError); + }); diff --git a/src/webgpu/compat/api/validation/texture/createTexture.spec.ts b/src/webgpu/compat/api/validation/texture/createTexture.spec.ts index 9f0d35326896..2dfc4d94ea07 100644 --- a/src/webgpu/compat/api/validation/texture/createTexture.spec.ts +++ b/src/webgpu/compat/api/validation/texture/createTexture.spec.ts @@ -1,8 +1,11 @@ export const description = ` Tests that you can not use bgra8unorm-srgb in compat mode. +Tests that textureBindingViewDimension must compatible with texture dimension `; import { makeTestGroup } from '../../../../../common/framework/test_group.js'; +import { kTextureDimensions, kTextureViewDimensions } from '../../../../capability_info.js'; +import { getTextureDimensionFromView } from '../../../../util/texture/base.js'; import { CompatibilityTest } from '../../../compatibility_test.js'; export const g = makeTestGroup(CompatibilityTest); @@ -39,3 +42,31 @@ g.test('unsupportedTextureViewFormats') true ); }); + +g.test('invalidTextureBindingViewDimension') + .desc( + `Tests that you can not specify a textureBindingViewDimension that is incompatible with the texture's dimension.` + ) + .params(u => + u // + .combine('dimension', kTextureDimensions) + .combine('textureBindingViewDimension', kTextureViewDimensions) + ) + .fn(t => { + const { dimension, textureBindingViewDimension } = t.params; + const shouldError = getTextureDimensionFromView(textureBindingViewDimension) !== dimension; + t.expectGPUError( + 'validation', + () => { + const texture = t.device.createTexture({ + size: [1, 1, dimension === '1d' ? 1 : 6], + format: 'rgba8unorm', + usage: GPUTextureUsage.TEXTURE_BINDING, + dimension, + textureBindingViewDimension, + } as GPUTextureDescriptor); // MAINTENANCE_TODO: remove cast once textureBindingViewDimension is added to IDL + t.trackForCleanup(texture); + }, + shouldError + ); + }); diff --git a/src/webgpu/listing_meta.json b/src/webgpu/listing_meta.json index b060b85e0726..55d9e98f1a2e 100644 --- a/src/webgpu/listing_meta.json +++ b/src/webgpu/listing_meta.json @@ -829,6 +829,7 @@ "webgpu:api,validation,texture,rg11b10ufloat_renderable:begin_render_pass_single_sampled:*": { "subcaseMS": 1.200 }, "webgpu:api,validation,texture,rg11b10ufloat_renderable:create_render_pipeline:*": { "subcaseMS": 2.400 }, "webgpu:api,validation,texture,rg11b10ufloat_renderable:create_texture:*": { "subcaseMS": 12.700 }, + "webgpu:compat,api,validation,createBindGroup:viewDimension_matches_textureBindingViewDimension:*": { "subcaseMS": 6.523 }, "webgpu:compat,api,validation,encoding,cmds,copyTextureToBuffer:compressed:*": { "subcaseMS": 202.929 }, "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,unused:*": { "subcaseMS": 1.501 }, "webgpu:compat,api,validation,encoding,programmable,pipeline_bind_group_compat:twoDifferentTextureViews,compute_pass,used:*": { "subcaseMS": 49.405 }, @@ -837,6 +838,7 @@ "webgpu:compat,api,validation,render_pipeline,fragment_state:colorState:*": { "subcaseMS": 32.604 }, "webgpu:compat,api,validation,render_pipeline,shader_module:sample_mask:*": { "subcaseMS": 14.801 }, "webgpu:compat,api,validation,render_pipeline,vertex_state:maxVertexAttributesVertexIndexInstanceIndex:*": { "subcaseMS": 3.700 }, + "webgpu:compat,api,validation,texture,createTexture:invalidTextureBindingViewDimension:*": { "subcaseMS": 6.022 }, "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureFormats:*": { "subcaseMS": 0.700 }, "webgpu:compat,api,validation,texture,createTexture:unsupportedTextureViewFormats:*": { "subcaseMS": 0.601 }, "webgpu:compat,api,validation,texture,cubeArray:cube_array:*": { "subcaseMS": 13.701 }, diff --git a/src/webgpu/util/texture/base.ts b/src/webgpu/util/texture/base.ts index 67b4fc715627..8da318aae633 100644 --- a/src/webgpu/util/texture/base.ts +++ b/src/webgpu/util/texture/base.ts @@ -157,15 +157,23 @@ export function viewDimensionsForTextureDimension(textureDimension: GPUTextureDi } } -/** Returns the default view dimension for a given texture descriptor. */ -export function defaultViewDimensionsForTexture(textureDescriptor: Readonly) { - switch (textureDescriptor.dimension) { +/** Returns the effective view dimension for a given texture dimension and depthOrArrayLayers */ +export function effectiveViewDimensionForDimension( + viewDimension: GPUTextureViewDimension | undefined, + dimension: GPUTextureDimension | undefined, + depthOrArrayLayers: number +) { + if (viewDimension) { + return viewDimension; + } + + switch (dimension || '2d') { case '1d': return '1d'; - case '2d': { - const sizeDict = reifyExtent3D(textureDescriptor.size); - return sizeDict.depthOrArrayLayers > 1 ? '2d-array' : '2d'; - } + case '2d': + case undefined: + return depthOrArrayLayers > 1 ? '2d-array' : '2d'; + break; case '3d': return '3d'; default: @@ -173,6 +181,28 @@ export function defaultViewDimensionsForTexture(textureDescriptor: Readonly) { + const sizeDict = reifyExtent3D(textureDescriptor.size); + return effectiveViewDimensionForDimension( + undefined, + textureDescriptor.dimension, + sizeDict.depthOrArrayLayers + ); +} + /** Reifies the optional fields of `GPUTextureDescriptor`. * MAINTENANCE_TODO: viewFormats should not be omitted here, but it seems likely that the * @webgpu/types definition will have to change before we can include it again.