diff --git a/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts b/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts index 0c4109855624..48683b8b5979 100644 --- a/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts +++ b/src/webgpu/api/validation/resource_usages/texture/in_render_common.spec.ts @@ -6,6 +6,21 @@ import { makeTestGroup } from '../../../../../common/framework/test_group.js'; import { assert, unreachable } from '../../../../../common/util/util.js'; import { ValidationTest } from '../../validation_test.js'; +export type TextureBindingType = + | 'sampled-texture' + | 'writeonly-storage-texture' + | 'readonly-storage-texture' + | 'readwrite-storage-texture'; +export const kTextureBindingTypes = [ + 'sampled-texture', + 'writeonly-storage-texture', + 'readonly-storage-texture', + 'readwrite-storage-texture', +] as const; +export function IsReadOnlyTextureBindingType(t: TextureBindingType): boolean { + return t === 'sampled-texture' || t === 'readonly-storage-texture'; +} + class F extends ValidationTest { getColorAttachment( texture: GPUTexture, @@ -23,21 +38,35 @@ class F extends ValidationTest { createBindGroupForTest( textureView: GPUTextureView, - textureUsage: 'texture' | 'storage', - sampleType: 'float' | 'depth' | 'uint' + textureUsage: TextureBindingType, + sampleType: 'unfilterable-float' | 'depth' | 'uint' ) { const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = { binding: 0, visibility: GPUShaderStage.FRAGMENT, }; switch (textureUsage) { - case 'texture': + case 'sampled-texture': bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType }; break; - case 'storage': + case 'readonly-storage-texture': + bindGroupLayoutEntry.storageTexture = { + access: 'read-only', + format: 'r32float', + viewDimension: '2d-array', + }; + break; + case 'readwrite-storage-texture': + bindGroupLayoutEntry.storageTexture = { + access: 'read-write', + format: 'r32float', + viewDimension: '2d-array', + }; + break; + case 'writeonly-storage-texture': bindGroupLayoutEntry.storageTexture = { access: 'write-only', - format: 'rgba8unorm', + format: 'r32float', viewDimension: '2d-array', }; break; @@ -89,7 +118,7 @@ g.test('subresources,color_attachments') const { layer0, level0, layer1, level1, inSamePass } = t.params; const texture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT, size: [kTextureSize, kTextureSize, kTextureLayers], mipLevelCount: kTextureLevels, @@ -152,8 +181,8 @@ g.test('subresources,color_attachment_and_bind_group') { bgLayer: 1, bgLayerCount: 1 }, { bgLayer: 1, bgLayerCount: 2 }, ]) - .combine('bgUsage', ['texture', 'storage'] as const) - .unless(t => t.bgUsage === 'storage' && t.bgLevelCount > 1) + .combine('bgUsage', kTextureBindingTypes) + .unless(t => t.bgUsage !== 'sampled-texture' && t.bgLevelCount > 1) .combine('inSamePass', [true, false]) ) .fn(t => { @@ -169,7 +198,7 @@ g.test('subresources,color_attachment_and_bind_group') } = t.params; const texture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.TEXTURE_BINDING | @@ -184,7 +213,7 @@ g.test('subresources,color_attachment_and_bind_group') baseMipLevel: bgLevel, mipLevelCount: bgLevelCount, }); - const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'float'); + const bindGroup = t.createBindGroupForTest(bindGroupView, bgUsage, 'unfilterable-float'); const colorAttachment = t.getColorAttachment(texture, { dimension: '2d', @@ -205,7 +234,7 @@ g.test('subresources,color_attachment_and_bind_group') renderPass.end(); const texture2 = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT, size: [kTextureSize, kTextureSize, 1], mipLevelCount: 1, @@ -293,7 +322,7 @@ g.test('subresources,depth_stencil_attachment_and_bind_group') aspect: bgAspect, }); const sampleType = bgAspect === 'depth-only' ? 'depth' : 'uint'; - const bindGroup = t.createBindGroupForTest(bindGroupView, 'texture', sampleType); + const bindGroup = t.createBindGroupForTest(bindGroupView, 'sampled-texture', sampleType); const attachmentView = texture.createView({ dimension: '2d', @@ -388,12 +417,12 @@ g.test('subresources,multiple_bind_groups') { base: 1, count: 1 }, { base: 1, count: 2 }, ]) - .combine('bgUsage0', ['texture', 'storage'] as const) - .combine('bgUsage1', ['texture', 'storage'] as const) + .combine('bgUsage0', kTextureBindingTypes) + .combine('bgUsage1', kTextureBindingTypes) .unless( t => - (t.bgUsage0 === 'storage' && t.bg0Levels.count > 1) || - (t.bgUsage1 === 'storage' && t.bg1Levels.count > 1) + (t.bgUsage0 !== 'sampled-texture' && t.bg0Levels.count > 1) || + (t.bgUsage1 !== 'sampled-texture' && t.bg1Levels.count > 1) ) .combine('inSamePass', [true, false]) ) @@ -401,7 +430,7 @@ g.test('subresources,multiple_bind_groups') const { bg0Levels, bg0Layers, bg1Levels, bg1Layers, bgUsage0, bgUsage1, inSamePass } = t.params; const texture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.STORAGE_BINDING | GPUTextureUsage.TEXTURE_BINDING, size: [kTextureSize, kTextureSize, kTextureLayers], mipLevelCount: kTextureLevels, @@ -420,11 +449,11 @@ g.test('subresources,multiple_bind_groups') baseMipLevel: bg1Levels.base, mipLevelCount: bg1Levels.count, }); - const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'float'); - const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'float'); + const bindGroup0 = t.createBindGroupForTest(bg0, bgUsage0, 'unfilterable-float'); + const bindGroup1 = t.createBindGroupForTest(bg1, bgUsage1, 'unfilterable-float'); const colorTexture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT, size: [kTextureSize, kTextureSize, 1], mipLevelCount: 1, @@ -449,6 +478,8 @@ g.test('subresources,multiple_bind_groups') renderPass2.end(); } + const bothReadOnly = + IsReadOnlyTextureBindingType(bgUsage0) && IsReadOnlyTextureBindingType(bgUsage1); const isMipLevelNotOverlapped = t.isRangeNotOverlapped( bg0Levels.base, bg0Levels.base + bg0Levels.count - 1, @@ -463,7 +494,7 @@ g.test('subresources,multiple_bind_groups') ); const isNotOverlapped = isMipLevelNotOverlapped || isArrayLayerNotOverlapped; - const success = !inSamePass || isNotOverlapped || bgUsage0 === bgUsage1; + const success = !inSamePass || bothReadOnly || isNotOverlapped || bgUsage0 === bgUsage1; t.expectValidationError(() => { encoder.finish(); }, !success); @@ -531,8 +562,8 @@ g.test('subresources,depth_stencil_texture_in_bind_groups') const sampleType0 = aspect0 === 'depth-only' ? 'depth' : 'uint'; const sampleType1 = aspect1 === 'depth-only' ? 'depth' : 'uint'; - const bindGroup0 = t.createBindGroupForTest(bindGroupView0, 'texture', sampleType0); - const bindGroup1 = t.createBindGroupForTest(bindGroupView1, 'texture', sampleType1); + const bindGroup0 = t.createBindGroupForTest(bindGroupView0, 'sampled-texture', sampleType0); + const bindGroup1 = t.createBindGroupForTest(bindGroupView1, 'sampled-texture', sampleType1); const colorTexture = t.device.createTexture({ format: 'rgba8unorm', 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 6f5feb55ce97..b56d6a4b8fc1 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 @@ -5,11 +5,16 @@ 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 { ValidationTest } from '../../validation_test.js'; +import { + TextureBindingType, + kTextureBindingTypes, + IsReadOnlyTextureBindingType, +} from '../texture/in_render_common.spec.js'; class F extends ValidationTest { createBindGroupLayoutForTest( - textureUsage: 'texture' | 'storage', - sampleType: 'float' | 'depth' | 'uint', + textureUsage: TextureBindingType, + sampleType: 'unfilterable-float' | 'depth' | 'uint', visibility: GPUShaderStage['FRAGMENT'] | GPUShaderStage['COMPUTE'] = GPUShaderStage['FRAGMENT'] ): GPUBindGroupLayout { const bindGroupLayoutEntry: GPUBindGroupLayoutEntry = { @@ -18,13 +23,27 @@ class F extends ValidationTest { }; switch (textureUsage) { - case 'texture': + case 'sampled-texture': bindGroupLayoutEntry.texture = { viewDimension: '2d-array', sampleType }; break; - case 'storage': + case 'readonly-storage-texture': + bindGroupLayoutEntry.storageTexture = { + access: 'read-only', + format: 'r32float', + viewDimension: '2d-array', + }; + break; + case 'writeonly-storage-texture': bindGroupLayoutEntry.storageTexture = { access: 'write-only', - format: 'rgba8unorm', + format: 'r32float', + viewDimension: '2d-array', + }; + break; + case 'readwrite-storage-texture': + bindGroupLayoutEntry.storageTexture = { + access: 'read-write', + format: 'r32float', viewDimension: '2d-array', }; break; @@ -39,8 +58,8 @@ class F extends ValidationTest { createBindGroupForTest( textureView: GPUTextureView, - textureUsage: 'texture' | 'storage', - sampleType: 'float' | 'depth' | 'uint', + textureUsage: TextureBindingType, + sampleType: 'unfilterable-float' | 'depth' | 'uint', visibility: GPUShaderStage['FRAGMENT'] | GPUShaderStage['COMPUTE'] = GPUShaderStage['FRAGMENT'] ) { return this.device.createBindGroup({ @@ -64,20 +83,16 @@ g.test('subresources,set_bind_group_on_same_index_color_texture') ) .params(u => u - .combineWithParams([ - { useDifferentTextureAsTexture2: true, baseLayer2: 0, view2Binding: 'texture' }, - { useDifferentTextureAsTexture2: false, baseLayer2: 0, view2Binding: 'texture' }, - { useDifferentTextureAsTexture2: false, baseLayer2: 1, view2Binding: 'texture' }, - { useDifferentTextureAsTexture2: false, baseLayer2: 0, view2Binding: 'storage' }, - { useDifferentTextureAsTexture2: false, baseLayer2: 1, view2Binding: 'storage' }, - ] as const) - .combine('hasConflict', [true, false]) + .combine('useDifferentTextureAsTexture2', [true, false]) + .combine('baseLayer2', [0, 1] as const) + .combine('view1Binding', kTextureBindingTypes) + .combine('view2Binding', kTextureBindingTypes) ) .fn(t => { - const { useDifferentTextureAsTexture2, baseLayer2, view2Binding, hasConflict } = t.params; + const { useDifferentTextureAsTexture2, baseLayer2, view1Binding, view2Binding } = t.params; const texture0 = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING, size: [kTextureSize, kTextureSize, kTextureLayers], }); @@ -87,19 +102,12 @@ g.test('subresources,set_bind_group_on_same_index_color_texture') baseArrayLayer: 0, arrayLayerCount: 1, }); - const bindGroup0 = t.createBindGroupForTest(textureView0, view2Binding, 'float'); - - // In one renderPassEncoder it is an error to set both bindGroup0 and bindGroup1. - const view1Binding = hasConflict - ? view2Binding === 'texture' - ? 'storage' - : 'texture' - : view2Binding; - const bindGroup1 = t.createBindGroupForTest(textureView0, view1Binding, 'float'); + const bindGroup0 = t.createBindGroupForTest(textureView0, view1Binding, 'unfilterable-float'); + const bindGroup1 = t.createBindGroupForTest(textureView0, view2Binding, 'unfilterable-float'); const texture2 = useDifferentTextureAsTexture2 ? t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING, size: [kTextureSize, kTextureSize, kTextureLayers], }) @@ -110,10 +118,14 @@ g.test('subresources,set_bind_group_on_same_index_color_texture') arrayLayerCount: kTextureLayers - baseLayer2, }); // There should be no conflict between bindGroup0 and validBindGroup2. - const validBindGroup2 = t.createBindGroupForTest(textureView2, view2Binding, 'float'); + const validBindGroup2 = t.createBindGroupForTest( + textureView2, + view2Binding, + 'unfilterable-float' + ); - const colorTexture = t.device.createTexture({ - format: 'rgba8unorm', + const unusedColorTexture = t.device.createTexture({ + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT, size: [kTextureSize, kTextureSize, 1], }); @@ -121,7 +133,7 @@ g.test('subresources,set_bind_group_on_same_index_color_texture') const renderPassEncoder = encoder.beginRenderPass({ colorAttachments: [ { - view: colorTexture.createView(), + view: unusedColorTexture.createView(), loadOp: 'load', storeOp: 'store', }, @@ -132,9 +144,12 @@ g.test('subresources,set_bind_group_on_same_index_color_texture') renderPassEncoder.setBindGroup(1, validBindGroup2); renderPassEncoder.end(); + const noConflict = + (IsReadOnlyTextureBindingType(view1Binding) && IsReadOnlyTextureBindingType(view2Binding)) || + view1Binding === view2Binding; t.expectValidationError(() => { encoder.finish(); - }, hasConflict); + }, !noConflict); }); g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture') @@ -165,12 +180,12 @@ g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture') dimension: '2d-array', aspect: bindAspect, }), - 'texture', + 'sampled-texture', bindAspect === 'depth-only' ? 'depth' : 'uint' ); const colorTexture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING, size: [kTextureSize, kTextureSize, 1], ...(t.isCompatibility && { @@ -181,8 +196,8 @@ g.test('subresources,set_bind_group_on_same_index_depth_stencil_texture') colorTexture.createView({ dimension: '2d-array', }), - 'texture', - 'float' + 'sampled-texture', + 'unfilterable-float' ); const encoder = t.device.createCommandEncoder(); @@ -210,12 +225,24 @@ g.test('subresources,set_unused_bind_group') used in the same render or compute pass encoder, its list of internal usages within one usage scope can only be a compatible usage list.` ) - .params(u => u.combine('inRenderPass', [true, false]).combine('hasConflict', [true, false])) + .params(u => + u + .combine('inRenderPass', [true, false]) + .combine('textureUsage0', kTextureBindingTypes) + .combine('textureUsage1', kTextureBindingTypes) + ) .fn(t => { - const { inRenderPass, hasConflict } = t.params; + const { inRenderPass, textureUsage0, textureUsage1 } = t.params; + + if ( + textureUsage0 === 'readwrite-storage-texture' || + textureUsage1 === 'readwrite-storage-texture' + ) { + t.requireLanguageFeatureOrSkipTestCase('readonly_and_readwrite_storage_textures'); + } const texture0 = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.STORAGE_BINDING, size: [kTextureSize, kTextureSize, kTextureLayers], }); @@ -227,40 +254,85 @@ g.test('subresources,set_unused_bind_group') }); const visibility = inRenderPass ? GPUShaderStage.FRAGMENT : GPUShaderStage.COMPUTE; // bindGroup0 is used by the pipelines, and bindGroup1 is not used by the pipelines. - const textureUsage0 = inRenderPass ? 'texture' : 'storage'; - const textureUsage1 = hasConflict ? (inRenderPass ? 'storage' : 'texture') : textureUsage0; - const bindGroup0 = t.createBindGroupForTest(textureView0, textureUsage0, 'float', visibility); - const bindGroup1 = t.createBindGroupForTest(textureView0, textureUsage1, 'float', visibility); + const bindGroup0 = t.createBindGroupForTest( + textureView0, + textureUsage0, + 'unfilterable-float', + visibility + ); + const bindGroup1 = t.createBindGroupForTest( + textureView0, + textureUsage1, + 'unfilterable-float', + visibility + ); const encoder = t.device.createCommandEncoder(); const colorTexture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT, size: [kTextureSize, kTextureSize, 1], }); - const pipelineLayout = t.device.createPipelineLayout({ - bindGroupLayouts: [t.createBindGroupLayoutForTest(textureUsage0, 'float', visibility)], - }); if (inRenderPass) { + let fragmentShader = ''; + switch (textureUsage0) { + case 'sampled-texture': + fragmentShader = ` + @group(0) @binding(0) var texture0 : texture_2d_array; + @fragment fn main() + -> @location(0) vec4 { + return textureLoad(texture0, vec2(), 0, 0); + } + `; + break; + case `readonly-storage-texture`: + fragmentShader = ` + @group(0) @binding(0) var texture0 : texture_storage_2d_array; + @fragment fn main() + -> @location(0) vec4 { + return textureLoad(texture0, vec2(), 0); + } + `; + break; + case `writeonly-storage-texture`: + fragmentShader = ` + @group(0) @binding(0) var texture0 : texture_storage_2d_array; + @fragment fn main() + -> @location(0) vec4 { + textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1)); + return vec4f(0, 0, 0, 1); + } + `; + break; + case `readwrite-storage-texture`: + fragmentShader = ` + @group(0) @binding(0) var texture0 : texture_storage_2d_array; + @fragment fn main() + -> @location(0) vec4 { + let color = textureLoad(texture0, vec2i(), 0); + textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1)); + return color; + } + `; + break; + } + const renderPipeline = t.device.createRenderPipeline({ - layout: pipelineLayout, + layout: t.device.createPipelineLayout({ + bindGroupLayouts: [ + t.createBindGroupLayoutForTest(textureUsage0, 'unfilterable-float', visibility), + ], + }), vertex: { module: t.device.createShaderModule({ code: t.getNoOpShaderCode('VERTEX'), }), - entryPoint: 'main', }, fragment: { module: t.device.createShaderModule({ - code: ` - @group(0) @binding(0) var texture0 : texture_2d_array; - @fragment fn main() - -> @location(0) vec4 { - return textureLoad(texture0, vec2(), 0, 0); - }`, + code: fragmentShader, }), - entryPoint: 'main', - targets: [{ format: 'rgba8unorm' }], + targets: [{ format: 'r32float' }], }, }); @@ -279,29 +351,97 @@ g.test('subresources,set_unused_bind_group') renderPassEncoder.draw(1); renderPassEncoder.end(); } else { + let computeShader = ''; + switch (textureUsage0) { + case 'sampled-texture': + computeShader = ` + @group(0) @binding(0) var texture0 : texture_2d_array; + @group(1) @binding(0) var writableStorage : texture_storage_2d_array; + @compute @workgroup_size(1) fn main() { + let value = textureLoad(texture0, vec2i(), 0, 0); + textureStore(writableStorage, vec2i(), 0, value); + } + `; + break; + case `readonly-storage-texture`: + computeShader = ` + @group(0) @binding(0) var texture0 : texture_storage_2d_array; + @group(1) @binding(0) var writableStorage : texture_storage_2d_array; + @compute @workgroup_size(1) fn main() { + let value = textureLoad(texture0, vec2(), 0); + textureStore(writableStorage, vec2i(), 0, value); + } + `; + break; + case `writeonly-storage-texture`: + computeShader = ` + @group(0) @binding(0) var texture0 : texture_storage_2d_array; + @group(1) @binding(0) var writableStorage : texture_storage_2d_array; + @compute @workgroup_size(1) fn main() { + textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1)); + textureStore(writableStorage, vec2i(), 0, vec4f(1, 0, 0, 1)); + } + `; + break; + case `readwrite-storage-texture`: + computeShader = ` + @group(0) @binding(0) var texture0 : texture_storage_2d_array; + @group(1) @binding(0) var writableStorage : texture_storage_2d_array; + @compute @workgroup_size(1) fn main() { + let color = textureLoad(texture0, vec2i(), 0); + textureStore(texture0, vec2i(), 0, vec4f(1, 0, 0, 1)); + textureStore(writableStorage, vec2i(), 0, color); + } + `; + break; + } + + const pipelineLayout = t.device.createPipelineLayout({ + bindGroupLayouts: [ + t.createBindGroupLayoutForTest(textureUsage0, 'unfilterable-float', visibility), + t.createBindGroupLayoutForTest( + 'writeonly-storage-texture', + 'unfilterable-float', + visibility + ), + ], + }); const computePipeline = t.device.createComputePipeline({ layout: pipelineLayout, compute: { module: t.device.createShaderModule({ - code: ` - @group(0) @binding(0) var texture0 : texture_storage_2d_array; - @compute @workgroup_size(1) - fn main() { - textureStore(texture0, vec2(), 0, vec4()); - }`, + code: computeShader, }), - entryPoint: 'main', }, }); + + const writableStorageTexture = t.device.createTexture({ + format: 'r32float', + usage: GPUTextureUsage.STORAGE_BINDING, + size: [kTextureSize, kTextureSize, 1], + }); + const writableStorageTextureView = writableStorageTexture.createView({ + dimension: '2d-array', + baseArrayLayer: 0, + arrayLayerCount: 1, + }); + const writableStorageTextureBindGroup = t.createBindGroupForTest( + writableStorageTextureView, + 'writeonly-storage-texture', + 'unfilterable-float', + visibility + ); + const computePassEncoder = encoder.beginComputePass(); computePassEncoder.setBindGroup(0, bindGroup0); - computePassEncoder.setBindGroup(1, bindGroup1); + computePassEncoder.setBindGroup(1, writableStorageTextureBindGroup); + computePassEncoder.setBindGroup(2, bindGroup1); computePassEncoder.setPipeline(computePipeline); computePassEncoder.dispatchWorkgroups(1); computePassEncoder.end(); } - // In WebGPU SPEC (Chapter 3.4.5, Synchronization): + // In WebGPU SPEC (https://gpuweb.github.io/gpuweb/#programming-model-synchronization): // This specification defines the following usage scopes: // - In a compute pass, each dispatch command (dispatchWorkgroups() or // dispatchWorkgroupsIndirect()) is one usage scope. A subresource is "used" in the usage @@ -312,7 +452,11 @@ g.test('subresources,set_unused_bind_group') // referenced by any (state-setting or non-state-setting) command. For example, in // setBindGroup(index, bindGroup, dynamicOffsets), every subresource in bindGroup is "used" in // the render pass’s usage scope. - const success = !inRenderPass || !hasConflict; + const success = + !inRenderPass || + (IsReadOnlyTextureBindingType(textureUsage0) && + IsReadOnlyTextureBindingType(textureUsage1)) || + textureUsage0 === textureUsage1; t.expectValidationError(() => { encoder.finish(); }, !success); @@ -330,16 +474,14 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') .combine('usage0', [ 'copy-src', 'copy-dst', - 'texture', - 'storage', 'color-attachment', + ...kTextureBindingTypes, ] as const) .combine('usage1', [ 'copy-src', 'copy-dst', - 'texture', - 'storage', 'color-attachment', + ...kTextureBindingTypes, ] as const) .filter( ({ usage0, usage1 }) => @@ -353,7 +495,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') const { usage0, usage1 } = t.params; const texture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.COPY_SRC | GPUTextureUsage.COPY_DST | @@ -368,7 +510,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') const UseTextureOnCommandEncoder = ( texture: GPUTexture, - usage: 'copy-src' | 'copy-dst' | 'texture' | 'storage' | 'color-attachment', + usage: 'copy-src' | 'copy-dst' | 'color-attachment' | TextureBindingType, encoder: GPUCommandEncoder ) => { switch (usage) { @@ -395,10 +537,12 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') renderPassEncoder.end(); break; } - case 'texture': - case 'storage': { + case 'sampled-texture': + case 'readonly-storage-texture': + case 'writeonly-storage-texture': + case 'readwrite-storage-texture': { const colorTexture = t.device.createTexture({ - format: 'rgba8unorm', + format: 'r32float', usage: GPUTextureUsage.RENDER_ATTACHMENT, size: [kTextureSize, kTextureSize, 1], }); @@ -412,7 +556,7 @@ g.test('subresources,texture_usages_in_copy_and_render_pass') dimension: '2d-array', }), usage, - 'float' + 'unfilterable-float' ); renderPassEncoder.setBindGroup(0, bindGroup); renderPassEncoder.end();