Skip to content

Commit dd2d76b

Browse files
committed
Compat: refactor state_tracking test for 0 frag buffers.
This is a first attempt. Feel free to push back and/or give ideas. The original tests use 2 read-only-storage buffers and 1 read-write storage buffer. Each has a single i32 in it and generally they substract the first 2 from the 2nd. Storage buffers in the fragment stage might not exist on some compat devices so the question is how to work around that and still test. This solution is to add subcases, `storage` and `uniform`. The `storage` case is unchanged. The compute pass case will run in compat always. The render pass and render bundle cases only run in compat if the device supports storage buffers in the fragment stage. The uniform cases use 2 uniform buffers and render to a single pixel r32sint texture. They then copy that texture to the `out` buffer that the original test was checking. This path needs no storage buffers in the fragment shader and so always runs. This works but it's effectively only checking 2 bindings, not 3. So, the question is, should I add 3rd buffer and change the algo to out = a - b - c etc.... so that we can shuffle more bindings? Or is this good enough? Or should I do something completely different. Also note: the last test 'compatible_pipelines' is unchagned and so only runs the comput pass unless the device supports storage buffers in fragment shaders. I didn't update it yet because for it to work requires either (a) two render passes to render to 2 different render targets. Or it needs some viewport settings to render to 2 different pixels in the same target. Or something..., all of which seem like the might require some big refactors. In the `createEncoder` infra in gpu_test.ts or else they'd just have to do their own thing entirely. Maybe that change doesn't need to happen in this PR but ideas are welcome.
1 parent 58f9d5d commit dd2d76b

File tree

3 files changed

+245
-65
lines changed

3 files changed

+245
-65
lines changed

src/webgpu/api/operation/command_buffer/programmable/programmable_state_test.ts

Lines changed: 121 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import { unreachable } from '../../../../../common/util/util.js';
2-
import { GPUTest } from '../../../../gpu_test.js';
2+
import { GPUTest, GPUTestBase } from '../../../../gpu_test.js';
33
import { EncoderType } from '../../../../util/command_buffer_maker.js';
44

