Skip to content

Commit

Permalink
Add readonly and readwrite storage textures as bindable resources (#3219
Browse files Browse the repository at this point in the history
)

* 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
  • Loading branch information
Jiawei-Shao authored Dec 11, 2023
1 parent 3cbe1f7 commit 7a6ef73
Show file tree
Hide file tree
Showing 4 changed files with 127 additions and 62 deletions.
32 changes: 25 additions & 7 deletions src/webgpu/api/validation/createBindGroup.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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))
Expand All @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -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,
};
Expand Down Expand Up @@ -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;
Expand All @@ -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,
});

Expand Down Expand Up @@ -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([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ const kResourceTypes: ValidBindableResource[] = [
'uniformBuf',
'filtSamp',
'sampledTex',
'storageTex',
'readonlyStorageTex',
'writeonlyStorageTex',
'readwriteStorageTex',
];

function getTestCmds(
Expand Down Expand Up @@ -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();
}

Expand Down Expand Up @@ -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;
}

Expand Down
20 changes: 12 additions & 8 deletions src/webgpu/api/validation/validation_test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
})
);
Expand Down Expand Up @@ -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();
}
}

Expand Down Expand Up @@ -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,
});
}
Expand Down Expand Up @@ -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();
}
}

Expand Down
111 changes: 68 additions & 43 deletions src/webgpu/capability_info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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).
Expand All @@ -337,7 +339,9 @@ export type ValidBindableResource =
| 'compareSamp'
| 'sampledTex'
| 'sampledTexMS'
| 'storageTex';
| 'readonlyStorageTex'
| 'writeonlyStorageTex'
| 'readwriteStorageTex';
type ErrorBindableResource = 'errorBuf' | 'errorSamp' | 'errorTex';

/**
Expand All @@ -353,7 +357,9 @@ export const kBindableResources = [
'compareSamp',
'sampledTex',
'sampledTexMS',
'storageTex',
'readonlyStorageTex',
'writeonlyStorageTex',
'readwriteStorageTex',
'errorBuf',
'errorSamp',
'errorTex',
Expand All @@ -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', },
};

/**
Expand All @@ -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 {
Expand All @@ -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
Expand Down Expand Up @@ -483,11 +495,27 @@ assertTypeTrue<TypeEqual<GPUTextureSampleType, (typeof kTextureSampleTypes)[numb

/** 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;
Expand Down Expand Up @@ -539,8 +567,10 @@ export function samplerBindingEntries(includeUndefined: boolean): readonly BGLEn
*/
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;
}
Expand All @@ -549,34 +579,29 @@ export function textureBindingEntries(includeUndefined: boolean): readonly BGLEn
*
* 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;
}
/** Generate a list of possible texture-or-storageTexture-typed BGLEntry values. */
export function sampledAndStorageBindingEntries(
includeUndefined: boolean,
storageTextureFormat: GPUTextureFormat = 'rgba8unorm'
): readonly BGLEntry[] {
export function storageTextureBindingEntries(): readonly BGLEntry[] {
return [
...textureBindingEntries(includeUndefined),
...storageTextureBindingEntries(storageTextureFormat),
{ 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): readonly BGLEntry[] {
return [...textureBindingEntries(includeUndefined), ...storageTextureBindingEntries()] as const;
}
/**
* Generate a list of possible BGLEntry values of every type, but not variants with different:
* - buffer.hasDynamicOffset
* - texture.sampleType
* - 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;
}

Expand Down

0 comments on commit 7a6ef73

Please sign in to comment.