From 1746bcbc10a809cbadb3b131675b885ed08d9da5 Mon Sep 17 00:00:00 2001 From: Geoff Lang Date: Mon, 30 Sep 2024 10:40:46 -0400 Subject: [PATCH] Test usage field of GPUTextureViewDescriptor (#3954) * Test usage field of GPUTextureViewDescriptor Add variants of existing view format writing tests to cover end2end inherited and minimal view usage. Add validation tests for texture view creation with usage and texture view usage compatibility in render passes/bind groups. --- .../api/operation/texture_view/write.spec.ts | 36 +++++++-- src/webgpu/api/validation/createView.spec.ts | 72 +++++++++++++++++ .../texture/in_render_misc.spec.ts | 77 +++++++++++++++++++ 3 files changed, 178 insertions(+), 7 deletions(-) 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(); + } + });