55
interface BindGroupIndices {
@@ -8,38 +8,81 @@ interface BindGroupIndices {
88
out: number;
99
}
1010

11+
type CreateEncoderType = ReturnType<
12+
typeof GPUTestBase.prototype.createEncoder<'compute pass' | 'render pass' | 'render bundle'>
13+
>['encoder'];
14+
1115
export class ProgrammableStateTest extends GPUTest {
1216
private commonBindGroupLayouts: Map<string, GPUBindGroupLayout> = new Map();
1317

14-
getBindGroupLayout(type: GPUBufferBindingType): GPUBindGroupLayout {
15-
if (!this.commonBindGroupLayouts.has(type)) {
18+
skipIfNeedsStorageBuffersInFragmentStageAndHaveNone(
19+
type: GPUBufferBindingType,
20+
encoderType: EncoderType
21+
) {
22+
if (!this.isCompatibility) {
23+
return;
24+
}
25+
26+
const needsStorageBuffersInFragmentStage =
27+
type === 'storage' && (encoderType === 'render bundle' || encoderType === 'render pass');
28+
29+
this.skipIf(
30+
needsStorageBuffersInFragmentStage &&
31+
!(this.device.limits.maxStorageBuffersInFragmentStage! >= 3),
32+
`maxStorageBuffersInFragmentStage(${this.device.limits.maxStorageBuffersInFragmentStage}) < 3`
33+
);
34+
}
35+
36+
getBindGroupLayout(
37+
type: GPUBufferBindingType,
38+
visibility: GPUShaderStageFlags
39+
): GPUBindGroupLayout {
40+
const id = `${type}:${visibility}`;
41+
if (!this.commonBindGroupLayouts.has(id)) {
1642
this.commonBindGroupLayouts.set(
17-
type,
43+
id,
1844
this.device.createBindGroupLayout({
1945
entries: [
2046
{
2147
binding: 0,
22-
visibility: GPUShaderStage.COMPUTE | GPUShaderStage.FRAGMENT,
48+
visibility,
2349
buffer: { type },
2450
},
2551
],
2652
})
2753
);
2854
}
29-
return this.commonBindGroupLayouts.get(type)!;
55+
return this.commonBindGroupLayouts.get(id)!;
3056
}
3157

32-
getBindGroupLayouts(indices: BindGroupIndices): GPUBindGroupLayout[] {
58+
getVisibilityForEncoderType(encoderType: EncoderType) {
59+
return encoderType === 'compute pass' ? GPUShaderStage.COMPUTE : GPUShaderStage.FRAGMENT;
60+
}
61+
62+
getBindGroupLayouts(
63+
indices: BindGroupIndices,
64+
type: GPUBufferBindingType,
65+
encoderType: EncoderType
66+
): GPUBindGroupLayout[] {
3367
const bindGroupLayouts: GPUBindGroupLayout[] = [];
34-
bindGroupLayouts[indices.a] = this.getBindGroupLayout('read-only-storage');
35-
bindGroupLayouts[indices.b] = this.getBindGroupLayout('read-only-storage');
36-
bindGroupLayouts[indices.out] = this.getBindGroupLayout('storage');
68+
const inputType = type === 'storage' ? 'read-only-storage' : 'uniform';
69+
const visibility = this.getVisibilityForEncoderType(encoderType);
70+
bindGroupLayouts[indices.a] = this.getBindGroupLayout(inputType, visibility);
71+
bindGroupLayouts[indices.b] = this.getBindGroupLayout(inputType, visibility);
72+
if (type === 'storage' || encoderType === 'compute pass') {
73+
bindGroupLayouts[indices.out] = this.getBindGroupLayout('storage', visibility);
74+
}
3775
return bindGroupLayouts;
3876
}
3977

40-
createBindGroup(buffer: GPUBuffer, type: GPUBufferBindingType): GPUBindGroup {
78+
createBindGroup(
79+
buffer: GPUBuffer,
80+
type: GPUBufferBindingType,
81+
encoderType: EncoderType
82+
): GPUBindGroup {
83+
const visibility = this.getVisibilityForEncoderType(encoderType);
4184
return this.device.createBindGroup({
42-
layout: this.getBindGroupLayout(type),
85+
layout: this.getBindGroupLayout(type, visibility),
4386
entries: [{ binding: 0, resource: { buffer } }],
4487
});
4588
}
@@ -57,6 +100,7 @@ export class ProgrammableStateTest extends GPUTest {
57100
createBindingStatePipeline<T extends EncoderType>(
58101
encoderType: T,
59102
groups: BindGroupIndices,
103+
type: GPUBufferBindingType,
60104
algorithm: string = 'a.value - b.value'
61105
): GPUComputePipeline | GPURenderPipeline {
62106
switch (encoderType) {
@@ -65,8 +109,8 @@ export class ProgrammableStateTest extends GPUTest {
65109
value : i32
66110
};
67111
68-
@group(${groups.a}) @binding(0) var<storage> a : Data;
69-
@group(${groups.b}) @binding(0) var<storage> b : Data;
112+
@group(${groups.a}) @binding(0) var<${type}> a : Data;
113+
@group(${groups.b}) @binding(0) var<${type}> b : Data;
70114
@group(${groups.out}) @binding(0) var<storage, read_write> out : Data;
71115
72116
@compute @workgroup_size(1) fn main() {
@@ -77,7 +121,7 @@ export class ProgrammableStateTest extends GPUTest {
77121

78122
return this.device.createComputePipeline({
79123
layout: this.device.createPipelineLayout({
80-
bindGroupLayouts: this.getBindGroupLayouts(groups),
124+
bindGroupLayouts: this.getBindGroupLayouts(groups, type, encoderType),
81125
}),
82126
compute: {
83127
module: this.device.createShaderModule({
@@ -92,7 +136,7 @@ export class ProgrammableStateTest extends GPUTest {
92136
const wgslShaders = {
93137
vertex: `
94138
@vertex fn vert_main() -> @builtin(position) vec4<f32> {
95-
return vec4<f32>(0.5, 0.5, 0.0, 1.0);
139+
return vec4<f32>(0, 0, 0, 1);
96140
}
97141
`,
98142

@@ -101,20 +145,23 @@ export class ProgrammableStateTest extends GPUTest {
101145
value : i32
102146
};
103147
104-
@group(${groups.a}) @binding(0) var<storage> a : Data;
105-
@group(${groups.b}) @binding(0) var<storage> b : Data;
148+
@group(${groups.a}) @binding(0) var<${type}> a : Data;
149+
@group(${groups.b}) @binding(0) var<${type}> b : Data;
106150
@group(${groups.out}) @binding(0) var<storage, read_write> out : Data;
107151
108-
@fragment fn frag_main() -> @location(0) vec4<f32> {
152+
@fragment fn frag_main_storage() -> @location(0) vec4<i32> {
109153
out.value = ${algorithm};
110-
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
154+
return vec4<i32>(1, 0, 0, 1);
155+
}
156+
@fragment fn frag_main_uniform() -> @location(0) vec4<i32> {
157+
return vec4<i32>(${algorithm});
111158
}
112159
`,
113160
};
114161

115162
return this.device.createRenderPipeline({
116163
layout: this.device.createPipelineLayout({
117-
bindGroupLayouts: this.getBindGroupLayouts(groups),
164+
bindGroupLayouts: this.getBindGroupLayouts(groups, type, encoderType),
118165
}),
119166
vertex: {
120167
module: this.device.createShaderModule({
@@ -126,8 +173,8 @@ export class ProgrammableStateTest extends GPUTest {
126173
module: this.device.createShaderModule({
127174
code: wgslShaders.fragment,
128175
}),
129-
entryPoint: 'frag_main',
130-
targets: [{ format: 'rgba8unorm' }],
176+
entryPoint: type === 'uniform' ? 'frag_main_uniform' : 'frag_main_storage',
177+
targets: [{ format: 'r32sint' }],
131178
},
132179
primitive: { topology: 'point-list' },
133180
});
@@ -137,6 +184,57 @@ export class ProgrammableStateTest extends GPUTest {
137184
}
138185
}
139186

187+
createEncoderForStateTest(
188+
type: GPUBufferBindingType,
189+
out: GPUBuffer,
190+
...params: Parameters<typeof GPUTestBase.prototype.createEncoder>
191+
): {
192+
encoder: CreateEncoderType;
193+
validateFinishAndSubmit: (shouldBeValid: boolean, submitShouldSucceedIfValid: boolean) => void;
194+
} {
195+
const encoderType = params[0];
196+
const renderTarget = this.createTextureTracked({
197+
size: [1, 1],
198+
format: 'r32sint',
199+
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
200+
});
201+
202+
// Note: This nightmare of gibberish is trying the result of 2 hours of
203+
// trying to get typescript to accept the code. Originally the code was
204+
// effectively just
205+
//
206+
// const { encoder, validateFinishAndSubmit } = this.createEncoder(...);
207+
// const fn = (b0, b1) => { validateFinishAndSubmit(b1, b1); if (...) { ... copyT2B ... } }
208+
// return { encoder: e__, validateFinishAndSubmit: fn };
209+
//
210+
// But TS didn't like it. I couldn't figure out why.
211+
const encoderAndFinish = this.createEncoder(encoderType, {
212+
attachmentInfo: { colorFormats: ['r32sint'] },
213+
targets: [renderTarget.createView()],
214+
});
215+
216+
const validateFinishAndSubmit = (
217+
shouldBeValid: boolean,
218+
submitShouldSucceedIfValid: boolean
219+
) => {
220+
encoderAndFinish.validateFinishAndSubmit(shouldBeValid, submitShouldSucceedIfValid);
221+
222+
if (
223+
type === 'uniform' &&
224+
(encoderType === 'render pass' || encoderType === 'render bundle')
225+
) {
226+
const encoder = this.device.createCommandEncoder();
227+
encoder.copyTextureToBuffer({ texture: renderTarget }, { buffer: out }, [1, 1]);
228+
this.device.queue.submit([encoder.finish()]);
229+
}
230+
};
231+
232+
return {
233+
encoder: encoderAndFinish.encoder as CreateEncoderType,
234+
validateFinishAndSubmit,
235+
};
236+
}
237+
140238
setPipeline(pass: GPUBindingCommandsMixin, pipeline: GPUComputePipeline | GPURenderPipeline) {
141239
if (pass instanceof GPUComputePassEncoder) {
142240
pass.setPipeline(pipeline as GPUComputePipeline);

0 commit comments

Comments
 (0)