diff --git a/src/webgpu/api/operation/texture_view/write.spec.ts b/src/webgpu/api/operation/texture_view/write.spec.ts index 43b27f2874a5..aa41e7e176ea 100644 --- a/src/webgpu/api/operation/texture_view/write.spec.ts +++ b/src/webgpu/api/operation/texture_view/write.spec.ts @@ -36,6 +36,9 @@ const kTextureViewWriteMethods = [ ] as const; type TextureViewWriteMethod = (typeof kTextureViewWriteMethods)[number]; +const kTextureViewUsageMethods = ['inherit', 'minimal'] as const; +type TextureViewUsageMethod = (typeof kTextureViewUsageMethods)[number]; + // Src color values to read from a shader array. const kColorsFloat = [ { R: 1.0, G: 0.0, B: 0.0, A: 0.8 }, @@ -271,6 +274,22 @@ function writeTextureAndGetExpectedTexelView( return expectedTexelView; } +function getTextureViewUsage( + viewUsageMethod: TextureViewUsageMethod, + minimalUsageForTest: GPUTextureUsageFlags +) { + switch (viewUsageMethod) { + case 'inherit': + return 0; + + case 'minimal': + return minimalUsageForTest; + + default: + unreachable(); + } +} + g.test('format') .desc( `Views of every allowed format. @@ -280,6 +299,7 @@ Read values from color array in the shader, and write it to the texture view via - x= every texture format - x= sampleCount {1, 4} if valid - x= every possible view write method (see above) +- x= inherited or minimal texture view usage TODO: Test sampleCount > 1 for 'render-pass-store' after extending copySinglePixelTextureToBufferUsingComputePass to read multiple pixels from multisampled textures. [1] @@ -318,6 +338,7 @@ TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-compone } return true; }) + .combine('viewUsageMethod', kTextureViewUsageMethods) ) .beforeAllSubcases(t => { const { format, method } = t.params; @@ -332,13 +353,12 @@ TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-compone } }) .fn(t => { - const { format, method, sampleCount } = t.params; + const { format, method, sampleCount, viewUsageMethod } = t.params; - const usage = - GPUTextureUsage.COPY_SRC | - (method.includes('storage') - ? GPUTextureUsage.STORAGE_BINDING - : GPUTextureUsage.RENDER_ATTACHMENT); + const textureUsageForMethod = method.includes('storage') + ? GPUTextureUsage.STORAGE_BINDING + : GPUTextureUsage.RENDER_ATTACHMENT; + const usage = GPUTextureUsage.COPY_SRC | textureUsageForMethod; const texture = t.createTextureTracked({ format, @@ -347,7 +367,9 @@ TODO: Test rgb10a2uint when TexelRepresentation.numericRange is made per-compone sampleCount, }); - const view = texture.createView(); + const view = texture.createView({ + usage: getTextureViewUsage(viewUsageMethod, textureUsageForMethod), + }); const expectedTexelView = writeTextureAndGetExpectedTexelView( t, method, diff --git a/src/webgpu/api/validation/createView.spec.ts b/src/webgpu/api/validation/createView.spec.ts index 56a603b714e8..c3e56bb4f011 100644 --- a/src/webgpu/api/validation/createView.spec.ts +++ b/src/webgpu/api/validation/createView.spec.ts @@ -6,8 +6,10 @@ import { unreachable } from '../../../common/util/util.js'; import { kTextureAspects, kTextureDimensions, + kTextureUsages, kTextureViewDimensions, } from '../../capability_info.js'; +import { GPUConst } from '../../constants.js'; import { kTextureFormatInfo, kAllTextureFormats, @@ -339,3 +341,73 @@ g.test('texture_state') texture.createView(); }, state === 'invalid'); }); + +g.test('texture_view_usage') + .desc( + `Test texture view usage (single, combined, inherited) for every texture format and texture usage` + ) + .params(u => + u // + .combine('format', kAllTextureFormats) + .combine('textureUsage0', kTextureUsages) + .combine('textureUsage1', kTextureUsages) + .filter(({ format, textureUsage0, textureUsage1 }) => { + const info = kTextureFormatInfo[format]; + const textureUsage = textureUsage0 | textureUsage1; + + if ( + (textureUsage & GPUConst.TextureUsage.RENDER_ATTACHMENT) !== 0 && + info.color && + !info.colorRender + ) { + return false; + } + + return true; + }) + .beginSubcases() + .combine('textureViewUsage0', [0, ...kTextureUsages]) + .combine('textureViewUsage1', [0, ...kTextureUsages]) + ) + .beforeAllSubcases(t => { + const { format, textureUsage0, textureUsage1 } = t.params; + const info = kTextureFormatInfo[format]; + const textureUsage = textureUsage0 | textureUsage1; + t.skipIfTextureFormatNotSupported(format); + t.selectDeviceOrSkipTestCase(info.feature); + if (textureUsage & GPUTextureUsage.STORAGE_BINDING) { + t.skipIfTextureFormatNotUsableAsStorageTexture(format); + } + }) + .fn(t => { + const { format, textureUsage0, textureUsage1, textureViewUsage0, textureViewUsage1 } = t.params; + const info = kTextureFormatInfo[format]; + + const size = [info.blockWidth, info.blockHeight, 1]; + const dimension = '2d'; + const mipLevelCount = 1; + const usage = textureUsage0 | textureUsage1; + + const textureDescriptor: GPUTextureDescriptor = { + size, + mipLevelCount, + dimension, + format, + usage, + }; + + const texture = t.createTextureTracked(textureDescriptor); + + let success = true; + + const textureViewUsage = textureViewUsage0 | textureViewUsage1; + + // Texture view usage must be a subset of texture usage + if ((~usage & textureViewUsage) !== 0) success = false; + + t.expectValidationError(() => { + texture.createView({ + usage: textureViewUsage, + }); + }, !success); + }); diff --git a/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts b/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts index 05f048ac050c..a601fafec2fc 100644 --- a/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts +++ b/src/webgpu/api/validation/resource_usages/texture/in_render_misc.spec.ts @@ -4,6 +4,7 @@ Texture Usages Validation Tests on All Kinds of WebGPU Subresource Usage Scopes. import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { unreachable } from '../../../../../common/util/util.js'; +import { kTextureUsages } from '../../../../capability_info.js'; import { ValidationTest } from '../../validation_test.js'; import { TextureBindingType, @@ -571,3 +572,79 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') encoder.finish(); }, false); }); + +g.test('subresources,texture_view_usages') + .desc( + ` + Test that the usages of the texture view are used to validate compatibility in command encoding + instead of the usages of the base texture.` + ) + .params(u => + u + .combine('bindingType', ['color-attachment', ...kTextureBindingTypes] as const) + .combine('viewUsage', [0, ...kTextureUsages]) + ) + .fn(t => { + const { bindingType, viewUsage } = t.params; + + const texture = t.createTextureTracked({ + format: 'r32float', + usage: + GPUTextureUsage.COPY_SRC | + GPUTextureUsage.COPY_DST | + GPUTextureUsage.TEXTURE_BINDING | + GPUTextureUsage.STORAGE_BINDING | + GPUTextureUsage.RENDER_ATTACHMENT, + size: [kTextureSize, kTextureSize, 1], + ...(t.isCompatibility && { + textureBindingViewDimension: '2d-array', + }), + }); + + switch (bindingType) { + case 'color-attachment': { + const encoder = t.device.createCommandEncoder(); + const renderPassEncoder = encoder.beginRenderPass({ + colorAttachments: [ + { view: texture.createView({ usage: viewUsage }), loadOp: 'load', storeOp: 'store' }, + ], + }); + renderPassEncoder.end(); + + const success = viewUsage === 0 || (viewUsage & GPUTextureUsage.RENDER_ATTACHMENT) !== 0; + + t.expectValidationError(() => { + encoder.finish(); + }, !success); + break; + } + case 'sampled-texture': + case 'readonly-storage-texture': + case 'writeonly-storage-texture': + case 'readwrite-storage-texture': + { + let success = true; + if (viewUsage !== 0) { + if (bindingType === 'sampled-texture') { + if ((viewUsage & GPUTextureUsage.TEXTURE_BINDING) === 0) success = false; + } else { + if ((viewUsage & GPUTextureUsage.STORAGE_BINDING) === 0) success = false; + } + } + + t.expectValidationError(() => { + t.createBindGroupForTest( + texture.createView({ + dimension: '2d-array', + usage: viewUsage, + }), + bindingType, + 'unfilterable-float' + ); + }, !success); + } + break; + default: + unreachable(); + } + });