Skip to content

Commit 8e05682

Browse files
authored
Expand the sample_texture_combo test to include storage textures. (#4294)
1 parent 8a6d878 commit 8e05682

File tree

1 file changed

+104
-35
lines changed

1 file changed

+104
-35
lines changed

src/webgpu/api/operation/sampling/sampler_texture.spec.ts

Lines changed: 104 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -16,26 +16,41 @@ g.test('sample_texture_combos')
1616
.desc(
1717
`
1818
Test that you can use the maximum number of textures with the maximum number of samplers.
19+
and the maximum number of storage textures.
1920
20-
The test works by making the maximum number of texture+sampler combos.
21-
Each texture is [maxSamplersPerShaderStage, 1] in size where each texel is [textureId, samplerId]
22-
A function "useCombo<StageNum>(comboId)" is made that returns stage[stageNum].combo[comboId].texel[id, 0]
23-
or to put it another way, it returns the nth texel from the nth combo for that stage.
21+
The test works by making the maximum number of texture+sampler combos and the max storage
22+
textures per stage. Each texture is [maxSamplersPerShaderStage + maxStorageTexturesInStage, 1]
23+
in size and each texel is [textureId, samplerId]. A function "useCombo<StageNum>(comboId)" is
24+
made that returns stage[stageNum].combo[comboId].texel[id, 0] or to put it another way, it
25+
returns the nth texel from the nth combo for that stage.
2426
25-
These are read in both the vertex shader and fragment shader and written to a [maxSamplerPerShaderStage, 2]
26-
texture where the top row is the values from the vertex shader and the bottom row from the fragment shader.
27+
These are read in both the vertex shader and fragment shader and written to a
28+
[maxSamplerPerShaderStage + maxStorageTexturesInStage, 2] texture where the top row is the
29+
values from the vertex shader and the bottom row from the fragment shader.
2730
2831
The result should be a texture that has a value in each texel unique to a particular combo
32+
or storage texture.
2933
`
3034
)
3135
.fn(t => {
3236
const { device } = t;
33-
const { maxSampledTexturesPerShaderStage, maxSamplersPerShaderStage, maxBindingsPerBindGroup } =
34-
device.limits;
37+
const {
38+
maxSampledTexturesPerShaderStage,
39+
maxSamplersPerShaderStage,
40+
maxBindingsPerBindGroup,
41+
maxStorageTexturesInVertexStage,
42+
maxStorageTexturesInFragmentStage,
43+
maxStorageTexturesPerShaderStage,
44+
} = device.limits;
3545

3646
assert(maxSampledTexturesPerShaderStage < 0xfffe);
3747
assert(maxSamplersPerShaderStage < 0xfffe);
3848

49+
const numStorageTexturesInVertexStage =
50+
maxStorageTexturesInVertexStage ?? maxStorageTexturesPerShaderStage;
51+
const numStorageTexturesInFragmentStage =
52+
maxStorageTexturesInFragmentStage ?? maxStorageTexturesPerShaderStage;
53+
3954
const maxTestableCombosPerStage = t.isCompatibility
4055
? Math.min(maxSampledTexturesPerShaderStage, maxSamplersPerShaderStage)
4156
: maxSampledTexturesPerShaderStage * maxSamplersPerShaderStage;
@@ -44,12 +59,17 @@ The result should be a texture that has a value in each texel unique to a partic
4459
const declarationLines: string[] = [];
4560
const groups: GPUBindGroupEntry[][] = [[]];
4661
const layouts: GPUBindGroupLayoutEntry[][] = [[]];
47-
const textureIds = new Set<string>();
62+
const textureIdToTexelValue = new Map<string, number>();
4863
const samplerIds = new Set<string>();
4964
// per stage, per texel, each texel has 2 numbers, the texture id, and sampler id
5065
const expected: number[][][] = [[], []];
5166

52-
function addResource(stage: number, resourceId: string, resource: GPUTextureView | GPUSampler) {
67+
function addResource(
68+
stage: number,
69+
resourceId: string,
70+
resource: GPUTextureView | GPUSampler,
71+
storageTexture?: boolean
72+
) {
5373
let bindGroupEntries = groups[groups.length - 1];
5474
let bindGroupLayoutEntries = layouts[groups.length - 1];
5575
if (bindGroupEntries.length === maxBindingsPerBindGroup) {
@@ -58,7 +78,12 @@ The result should be a texture that has a value in each texel unique to a partic
5878
groups.push(bindGroupEntries);
5979
layouts.push(bindGroupLayoutEntries);
6080
}
61-
const resourceType = resource instanceof GPUSampler ? 'sampler' : 'texture_2d<f32>';
81+
const resourceType =
82+
resource instanceof GPUSampler
83+
? 'sampler'
84+
: storageTexture
85+
? 'texture_storage_2d<rgba8unorm, read>'
86+
: 'texture_2d<f32>';
6287
const binding = bindGroupEntries.length;
6388
declarationLines.push(
6489
` @group(${groups.length - 1}) @binding(${binding}) var ${resourceId}: ${resourceType};`
@@ -74,37 +99,54 @@ The result should be a texture that has a value in each texel unique to a partic
7499
? {
75100
sampler: {},
76101
}
102+
: storageTexture
103+
? {
104+
storageTexture: {
105+
access: 'read-only',
106+
format: 'rgba8unorm',
107+
},
108+
}
77109
: {
78110
texture: {},
79111
}),
80112
});
81113
}
82114

83-
function addTexture(stage: number, textureNum: number) {
115+
const width =
116+
maxSamplersPerShaderStage +
117+
Math.max(numStorageTexturesInVertexStage, numStorageTexturesInFragmentStage);
118+
t.debug(`width: ${width}`);
119+
120+
function addTexture(stage: number, textureNum: number, storageTexture: boolean) {
84121
const textureId = `tex${stage}_${textureNum}`;
85-
if (!textureIds.has(textureId)) {
86-
textureIds.add(textureId);
122+
let texelValue = textureIdToTexelValue.get(textureId);
123+
if (texelValue === undefined) {
124+
texelValue = textures.length + 1;
125+
textureIdToTexelValue.set(textureId, texelValue);
87126
const texture = t.createTextureTracked({
88127
format: 'rgba8unorm',
89-
size: [maxSamplersPerShaderStage, 1],
90-
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
128+
size: [width, 1],
129+
usage:
130+
GPUTextureUsage.STORAGE_BINDING |
131+
GPUTextureUsage.TEXTURE_BINDING |
132+
GPUTextureUsage.COPY_DST,
91133
});
92134
textures.push(texture);
93135
// Encode an rgba8unorm texture with rg16uint data where each texel is
94-
// [(textureId + 1) | (stage << 15), {samplerId + 1}]
136+
// [texelValue | (stage << 15), {samplerId + 1}]
95137
// The +1 is to avoid 0.
96-
const data = new Uint16Array(maxSamplersPerShaderStage * 2);
97-
const rg = (textureNum + 1) | (stage << 15);
98-
for (let x = 0; x < maxSamplersPerShaderStage; ++x) {
138+
const data = new Uint16Array(width * 2);
139+
const rg = texelValue | (stage << 15);
140+
for (let x = 0; x < width; ++x) {
99141
const offset = x * 2;
100-
const samplerNum = x + 1;
142+
const samplerNum = (x % maxSamplersPerShaderStage) + 1;
101143
data[offset + 0] = rg;
102-
data[offset + 1] = samplerNum;
144+
data[offset + 1] = storageTexture ? 0 : samplerNum;
103145
}
104-
device.queue.writeTexture({ texture }, data, {}, [maxSamplersPerShaderStage]);
105-
addResource(stage, textureId, texture.createView());
146+
device.queue.writeTexture({ texture }, data, {}, [width]);
147+
addResource(stage, textureId, texture.createView(), storageTexture);
106148
}
107-
return textureId;
149+
return { textureId, texelValue };
108150
}
109151

110152
const kAddressModes = ['repeat', 'clamp-to-edge', 'mirror-repeat'] as const;
@@ -135,32 +177,52 @@ The result should be a texture that has a value in each texel unique to a partic
135177
return samplerId;
136178
}
137179

180+
const numStorageTexturesInStage = [
181+
numStorageTexturesInVertexStage,
182+
numStorageTexturesInFragmentStage,
183+
];
184+
138185
// Note: We are storing textureId, samplerId in the texture. That suggests we could use rgba32uint
139186
// texture but we can't do that because we want to be able to set the samplers to linear.
140187
// Similarly we can't use rgba32float since they're not filterable by default.
141188
// So, we encode via rgba8unorm where rg is a 16bit textureId and ba is a 16bit samplerId
142189
const code = `
143190
// maxTestableCombosPerStage: ${maxTestableCombosPerStage}
191+
// numStorageTexturesPerVertexStage: ${numStorageTexturesInVertexStage}
192+
// numStorageTexturesPerFragmentStage: ${numStorageTexturesInFragmentStage}
144193
145194
fn sample(t: texture_2d<f32>, s: sampler, validId: u32, currentId: u32, c: vec4f) -> vec4f {
146195
let size = textureDimensions(t, 0);
147-
let uv = vec2f((f32(currentId) + 0.5) / f32(size.x), 0.5);
196+
let uv = vec2f((f32(currentId % ${maxSamplersPerShaderStage}) + 0.5) / f32(size.x), 0.5);
148197
let v = textureSampleLevel(t, s, uv, 0);
149198
return select(c, v, currentId == validId);
150199
}
151200
201+
fn load(t: texture_storage_2d<rgba8unorm, read>, validId: u32, currentId: u32, c: vec4f) -> vec4f {
202+
let size = textureDimensions(t);
203+
let uv = vec2u(currentId % size.x, 0);
204+
let v = textureLoad(t, uv);
205+
return select(c, v, currentId == validId);
206+
}
207+
152208
${range(
153209
2,
154210
stage => `
155211
fn useCombos${stage}(id: u32) -> vec4f {
156212
var c: vec4f;
157213
${range(maxTestableCombosPerStage, i => {
158214
const texNum = (i / maxSamplersPerShaderStage) | 0;
159-
const textureId = addTexture(stage, texNum);
215+
const { textureId, texelValue } = addTexture(stage, texNum, false);
160216
const smpNum = i % maxSamplersPerShaderStage;
161217
const samplerId = addSampler(stage, smpNum);
162-
expected[stage].push([(texNum + 1) | (stage << 15), smpNum + 1]);
218+
expected[stage].push([texelValue | (stage << 15), smpNum + 1]);
163219
return ` c = sample(${textureId}, ${samplerId}, ${i}, id, c);`;
220+
}).join('\n')}
221+
${range(numStorageTexturesInStage[stage], i => {
222+
const texNum = textures.length;
223+
const { textureId, texelValue } = addTexture(stage, texNum, true);
224+
expected[stage].push([texelValue | (stage << 15), 0]);
225+
return ` c = load(${textureId}, ${i + maxTestableCombosPerStage}, id, c);`;
164226
}).join('\n')}
165227
return c;
166228
}
@@ -192,6 +254,8 @@ ${declarationLines.join('\n')}
192254
}
193255
`;
194256

257+
t.debug(code);
258+
195259
const module = device.createShaderModule({ code });
196260
const bindGroupLayouts = layouts.map(entries => device.createBindGroupLayout({ entries }));
197261

@@ -214,9 +278,14 @@ ${declarationLines.join('\n')}
214278
})
215279
);
216280

281+
const numAcross =
282+
maxTestableCombosPerStage +
283+
numStorageTexturesInVertexStage +
284+
numStorageTexturesInFragmentStage;
285+
217286
const renderTarget = t.createTextureTracked({
218287
format: 'rg16uint',
219-
size: [maxTestableCombosPerStage, 2],
288+
size: [numAcross, 2],
220289
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
221290
});
222291
textures.push(renderTarget);
@@ -234,7 +303,7 @@ ${declarationLines.join('\n')}
234303
pass.setPipeline(pipeline);
235304
bindGroups.forEach((bindGroup, i) => pass.setBindGroup(i, bindGroup));
236305
for (let y = 0; y < 2; ++y) {
237-
for (let x = 0; x < maxTestableCombosPerStage; ++x) {
306+
for (let x = 0; x < numAcross; ++x) {
238307
pass.setViewport(x, y, 1, 1, 0, 1);
239308
pass.draw(1, 1, 0, x);
240309
}
@@ -243,10 +312,10 @@ ${declarationLines.join('\n')}
243312

244313
device.queue.submit([encoder.finish()]);
245314

246-
const expectedData = new Uint16Array(maxTestableCombosPerStage * 2 * 2);
315+
const expectedData = new Uint16Array(numAcross * 2 * 2);
247316
for (let stage = 0; stage < 2; ++stage) {
248317
expected[stage].forEach(([tid, sid], i) => {
249-
const offset = (maxTestableCombosPerStage * stage + i) * 2;
318+
const offset = (numAcross * stage + i) * 2;
250319
expectedData[offset + 0] = tid;
251320
expectedData[offset + 1] = sid;
252321
});
@@ -256,14 +325,14 @@ ${declarationLines.join('\n')}
256325
'rg16uint',
257326
new Uint8Array(expectedData.buffer),
258327
{
259-
bytesPerRow: maxTestableCombosPerStage * 4,
328+
bytesPerRow: numAcross * 4,
260329
rowsPerImage: 2,
261330
subrectOrigin: [0, 0, 0],
262-
subrectSize: [maxTestableCombosPerStage, 2],
331+
subrectSize: [numAcross, 2],
263332
}
264333
);
265334

266-
const size = [maxSamplersPerShaderStage, 2];
335+
const size = [numAcross, 2];
267336
t.expectTexelViewComparisonIsOkInTexture({ texture: renderTarget }, expTexelView, size);
268337

269338
textures.forEach(texture => texture.destroy());

0 commit comments

Comments
 (0)