Skip to content

Commit

Permalink
Compat: refactor state_tracking test for 0 frag buffers.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
greggman committed Dec 19, 2024
1 parent 8be4ce7 commit ab48dfc
Show file tree
Hide file tree
Showing 3 changed files with 181 additions and 73 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { unreachable } from '../../../../../common/util/util.js';
import { GPUTest } from '../../../../gpu_test.js';
import { GPUTest, GPUTestBase } from '../../../../gpu_test.js';
import { EncoderType } from '../../../../util/command_buffer_maker.js';

interface BindGroupIndices {
Expand All @@ -8,9 +8,31 @@ interface BindGroupIndices {
out: number;
}

type CreateEncoderType = ReturnType<
typeof GPUTestBase.prototype.createEncoder<'compute pass' | 'render pass' | 'render bundle'>
>['encoder'];

export class ProgrammableStateTest extends GPUTest {
private commonBindGroupLayouts: Map<string, GPUBindGroupLayout> = new Map();

skipIfNeedsStorageBuffersInFragmentStageAndHaveNone(
type: GPUBufferBindingType,
encoderType: EncoderType
) {
if (!this.isCompatibility) {
return;
}

const needsStorageBuffersInFragmentStage =
type === 'storage' && (encoderType === 'render bundle' || encoderType === 'render pass');

this.skipIf(
needsStorageBuffersInFragmentStage &&
!(this.device.limits.maxStorageBuffersInFragmentStage! >= 3),
`maxStorageBuffersInFragmentStage(${this.device.limits.maxStorageBuffersInFragmentStage}) < 3`
);
}

getBindGroupLayout(type: GPUBufferBindingType): GPUBindGroupLayout {
if (!this.commonBindGroupLayouts.has(type)) {
this.commonBindGroupLayouts.set(
Expand All @@ -29,11 +51,18 @@ export class ProgrammableStateTest extends GPUTest {
return this.commonBindGroupLayouts.get(type)!;
}

getBindGroupLayouts(indices: BindGroupIndices): GPUBindGroupLayout[] {
getBindGroupLayouts(
indices: BindGroupIndices,
type: GPUBufferBindingType,
encoderType: EncoderType
): GPUBindGroupLayout[] {
const bindGroupLayouts: GPUBindGroupLayout[] = [];
bindGroupLayouts[indices.a] = this.getBindGroupLayout('read-only-storage');
bindGroupLayouts[indices.b] = this.getBindGroupLayout('read-only-storage');
bindGroupLayouts[indices.out] = this.getBindGroupLayout('storage');
const inputType = type === 'storage' ? 'read-only-storage' : 'uniform';
bindGroupLayouts[indices.a] = this.getBindGroupLayout(inputType);
bindGroupLayouts[indices.b] = this.getBindGroupLayout(inputType);
if (type === 'storage' || encoderType === 'compute pass') {
bindGroupLayouts[indices.out] = this.getBindGroupLayout('storage');
}
return bindGroupLayouts;
}

Expand All @@ -57,6 +86,7 @@ export class ProgrammableStateTest extends GPUTest {
createBindingStatePipeline<T extends EncoderType>(
encoderType: T,
groups: BindGroupIndices,
type: GPUBufferBindingType,
algorithm: string = 'a.value - b.value'
): GPUComputePipeline | GPURenderPipeline {
switch (encoderType) {
Expand All @@ -65,8 +95,8 @@ export class ProgrammableStateTest extends GPUTest {
value : i32
};
@group(${groups.a}) @binding(0) var<storage> a : Data;
@group(${groups.b}) @binding(0) var<storage> b : Data;
@group(${groups.a}) @binding(0) var<${type}> a : Data;
@group(${groups.b}) @binding(0) var<${type}> b : Data;
@group(${groups.out}) @binding(0) var<storage, read_write> out : Data;
@compute @workgroup_size(1) fn main() {
Expand All @@ -77,7 +107,7 @@ export class ProgrammableStateTest extends GPUTest {

return this.device.createComputePipeline({
layout: this.device.createPipelineLayout({
bindGroupLayouts: this.getBindGroupLayouts(groups),
bindGroupLayouts: this.getBindGroupLayouts(groups, type, encoderType),
}),
compute: {
module: this.device.createShaderModule({
Expand All @@ -92,7 +122,7 @@ export class ProgrammableStateTest extends GPUTest {
const wgslShaders = {
vertex: `
@vertex fn vert_main() -> @builtin(position) vec4<f32> {
return vec4<f32>(0.5, 0.5, 0.0, 1.0);
return vec4<f32>(0, 0, 0, 1);
}
`,

Expand All @@ -101,20 +131,23 @@ export class ProgrammableStateTest extends GPUTest {
value : i32
};
@group(${groups.a}) @binding(0) var<storage> a : Data;
@group(${groups.b}) @binding(0) var<storage> b : Data;
@group(${groups.a}) @binding(0) var<${type}> a : Data;
@group(${groups.b}) @binding(0) var<${type}> b : Data;
@group(${groups.out}) @binding(0) var<storage, read_write> out : Data;
@fragment fn frag_main() -> @location(0) vec4<f32> {
@fragment fn frag_main_storage() -> @location(0) vec4<i32> {
out.value = ${algorithm};
return vec4<f32>(1.0, 0.0, 0.0, 1.0);
return vec4<i32>(1, 0, 0, 1);
}
@fragment fn frag_main_uniform() -> @location(0) vec4<i32> {
return vec4<i32>(${algorithm});
}
`,
};

return this.device.createRenderPipeline({
layout: this.device.createPipelineLayout({
bindGroupLayouts: this.getBindGroupLayouts(groups),
bindGroupLayouts: this.getBindGroupLayouts(groups, type, encoderType),
}),
vertex: {
module: this.device.createShaderModule({
Expand All @@ -126,8 +159,8 @@ export class ProgrammableStateTest extends GPUTest {
module: this.device.createShaderModule({
code: wgslShaders.fragment,
}),
entryPoint: 'frag_main',
targets: [{ format: 'rgba8unorm' }],
entryPoint: type === 'uniform' ? 'frag_main_uniform' : 'frag_main_storage',
targets: [{ format: 'r32sint' }],
},
primitive: { topology: 'point-list' },
});
Expand All @@ -137,6 +170,57 @@ export class ProgrammableStateTest extends GPUTest {
}
}

createEncoderForStateTest(
type: GPUBufferBindingType,
out: GPUBuffer,
...params: Parameters<typeof GPUTestBase.prototype.createEncoder>
): {
encoder: CreateEncoderType;
validateFinishAndSubmit: (shouldBeValid: boolean, submitShouldSucceedIfValid: boolean) => void;
} {
const encoderType = params[0];
const renderTarget = this.createTextureTracked({
size: [1, 1],
format: 'r32sint',
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
});

// Note: This nightmare of gibberish is trying the result of 2 hours of
// trying to get typescript to accept the code. Originally the code was
// effectively just
//
// const { encoder, validateFinishAndSubmit } = this.createEncoder(...);
// const fn = (b0, b1) => { validateFinishAndSubmit(b1, b1); if (...) { ... copyT2B ... } }
// return { encoder: e__, validateFinishAndSubmit: fn };
//
// But TS didn't like it. I couldn't figure out why.
const encoderAndFinish = this.createEncoder(encoderType, {
attachmentInfo: { colorFormats: ['r32sint'] },
targets: [renderTarget.createView()],
});

const validateFinishAndSubmit = (
shouldBeValid: boolean,
submitShouldSucceedIfValid: boolean
) => {
encoderAndFinish.validateFinishAndSubmit(shouldBeValid, submitShouldSucceedIfValid);

if (
type === 'uniform' &&
(encoderType === 'render pass' || encoderType === 'render bundle')
) {
const encoder = this.device.createCommandEncoder();
encoder.copyTextureToBuffer({ texture: renderTarget }, { buffer: out }, [1, 1]);
this.device.queue.submit([encoder.finish()]);
}
};

return {
encoder: encoderAndFinish.encoder as CreateEncoderType,
validateFinishAndSubmit,
};
}

setPipeline(pass: GPUBindingCommandsMixin, pipeline: GPUComputePipeline | GPURenderPipeline) {
if (pass instanceof GPUComputePassEncoder) {
pass.setPipeline(pipeline as GPUComputePipeline);
Expand Down
Loading

0 comments on commit ab48dfc

Please sign in to comment.