From fa4873f0a303566ca6f34744a253d96f5e462d3d Mon Sep 17 00:00:00 2001 From: Yunchao He Date: Fri, 14 Aug 2020 18:18:21 -0700 Subject: [PATCH] Add test for different read write usages combinations (#253) * Add test for different read write usages combinations Texture read usages include sampled-texture and readonly storage texture. Texture write usages include writeonly storage texture and render target. Different usages upon the same subresource should follow this rule: - multiple read usages upon the same subresource is valid. - multiple write usages, or read/write usages upon the same subresource is invalid, with one exception that multiple writeonly storage texture usages are valid. * Add a comment * Addressed feedback from Corentin and Kai * Addressed Kai's feedback * Addressed Kai's feedback: add test for depth/stencil * rename a test, update test file description * Address feedback from Corentin Co-authored-by: Kai Ninomiya --- .../textureUsageInRender.spec.ts | 304 +++++++++++++----- 1 file changed, 228 insertions(+), 76 deletions(-) diff --git a/src/webgpu/api/validation/resource_usages/textureUsageInRender.spec.ts b/src/webgpu/api/validation/resource_usages/textureUsageInRender.spec.ts index dd1f0d47f696..9c54aee70361 100644 --- a/src/webgpu/api/validation/resource_usages/textureUsageInRender.spec.ts +++ b/src/webgpu/api/validation/resource_usages/textureUsageInRender.spec.ts @@ -2,11 +2,23 @@ export const description = ` Texture Usages Validation Tests in Render Pass. Test Coverage: - - Tests that read and write usages upon the same texture subresource, or different subresources - of the same texture. Different subresources of the same texture includes different mip levels, - different array layers, and different aspects. - - When read and write usages are binding to the same texture subresource, an error should be - generated. Otherwise, no error should be generated. + + - For each combination of two texture usages: + - For various subresource ranges (different mip levels or array layers) that overlap a given + subresources or not for color formats: + - Check that an error is generated when read-write or write-write usages are binding to the + same texture subresource. Otherwise, no error should be generated. One exception is race + condition upon two writeonly-storage-texture usages, which is valid. + + - For each combination of two texture usages: + - For various aspects (all, depth-only, stencil-only) that overlap a given subresources or not + for depth/stencil formats: + - Check that an error is generated when read-write or write-write usages are binding to the + same aspect. Otherwise, no error should be generated. + + - Test combinations of two shader stages: + - Texture usages in bindings with invisible shader stages should be tracked. Invisible shader + stages include shader stage with visibility none and compute shader stage in render pass. `; import { poptions, params } from '../../../../common/framework/params_builder.js'; @@ -53,64 +65,173 @@ class TextureUsageTracking extends ValidationTest { export const g = makeTestGroup(TextureUsageTracking); -const READ_BASE_LEVEL = 3; -const READ_BASE_LAYER = 0; - -g.test('readwrite_upon_subresources') - .params([ - // read and write usages are binding to the same texture subresource. - { - writeBaseLevel: READ_BASE_LEVEL, - writeBaseLayer: READ_BASE_LAYER, - _success: false, - }, - - // read and write usages are binding to different mip levels of the same texture. - { - writeBaseLevel: READ_BASE_LEVEL + 1, - writeBaseLayer: READ_BASE_LAYER, - _success: true, - }, - - // read and write usages are binding to different array layers of the same texture. - { - writeBaseLevel: READ_BASE_LEVEL, - writeBaseLayer: READ_BASE_LAYER + 1, - _success: true, - }, - ]) - .fn(async t => { - const { writeBaseLevel, writeBaseLayer, _success } = t.params; +const BASE_LEVEL = 3; +const BASE_LAYER = 0; +const TOTAL_LEVELS = 6; +const TOTAL_LAYERS = 2; - const texture = t.createTexture({ arrayLayerCount: 2, mipLevelCount: 6 }); +g.test('subresources_and_binding_types_combination_for_color') + .params( + params() + .combine([ + // Two texture usages are binding to the same texture subresource. + { + baseLevel: BASE_LEVEL, + baseLayer: BASE_LAYER, + levelCount: 1, + layerCount: 1, + _resourceSuccess: false, + }, - const sampleView = texture.createView({ - baseMipLevel: READ_BASE_LEVEL, - mipLevelCount: 1, - baseArrayLayer: READ_BASE_LAYER, - arrayLayerCount: 1, + // Two texture usages are binding to different mip levels of the same texture. + { + baseLevel: BASE_LEVEL + 1, + baseLayer: BASE_LAYER, + levelCount: 1, + layerCount: 1, + _resourceSuccess: true, + }, + + // Two texture usages are binding to different array layers of the same texture. + { + baseLevel: BASE_LEVEL, + baseLayer: BASE_LAYER + 1, + levelCount: 1, + layerCount: 1, + _resourceSuccess: true, + }, + + // The second texture usage contains the whole mip chain where the first texture usage is using. + { + baseLevel: 0, + baseLayer: BASE_LAYER, + levelCount: TOTAL_LEVELS, + layerCount: 1, + _resourceSuccess: false, + }, + + // The second texture usage contains the all layers where the first texture usage is using. + { + baseLevel: BASE_LEVEL, + baseLayer: 0, + levelCount: 1, + layerCount: TOTAL_LAYERS, + _resourceSuccess: false, + }, + ]) + .combine([ + { + type0: 'sampled-texture', + type1: 'sampled-texture', + _usageSuccess: true, + }, + { + type0: 'sampled-texture', + type1: 'readonly-storage-texture', + _usageSuccess: true, + }, + { + type0: 'sampled-texture', + type1: 'writeonly-storage-texture', + _usageSuccess: false, + }, + { + type0: 'sampled-texture', + type1: 'render-target', + _usageSuccess: false, + }, + { + type0: 'readonly-storage-texture', + type1: 'readonly-storage-texture', + _usageSuccess: true, + }, + { + type0: 'readonly-storage-texture', + type1: 'writeonly-storage-texture', + _usageSuccess: false, + }, + { + type0: 'readonly-storage-texture', + type1: 'render-target', + _usageSuccess: false, + }, + // Race condition upon multiple writable storage texture is valid. + { + type0: 'writeonly-storage-texture', + type1: 'writeonly-storage-texture', + _usageSuccess: true, + }, + { + type0: 'writeonly-storage-texture', + type1: 'render-target', + _usageSuccess: false, + }, + ] as const) + ) + .fn(async t => { + const { + baseLevel, + baseLayer, + levelCount, + layerCount, + type0, + type1, + _usageSuccess, + _resourceSuccess, + } = t.params; + + const texture = t.createTexture({ + arrayLayerCount: TOTAL_LAYERS, + mipLevelCount: TOTAL_LEVELS, + usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.STORAGE | GPUTextureUsage.OUTPUT_ATTACHMENT, }); - const renderView = texture.createView({ - baseMipLevel: writeBaseLevel, + + const view0 = texture.createView({ + baseMipLevel: BASE_LEVEL, mipLevelCount: 1, - baseArrayLayer: writeBaseLayer, + baseArrayLayer: BASE_LAYER, arrayLayerCount: 1, }); - const bindGroupLayout = t.device.createBindGroupLayout({ - entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }], + const view1Dimension = layerCount !== 1 ? '2d-array' : '2d'; + const view1 = texture.createView({ + dimension: view1Dimension, + baseMipLevel: baseLevel, + mipLevelCount: levelCount, + baseArrayLayer: baseLayer, + arrayLayerCount: layerCount, }); + // TODO: Add two 'render-target' usages for color attachments. + const bglEntries: GPUBindGroupLayoutEntry[] = [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + type: type0, + storageTextureFormat: type0 === 'sampled-texture' ? undefined : 'rgba8unorm', + }, + ]; + const bgEntries: GPUBindGroupEntry[] = [{ binding: 0, resource: view0 }]; + if (type1 !== 'render-target') { + bglEntries.push({ + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + type: type1, + viewDimension: view1Dimension, + storageTextureFormat: type1 === 'sampled-texture' ? undefined : 'rgba8unorm', + }); + bgEntries.push({ binding: 1, resource: view1 }); + } const bindGroup = t.device.createBindGroup({ - entries: [{ binding: 0, resource: sampleView }], - layout: bindGroupLayout, + entries: bgEntries, + layout: t.device.createBindGroupLayout({ entries: bglEntries }), }); const encoder = t.device.createCommandEncoder(); const pass = encoder.beginRenderPass({ colorAttachments: [ { - attachment: renderView, + attachment: type1 === 'render-target' ? view1 : t.createTexture().createView(), loadValue: { r: 0.0, g: 1.0, b: 0.0, a: 1.0 }, storeOp: 'store', }, @@ -119,46 +240,69 @@ g.test('readwrite_upon_subresources') pass.setBindGroup(0, bindGroup); pass.endPass(); + const success = _resourceSuccess || _usageSuccess; t.expectValidationError(() => { encoder.finish(); - }, !_success); + }, !success); }); -g.test('readwrite_upon_aspects') +g.test('subresources_and_binding_types_combination_for_aspect') .params( params() .combine(poptions('format', kDepthStencilFormats)) - .combine(poptions('readAspect', ['all', 'depth-only', 'stencil-only'] as const)) - .combine(poptions('writeAspect', ['all', 'depth-only', 'stencil-only'] as const)) + .combine(poptions('aspect0', ['all', 'depth-only', 'stencil-only'] as const)) + .combine(poptions('aspect1', ['all', 'depth-only', 'stencil-only'] as const)) .unless( - ({ format, readAspect, writeAspect }) => - (readAspect === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) || - (writeAspect === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) + ({ format, aspect0, aspect1 }) => + (aspect0 === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) || + (aspect1 === 'stencil-only' && !kDepthStencilFormatInfo[format].stencil) ) .unless( - ({ format, readAspect, writeAspect }) => - (readAspect === 'depth-only' && !kDepthStencilFormatInfo[format].depth) || - (writeAspect === 'depth-only' && !kDepthStencilFormatInfo[format].depth) + ({ format, aspect0, aspect1 }) => + (aspect0 === 'depth-only' && !kDepthStencilFormatInfo[format].depth) || + (aspect1 === 'depth-only' && !kDepthStencilFormatInfo[format].depth) ) + .combine([ + { + type0: 'sampled-texture', + type1: 'sampled-texture', + _usageSuccess: true, + }, + { + type0: 'sampled-texture', + type1: 'render-target', + _usageSuccess: false, + }, + ] as const) ) .fn(async t => { - const { format, readAspect, writeAspect } = t.params; + const { format, aspect0, aspect1, type0, type1, _usageSuccess } = t.params; - const view = t.createTexture({ format }).createView(); - - const bindGroupLayout = t.device.createBindGroupLayout({ - entries: [{ binding: 0, visibility: GPUShaderStage.FRAGMENT, type: 'sampled-texture' }], - }); + const texture = t.createTexture({ format }); + const view0 = texture.createView({ aspect: aspect0 }); + const view1 = texture.createView({ aspect: aspect1 }); + const bglEntries: GPUBindGroupLayoutEntry[] = [ + { + binding: 0, + visibility: GPUShaderStage.FRAGMENT, + type: type0, + }, + ]; + const bgEntries: GPUBindGroupEntry[] = [{ binding: 0, resource: view0 }]; + if (type1 !== 'render-target') { + bglEntries.push({ + binding: 1, + visibility: GPUShaderStage.FRAGMENT, + type: type1, + }); + bgEntries.push({ binding: 1, resource: view1 }); + } const bindGroup = t.device.createBindGroup({ - entries: [{ binding: 0, resource: view }], - layout: bindGroupLayout, + entries: bgEntries, + layout: t.device.createBindGroupLayout({ entries: bglEntries }), }); - const success = - (readAspect === 'depth-only' && writeAspect === 'stencil-only') || - (readAspect === 'stencil-only' && writeAspect === 'depth-only'); - const encoder = t.device.createCommandEncoder(); const pass = encoder.beginRenderPass({ colorAttachments: [ @@ -168,17 +312,25 @@ g.test('readwrite_upon_aspects') storeOp: 'store', }, ], - depthStencilAttachment: { - attachment: view, - depthStoreOp: 'clear', - depthLoadValue: 'load', - stencilStoreOp: 'clear', - stencilLoadValue: 'load', - }, + depthStencilAttachment: + type1 !== 'render-target' + ? undefined + : { + attachment: view1, + depthStoreOp: 'clear', + depthLoadValue: 'load', + stencilStoreOp: 'clear', + stencilLoadValue: 'load', + }, }); pass.setBindGroup(0, bindGroup); pass.endPass(); + const disjointAspects = + (aspect0 === 'depth-only' && aspect1 === 'stencil-only') || + (aspect0 === 'stencil-only' && aspect1 === 'depth-only'); + const success = disjointAspects || _usageSuccess; + t.expectValidationError(() => { encoder.finish(); }, !success);