Skip to content

Commit 7a6ef73

Browse files
authored
Add readonly and readwrite storage textures as bindable resources (#3219)
* Add readonly, writeonly and readwrite storage texture as bindable resources This patch adds `readonlyStorageTex`, `writeonlyStorageTex` and `readwriteStorageTex` as `ValidBinableResource` and updates all the related tests to support them. * Small fix * Use r32float for all storage textures
1 parent 3cbe1f7 commit 7a6ef73

File tree

4 files changed

+127
-62
lines changed

4 files changed

+127
-62
lines changed

src/webgpu/api/validation/createBindGroup.spec.ts

+25-7
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { makeTestGroup } from '../../../common/framework/test_group.js';
88
import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js';
99
import {
1010
allBindingEntries,
11+
BindableResource,
1112
bindingTypeInfo,
1213
bufferBindingEntries,
1314
bufferBindingTypeInfo,
@@ -106,7 +107,7 @@ g.test('binding_must_contain_resource_defined_in_layout')
106107
.desc(
107108
'Test that only compatible resource types specified in the BindGroupLayout are allowed for each entry.'
108109
)
109-
.paramsSubcasesOnly(u =>
110+
.params(u =>
110111
u //
111112
.combine('resourceType', kBindableResources)
112113
.combine('entry', allBindingEntries(false))
@@ -121,6 +122,17 @@ g.test('binding_must_contain_resource_defined_in_layout')
121122

122123
const resource = t.getBindingResource(resourceType);
123124

125+
const IsStorageTextureResourceType = (resourceType: BindableResource) => {
126+
switch (resourceType) {
127+
case 'readonlyStorageTex':
128+
case 'readwriteStorageTex':
129+
case 'writeonlyStorageTex':
130+
return true;
131+
default:
132+
return false;
133+
}
134+
};
135+
124136
let resourceBindingIsCompatible;
125137
switch (info.resource) {
126138
// 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')
131143
case 'nonFiltSamp':
132144
resourceBindingIsCompatible = resourceType === 'nonFiltSamp';
133145
break;
146+
case 'readonlyStorageTex':
147+
case 'readwriteStorageTex':
148+
case 'writeonlyStorageTex':
149+
resourceBindingIsCompatible = IsStorageTextureResourceType(resourceType);
150+
break;
134151
default:
135152
resourceBindingIsCompatible = info.resource === resourceType;
136153
break;
@@ -166,7 +183,7 @@ g.test('texture_binding_must_have_correct_usage')
166183

167184
const descriptor = {
168185
size: { width: 16, height: 16, depthOrArrayLayers: 1 },
169-
format: 'rgba8unorm' as const,
186+
format: 'r32float' as const,
170187
usage: appliedUsage,
171188
sampleCount: info.resource === 'sampledTexMS' ? 4 : 1,
172189
};
@@ -539,9 +556,7 @@ g.test('buffer,resource_state')
539556
g.test('texture,resource_state')
540557
.desc('Test bind group creation with various texture resource states')
541558
.paramsSubcasesOnly(u =>
542-
u
543-
.combine('state', kResourceStates)
544-
.combine('entry', sampledAndStorageBindingEntries(true, 'rgba8unorm'))
559+
u.combine('state', kResourceStates).combine('entry', sampledAndStorageBindingEntries(true))
545560
)
546561
.fn(t => {
547562
const { state, entry } = t.params;
@@ -561,10 +576,11 @@ g.test('texture,resource_state')
561576
const usage = entry.texture?.multisampled
562577
? info.usage | GPUConst.TextureUsage.RENDER_ATTACHMENT
563578
: info.usage;
579+
const format = entry.storageTexture !== undefined ? 'r32float' : 'rgba8unorm';
564580
const texture = t.createTextureWithState(state, {
565581
usage,
566582
size: [1, 1],
567-
format: 'rgba8unorm',
583+
format,
568584
sampleCount: entry.texture?.multisampled ? 4 : 1,
569585
});
570586

@@ -639,7 +655,9 @@ g.test('binding_resources,device_mismatch')
639655
{ buffer: { type: 'storage' } },
640656
{ sampler: { type: 'filtering' } },
641657
{ texture: { multisampled: false } },
642-
{ storageTexture: { access: 'write-only', format: 'rgba8unorm' } },
658+
{ storageTexture: { access: 'write-only', format: 'r32float' } },
659+
{ storageTexture: { access: 'read-only', format: 'r32float' } },
660+
{ storageTexture: { access: 'read-write', format: 'r32float' } },
643661
] as const)
644662
.beginSubcases()
645663
.combineWithParams([

src/webgpu/api/validation/encoding/programmable/pipeline_bind_group_compat.spec.ts

+22-4
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,9 @@ const kResourceTypes: ValidBindableResource[] = [
3838
'uniformBuf',
3939
'filtSamp',
4040
'sampledTex',
41-
'storageTex',
41+
'readonlyStorageTex',
42+
'writeonlyStorageTex',
43+
'readwriteStorageTex',
4244
];
4345

4446
function getTestCmds(
@@ -75,7 +77,17 @@ class F extends ValidationTest {
7577
if (entry.buffer !== undefined) return 'uniformBuf';
7678
if (entry.sampler !== undefined) return 'filtSamp';
7779
if (entry.texture !== undefined) return 'sampledTex';
78-
if (entry.storageTexture !== undefined) return 'storageTex';
80+
if (entry.storageTexture !== undefined) {
81+
switch (entry.storageTexture.access) {
82+
case undefined:
83+
case 'write-only':
84+
return 'writeonlyStorageTex';
85+
case 'read-only':
86+
return 'readonlyStorageTex';
87+
case 'read-write':
88+
return 'readwriteStorageTex';
89+
}
90+
}
7991
unreachable();
8092
}
8193

@@ -208,8 +220,14 @@ class F extends ValidationTest {
208220
case 'sampledTex':
209221
entry.texture = {}; // default sampleType: float
210222
break;
211-
case 'storageTex':
212-
entry.storageTexture = { access: 'write-only', format: 'rgba8unorm' };
223+
case 'readonlyStorageTex':
224+
entry.storageTexture = { access: 'read-only', format: 'r32float' };
225+
break;
226+
case 'writeonlyStorageTex':
227+
entry.storageTexture = { access: 'write-only', format: 'r32float' };
228+
break;
229+
case 'readwriteStorageTex':
230+
entry.storageTexture = { access: 'read-write', format: 'r32float' };
213231
break;
214232
}
215233

src/webgpu/api/validation/validation_test.ts

+12-8
Original file line numberDiff line numberDiff line change
@@ -152,11 +152,11 @@ export class ValidationTest extends GPUTest {
152152
}
153153

154154
/** Return an arbitrarily-configured GPUTexture with the `STORAGE_BINDING` usage. */
155-
getStorageTexture(): GPUTexture {
155+
getStorageTexture(format: GPUTextureFormat): GPUTexture {
156156
return this.trackForCleanup(
157157
this.device.createTexture({
158158
size: { width: 16, height: 16, depthOrArrayLayers: 1 },
159-
format: 'rgba8unorm',
159+
format,
160160
usage: GPUTextureUsage.STORAGE_BINDING,
161161
})
162162
);
@@ -220,8 +220,10 @@ export class ValidationTest extends GPUTest {
220220
return this.getSampledTexture(1).createView();
221221
case 'sampledTexMS':
222222
return this.getSampledTexture(4).createView();
223-
case 'storageTex':
224-
return this.getStorageTexture().createView();
223+
case 'readonlyStorageTex':
224+
case 'writeonlyStorageTex':
225+
case 'readwriteStorageTex':
226+
return this.getStorageTexture('r32float').createView();
225227
}
226228
}
227229

@@ -255,10 +257,10 @@ export class ValidationTest extends GPUTest {
255257
}
256258

257259
/** Return an arbitrarily-configured GPUTexture with the `STORAGE` usage from mismatched device. */
258-
getDeviceMismatchedStorageTexture(): GPUTexture {
260+
getDeviceMismatchedStorageTexture(format: GPUTextureFormat): GPUTexture {
259261
return this.getDeviceMismatchedTexture({
260262
size: { width: 4, height: 4, depthOrArrayLayers: 1 },
261-
format: 'rgba8unorm',
263+
format,
262264
usage: GPUTextureUsage.STORAGE_BINDING,
263265
});
264266
}
@@ -289,8 +291,10 @@ export class ValidationTest extends GPUTest {
289291
return this.getDeviceMismatchedSampledTexture(1).createView();
290292
case 'sampledTexMS':
291293
return this.getDeviceMismatchedSampledTexture(4).createView();
292-
case 'storageTex':
293-
return this.getDeviceMismatchedStorageTexture().createView();
294+
case 'readonlyStorageTex':
295+
case 'writeonlyStorageTex':
296+
case 'readwriteStorageTex':
297+
return this.getDeviceMismatchedStorageTexture('r32float').createView();
294298
}
295299
}
296300

src/webgpu/capability_info.ts

+68-43
Original file line numberDiff line numberDiff line change
@@ -322,7 +322,9 @@ export type PerStageBindingLimitClass =
322322
| 'storageBuf'
323323
| 'sampler'
324324
| 'sampledTex'
325-
| 'storageTex';
325+
| 'readonlyStorageTex'
326+
| 'writeonlyStorageTex'
327+
| 'readwriteStorageTex';
326328
/**
327329
* Classes of `PerPipelineLayout` binding limits. Two bindings with the same class
328330
* count toward the same `PerPipelineLayout` limit(s) in the spec (if any).
@@ -337,7 +339,9 @@ export type ValidBindableResource =
337339
| 'compareSamp'
338340
| 'sampledTex'
339341
| 'sampledTexMS'
340-
| 'storageTex';
342+
| 'readonlyStorageTex'
343+
| 'writeonlyStorageTex'
344+
| 'readwriteStorageTex';
341345
type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex';
342346

343347
/**
@@ -353,7 +357,9 @@ export const kBindableResources = [
353357
'compareSamp',
354358
'sampledTex',
355359
'sampledTexMS',
356-
'storageTex',
360+
'readonlyStorageTex',
361+
'writeonlyStorageTex',
362+
'readwriteStorageTex',
357363
'errorBuf',
358364
'errorSamp',
359365
'errorTex',
@@ -376,11 +382,13 @@ export const kPerStageBindingLimits: {
376382
};
377383
} =
378384
/* prettier-ignore */ {
379-
'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', },
380-
'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', },
381-
'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', },
382-
'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', },
383-
'storageTex': { class: 'storageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
385+
'uniformBuf': { class: 'uniformBuf', maxLimit: 'maxUniformBuffersPerShaderStage', },
386+
'storageBuf': { class: 'storageBuf', maxLimit: 'maxStorageBuffersPerShaderStage', },
387+
'sampler': { class: 'sampler', maxLimit: 'maxSamplersPerShaderStage', },
388+
'sampledTex': { class: 'sampledTex', maxLimit: 'maxSampledTexturesPerShaderStage', },
389+
'readonlyStorageTex': { class: 'readonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
390+
'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
391+
'readwriteStorageTex': { class: 'readwriteStorageTex', maxLimit: 'maxStorageTexturesPerShaderStage', },
384392
};
385393

386394
/**
@@ -398,11 +406,13 @@ export const kPerPipelineBindingLimits: {
398406
};
399407
} =
400408
/* prettier-ignore */ {
401-
'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', },
402-
'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', },
403-
'sampler': { class: 'sampler', maxDynamicLimit: '', },
404-
'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', },
405-
'storageTex': { class: 'storageTex', maxDynamicLimit: '', },
409+
'uniformBuf': { class: 'uniformBuf', maxDynamicLimit: 'maxDynamicUniformBuffersPerPipelineLayout', },
410+
'storageBuf': { class: 'storageBuf', maxDynamicLimit: 'maxDynamicStorageBuffersPerPipelineLayout', },
411+
'sampler': { class: 'sampler', maxDynamicLimit: '', },
412+
'sampledTex': { class: 'sampledTex', maxDynamicLimit: '', },
413+
'readonlyStorageTex': { class: 'readonlyStorageTex', maxDynamicLimit: '', },
414+
'writeonlyStorageTex': { class: 'writeonlyStorageTex', maxDynamicLimit: '', },
415+
'readwriteStorageTex': { class: 'readwriteStorageTex', maxDynamicLimit: '', },
406416
};
407417

408418
interface BindingKindInfo {
@@ -416,14 +426,16 @@ const kBindingKind: {
416426
readonly [k in ValidBindableResource]: BindingKindInfo;
417427
} =
418428
/* prettier-ignore */ {
419-
uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, },
420-
storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, },
421-
filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
422-
nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
423-
compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
424-
sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
425-
sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
426-
storageTex: { resource: 'storageTex', perStageLimitClass: kPerStageBindingLimits.storageTex, perPipelineLimitClass: kPerPipelineBindingLimits.storageTex, },
429+
uniformBuf: { resource: 'uniformBuf', perStageLimitClass: kPerStageBindingLimits.uniformBuf, perPipelineLimitClass: kPerPipelineBindingLimits.uniformBuf, },
430+
storageBuf: { resource: 'storageBuf', perStageLimitClass: kPerStageBindingLimits.storageBuf, perPipelineLimitClass: kPerPipelineBindingLimits.storageBuf, },
431+
filtSamp: { resource: 'filtSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
432+
nonFiltSamp: { resource: 'nonFiltSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
433+
compareSamp: { resource: 'compareSamp', perStageLimitClass: kPerStageBindingLimits.sampler, perPipelineLimitClass: kPerPipelineBindingLimits.sampler, },
434+
sampledTex: { resource: 'sampledTex', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
435+
sampledTexMS: { resource: 'sampledTexMS', perStageLimitClass: kPerStageBindingLimits.sampledTex, perPipelineLimitClass: kPerPipelineBindingLimits.sampledTex, },
436+
readonlyStorageTex: { resource: 'readonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.readonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readonlyStorageTex, },
437+
writeonlyStorageTex: { resource: 'writeonlyStorageTex', perStageLimitClass: kPerStageBindingLimits.writeonlyStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.writeonlyStorageTex, },
438+
readwriteStorageTex: { resource: 'readwriteStorageTex', perStageLimitClass: kPerStageBindingLimits.readwriteStorageTex, perPipelineLimitClass: kPerPipelineBindingLimits.readwriteStorageTex, },
427439
};
428440

429441
// Binding type info
@@ -483,11 +495,27 @@ assertTypeTrue<TypeEqual<GPUTextureSampleType, (typeof kTextureSampleTypes)[numb
483495

484496
/** Binding type info (including class limits) for the specified GPUStorageTextureBindingLayout. */
485497
export function storageTextureBindingTypeInfo(d: GPUStorageTextureBindingLayout) {
486-
return {
487-
usage: GPUConst.TextureUsage.STORAGE_BINDING,
488-
...kBindingKind.storageTex,
489-
...kValidStagesStorageWrite,
490-
};
498+
switch (d.access) {
499+
case undefined:
500+
case 'write-only':
501+
return {
502+
usage: GPUConst.TextureUsage.STORAGE_BINDING,
503+
...kBindingKind.writeonlyStorageTex,
504+
...kValidStagesStorageWrite,
505+
};
506+
case 'read-only':
507+
return {
508+
usage: GPUConst.TextureUsage.STORAGE_BINDING,
509+
...kBindingKind.readonlyStorageTex,
510+
...kValidStagesAll,
511+
};
512+
case 'read-write':
513+
return {
514+
usage: GPUConst.TextureUsage.STORAGE_BINDING,
515+
...kBindingKind.readwriteStorageTex,
516+
...kValidStagesStorageWrite,
517+
};
518+
}
491519
}
492520
/** List of all GPUStorageTextureAccess values. */
493521
export const kStorageTextureAccessValues = ['read-only', 'read-write', 'write-only'] as const;
@@ -539,8 +567,10 @@ export function samplerBindingEntries(includeUndefined: boolean): readonly BGLEn
539567
*/
540568
export function textureBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
541569
return [
542-
...(includeUndefined ? [{ texture: { multisampled: undefined } }] : []),
543-
{ texture: { multisampled: false } },
570+
...(includeUndefined
571+
? [{ texture: { multisampled: undefined, sampleType: 'unfilterable-float' } } as const]
572+
: []),
573+
{ texture: { multisampled: false, sampleType: 'unfilterable-float' } },
544574
{ texture: { multisampled: true, sampleType: 'unfilterable-float' } },
545575
] as const;
546576
}
@@ -549,34 +579,29 @@ export function textureBindingEntries(includeUndefined: boolean): readonly BGLEn
549579
*
550580
* Note: Generates different `access` options, but not `format` or `viewDimension` options.
551581
*/
552-
export function storageTextureBindingEntries(format: GPUTextureFormat): readonly BGLEntry[] {
553-
return [{ storageTexture: { access: 'write-only', format } }] as const;
554-
}
555-
/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
556-
export function sampledAndStorageBindingEntries(
557-
includeUndefined: boolean,
558-
storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
559-
): readonly BGLEntry[] {
582+
export function storageTextureBindingEntries(): readonly BGLEntry[] {
560583
return [
561-
...textureBindingEntries(includeUndefined),
562-
...storageTextureBindingEntries(storageTextureFormat),
584+
{ storageTexture: { access: 'write-only', format: 'r32float' } },
585+
{ storageTexture: { access: 'read-only', format: 'r32float' } },
586+
{ storageTexture: { access: 'read-write', format: 'r32float' } },
563587
] as const;
564588
}
589+
/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
590+
export function sampledAndStorageBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
591+
return [...textureBindingEntries(includeUndefined), ...storageTextureBindingEntries()] as const;
592+
}
565593
/**
566594
* Generate a list of possible BGLEntry values of every type, but not variants with different:
567595
* - buffer.hasDynamicOffset
568596
* - texture.sampleType
569597
* - texture.viewDimension
570598
* - storageTexture.viewDimension
571599
*/
572-
export function allBindingEntries(
573-
includeUndefined: boolean,
574-
storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
575-
): readonly BGLEntry[] {
600+
export function allBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
576601
return [
577602
...bufferBindingEntries(includeUndefined),
578603
...samplerBindingEntries(includeUndefined),
579-
...sampledAndStorageBindingEntries(includeUndefined, storageTextureFormat),
604+
...sampledAndStorageBindingEntries(includeUndefined),
580605
] as const;
581606
}
582607

0 commit comments

Comments
 (0)