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
diff --git a/src/webgpu/api/validation/createBindGroup.spec.ts b/src/webgpu/api/validation/createBindGroup.spec.ts
index c195134..75a545a 100644
--- a/src/webgpu/api/validation/createBindGroup.spec.ts
+++ b/src/webgpu/api/validation/createBindGroup.spec.ts
@@ -8,6 +8,7 @@
import { assert, makeValueTestVariant, unreachable } from '../../../common/util/util.js';
import {
allBindingEntries,
+ BindableResource,
bindingTypeInfo,
bufferBindingEntries,
bufferBindingTypeInfo,
@@ -106,7 +107,7 @@
.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 @@
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 @@
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 @@
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('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 @@
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 @@
{ 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 163c20c..bef3396 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 @@
'uniformBuf',
'filtSamp',
'sampledTex',
- 'storageTex',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
];
function getTestCmds(
@@ -75,7 +77,17 @@
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 @@
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 1be0866..6e6802d 100644
--- a/src/webgpu/api/validation/validation_test.ts
+++ b/src/webgpu/api/validation/validation_test.ts
@@ -152,11 +152,11 @@
}
/** 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 @@
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 @@
}
/** 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 @@
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 d7fe718..1bd5d3b 100644
--- a/src/webgpu/capability_info.ts
+++ b/src/webgpu/capability_info.ts
@@ -322,7 +322,9 @@
| '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 @@
| 'compareSamp'
| 'sampledTex'
| 'sampledTexMS'
- | 'storageTex';
+ | 'readonlyStorageTex'
+ | 'writeonlyStorageTex'
+ | 'readwriteStorageTex';
type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex';
/**
@@ -353,7 +357,9 @@
'compareSamp',
'sampledTex',
'sampledTexMS',
- 'storageTex',
+ 'readonlyStorageTex',
+ 'writeonlyStorageTex',
+ 'readwriteStorageTex',
'errorBuf',
'errorSamp',
'errorTex',
@@ -376,11 +382,13 @@
};
} =
/* 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 @@
};
} =
/* 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 @@
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 @@
/** Binding type info (including class limits) for the specified GPUStorageTextureBindingLayout. */
export function storageTextureBindingTypeInfo(d: GPUStorageTextureBindingLayout) {
- return {
- usage: GPUConst.TextureUsage.STORAGE_BINDING,
- ...kBindingKind.storageTex,
- ...kValidStagesStorageWrite,
- };
+ switch (d.access) {
+ case undefined:
+ case 'write-only':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.writeonlyStorageTex,
+ ...kValidStagesStorageWrite,
+ };
+ case 'read-only':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.readonlyStorageTex,
+ ...kValidStagesAll,
+ };
+ case 'read-write':
+ return {
+ usage: GPUConst.TextureUsage.STORAGE_BINDING,
+ ...kBindingKind.readwriteStorageTex,
+ ...kValidStagesStorageWrite,
+ };
+ }
}
/** List of all GPUStorageTextureAccess values. */
export const kStorageTextureAccessValues = ['read-only', 'read-write', 'write-only'] as const;
@@ -539,8 +567,10 @@
*/
export function textureBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
return [
- ...(includeUndefined ? [{ texture: { multisampled: undefined } }] : []),
- { texture: { multisampled: false } },
+ ...(includeUndefined
+ ? [{ texture: { multisampled: undefined, sampleType: 'unfilterable-float' } } as const]
+ : []),
+ { texture: { multisampled: false, sampleType: 'unfilterable-float' } },
{ texture: { multisampled: true, sampleType: 'unfilterable-float' } },
] as const;
}
@@ -549,18 +579,16 @@
*
* Note: Generates different `access` options, but not `format` or `viewDimension` options.
*/
-export function storageTextureBindingEntries(format: GPUTextureFormat): readonly BGLEntry[] {
- return [{ storageTexture: { access: 'write-only', format } }] as const;
+export function storageTextureBindingEntries(): readonly BGLEntry[] {
+ return [
+ { storageTexture: { access: 'write-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-only', format: 'r32float' } },
+ { storageTexture: { access: 'read-write', format: 'r32float' } },
+ ] as const;
}
/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
-export function sampledAndStorageBindingEntries(
- includeUndefined: boolean,
- storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
-): readonly BGLEntry[] {
- return [
- ...textureBindingEntries(includeUndefined),
- ...storageTextureBindingEntries(storageTextureFormat),
- ] as const;
+export function sampledAndStorageBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
+ return [...textureBindingEntries(includeUndefined), ...storageTextureBindingEntries()] as const;
}
/**
* Generate a list of possible BGLEntry values of every type, but not variants with different:
@@ -569,14 +597,11 @@
* - texture.viewDimension
* - storageTexture.viewDimension
*/
-export function allBindingEntries(
- includeUndefined: boolean,
- storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
-): readonly BGLEntry[] {
+export function allBindingEntries(includeUndefined: boolean): readonly BGLEntry[] {
return [
...bufferBindingEntries(includeUndefined),
...samplerBindingEntries(includeUndefined),
- ...sampledAndStorageBindingEntries(includeUndefined, storageTextureFormat),
+ ...sampledAndStorageBindingEntries(includeUndefined),
] as const;
}