diff --git a/src/webgpu/api/validation/createBindGroup.spec.ts b/src/webgpu/api/validation/createBindGroup.spec.ts index c1951348ce1d..75a545aeb5ee 100644 --- a/src/webgpu/api/validation/createBindGroup.spec.ts +++ b/src/webgpu/api/validation/createBindGroup.spec.ts @@ -8,6 +8,7 @@ import { makeTestGroup } from '../../../common/framework/test_group.js'; import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js'; import { allBindingEntries, + BindableResource, bindingTypeInfo, bufferBindingEntries, bufferBindingTypeInfo, @@ -106,7 +107,7 @@ g.test('binding_must_contain_resource_defined_in_layout') .desc( 'Test that only compatible resource types specified in the BindGroupLayout are allowed for each entry.' ) - .paramsSubcasesOnly(u => + .params(u => u // .combine('resourceType', kBindableResources) .combine('entry', allBindingEntries(false)) @@ -121,6 +122,17 @@ g.test('binding_must_contain_resource_defined_in_layout') const resource = t.getBindingResource(resourceType); + const IsStorageTextureResourceType = (resourceType: BindableResource) => { + switch (resourceType) { + case 'readonlyStorageTex': + case 'readwriteStorageTex': + case 'writeonlyStorageTex': + return true; + default: + return false; + } + }; + let resourceBindingIsCompatible; switch (info.resource) { // Either type of sampler may be bound to a filtering sampler binding. @@ -131,6 +143,11 @@ g.test('binding_must_contain_resource_defined_in_layout') case 'nonFiltSamp': resourceBindingIsCompatible = resourceType === 'nonFiltSamp'; break; + case 'readonlyStorageTex': + case 'readwriteStorageTex': + case 'writeonlyStorageTex': + resourceBindingIsCompatible = IsStorageTextureResourceType(resourceType); + break; default: resourceBindingIsCompatible = info.resource === resourceType; break; @@ -166,7 +183,7 @@ g.test('texture_binding_must_have_correct_usage') const descriptor = { size: { width: 16, height: 16, depthOrArrayLayers: 1 }, - format: 'rgba8unorm' as const, + format: 'r32float' as const, usage: appliedUsage, sampleCount: info.resource === 'sampledTexMS' ? 4 : 1, }; @@ -539,9 +556,7 @@ g.test('buffer,resource_state') g.test('texture,resource_state') .desc('Test bind group creation with various texture resource states') .paramsSubcasesOnly(u => - u - .combine('state', kResourceStates) - .combine('entry', sampledAndStorageBindingEntries(true, 'rgba8unorm')) + u.combine('state', kResourceStates).combine('entry', sampledAndStorageBindingEntries(true)) ) .fn(t => { const { state, entry } = t.params; @@ -561,10 +576,11 @@ g.test('texture,resource_state') const usage = entry.texture?.multisampled ? info.usage | GPUConst.TextureUsage.RENDER_ATTACHMENT : info.usage; + const format = entry.storageTexture !== undefined ? 'r32float' : 'rgba8unorm'; const texture = t.createTextureWithState(state, { usage, size: [1, 1], - format: 'rgba8unorm', + format, sampleCount: entry.texture?.multisampled ? 4 : 1, }); @@ -639,7 +655,9 @@ g.test('binding_resources,device_mismatch') { buffer: { type: 'storage' } }, { sampler: { type: 'filtering' } }, { texture: { multisampled: false } }, - { storageTexture: { access: 'write-only', format: 'rgba8unorm' } }, + { storageTexture: { access: 'write-only', format: 'r32float' } }, + { storageTexture: { access: 'read-only', format: 'r32float' } }, + { storageTexture: { access: 'read-write', format: 'r32float' } }, ] as const) .beginSubcases() .combineWithParams([ diff --git a/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts b/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts index 163c20c311e2..bef33968b04f 100644 --- a/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts +++ b/src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts @@ -38,7 +38,9 @@ const kResourceTypes: ValidBindableResource[] = [ 'uniformBuf', 'filtSamp', 'sampledTex', - 'storageTex', + 'readonlyStorageTex', + 'writeonlyStorageTex', + 'readwriteStorageTex', ]; function getTestCmds( @@ -75,7 +77,17 @@ class F extends ValidationTest { if (entry.buffer !== undefined) return 'uniformBuf'; if (entry.sampler !== undefined) return 'filtSamp'; if (entry.texture !== undefined) return 'sampledTex'; - if (entry.storageTexture !== undefined) return 'storageTex'; + if (entry.storageTexture !== undefined) { + switch (entry.storageTexture.access) { + case undefined: + case 'write-only': + return 'writeonlyStorageTex'; + case 'read-only': + return 'readonlyStorageTex'; + case 'read-write': + return 'readwriteStorageTex'; + } + } unreachable(); } @@ -208,8 +220,14 @@ class F extends ValidationTest { case 'sampledTex': entry.texture = {}; // default sampleType: float break; - case 'storageTex': - entry.storageTexture = { access: 'write-only', format: 'rgba8unorm' }; + case 'readonlyStorageTex': + entry.storageTexture = { access: 'read-only', format: 'r32float' }; + break; + case 'writeonlyStorageTex': + entry.storageTexture = { access: 'write-only', format: 'r32float' }; + break; + case 'readwriteStorageTex': + entry.storageTexture = { access: 'read-write', format: 'r32float' }; break; } diff --git a/src/webgpu/api/validation/validation_test.ts b/src/webgpu/api/validation/validation_test.ts index 1be0866e1d60..6e6802d95f03 100644 --- a/src/webgpu/api/validation/validation_test.ts +++ b/src/webgpu/api/validation/validation_test.ts @@ -152,11 +152,11 @@ export class ValidationTest extends GPUTest { } /** Return an arbitrarily-configured GPUTexture with the `STORAGE_BINDING` usage. */ - getStorageTexture(): GPUTexture { + getStorageTexture(format: GPUTextureFormat): GPUTexture { return this.trackForCleanup( this.device.createTexture({ size: { width: 16, height: 16, depthOrArrayLayers: 1 }, - format: 'rgba8unorm', + format, usage: GPUTextureUsage.STORAGE_BINDING, }) ); @@ -220,8 +220,10 @@ export class ValidationTest extends GPUTest { return this.getSampledTexture(1).createView(); case 'sampledTexMS': return this.getSampledTexture(4).createView(); - case 'storageTex': - return this.getStorageTexture().createView(); + case 'readonlyStorageTex': + case 'writeonlyStorageTex': + case 'readwriteStorageTex': + return this.getStorageTexture('r32float').createView(); } } @@ -255,10 +257,10 @@ export class ValidationTest extends GPUTest { } /** Return an arbitrarily-configured GPUTexture with the `STORAGE` usage from mismatched device. */ - getDeviceMismatchedStorageTexture(): GPUTexture { + getDeviceMismatchedStorageTexture(format: GPUTextureFormat): GPUTexture { return this.getDeviceMismatchedTexture({ size: { width: 4, height: 4, depthOrArrayLayers: 1 }, - format: 'rgba8unorm', + format, usage: GPUTextureUsage.STORAGE_BINDING, }); } @@ -289,8 +291,10 @@ export class ValidationTest extends GPUTest { return this.getDeviceMismatchedSampledTexture(1).createView(); case 'sampledTexMS': return this.getDeviceMismatchedSampledTexture(4).createView(); - case 'storageTex': - return this.getDeviceMismatchedStorageTexture().createView(); + case 'readonlyStorageTex': + case 'writeonlyStorageTex': + case 'readwriteStorageTex': + return this.getDeviceMismatchedStorageTexture('r32float').createView(); } } diff --git a/src/webgpu/capability_info.ts b/src/webgpu/capability_info.ts index d7fe7180fd1b..1bd5d3b7c698 100644 --- a/src/webgpu/capability_info.ts +++ b/src/webgpu/capability_info.ts @@ -322,7 +322,9 @@ export type PerStageBindingLimitClass = | 'storageBuf' | 'sampler' | 'sampledTex' - | 'storageTex'; + | 'readonlyStorageTex' + | 'writeonlyStorageTex' + | 'readwriteStorageTex'; /** * Classes of `PerPipelineLayout` binding limits. Two bindings with the same class * count toward the same `PerPipelineLayout` limit(s) in the spec (if any). @@ -337,7 +339,9 @@ export type ValidBindableResource = | 'compareSamp' | 'sampledTex' | 'sampledTexMS' - | 'storageTex'; + | 'readonlyStorageTex' + | 'writeonlyStorageTex' + | 'readwriteStorageTex'; type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex'; /** @@ -353,7 +357,9 @@ export const kBindableResources = [ 'compareSamp', 'sampledTex', 'sampledTexMS', - 'storageTex', + 'readonlyStorageTex', + 'writeonlyStorageTex', + 'readwriteStorageTex', 'errorBuf', 'errorSamp', 'errorTex', @@ -376,11 +382,13 @@ export const kPerStageBindingLimits: { }; } = /* prettier-ignore */ { - 'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', }, - 'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', }, - 'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', }, - 'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', }, - 'storageTex': { class: 'storageTex', maxLimit: 'maxStorageTexturesPerShaderStage', }, + 'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', }, + 'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', }, + 'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', }, + 'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', }, + 'readonlyStorageTex': { class: 'readonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', }, + 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', }, + 'readwriteStorageTex': { class: 'readwriteStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', }, }; /** @@ -398,11 +406,13 @@ export const kPerPipelineBindingLimits: { }; } = /* prettier-ignore */ { - 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', }, - 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', }, - 'sampler': { class: 'sampler', maxDynamicLimit: '', }, - 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', }, - 'storageTex': { class: 'storageTex', maxDynamicLimit: '', }, + 'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', }, + 'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', }, + 'sampler': { class: 'sampler', maxDynamicLimit: '', }, + 'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', }, + 'readonlyStorageTex': { class: 'readonlyStorageTex', maxDynamicLimit: '', }, + 'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxDynamicLimit: '', }, + 'readwriteStorageTex': { class: 'readwriteStorageTex', maxDynamicLimit: '', }, }; interface BindingKindInfo { @@ -416,14 +426,16 @@ const kBindingKind: { readonly [k in ValidBindableResource]: BindingKindInfo; } = /* prettier-ignore */ { - uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, }, - storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, }, - filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, - nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, - compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, - sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, }, - sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, }, - storageTex: { resource: 'storageTex', perStageLimitClass: kPerStageBindingLimits.storageTex, perPipelineLimitClass: kPerPipelineBindingLimits.storageTex, }, + uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, }, + storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, }, + filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, + nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, + compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, }, + sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, }, + sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, }, + readonlyStorageTex: { resource: 'readonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.readonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readonlyStorageTex, }, + writeonlyStorageTex: { resource: 'writeonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.writeonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.writeonlyStorageTex, }, + readwriteStorageTex: { resource: 'readwriteStorageTex', perStageLimitClass: kPerStageBindingLimits.readwriteStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readwriteStorageTex, }, }; // Binding type info @@ -483,11 +495,27 @@ assertTypeTrue