Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add operation test on the creation of pipeline layout with null bind group layout #4116

Merged
merged 8 commits into from
Jan 9, 2025
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,324 @@
export const description = `
Tests for the creation of pipeline layouts with null bind group layouts.
`;

import { makeTestGroup } from '../../../../common/framework/test_group.js';
import { GPUConst } from '../../../constants.js';
import { GPUTest } from '../../../gpu_test.js';

export const g = makeTestGroup(GPUTest);

g.test('pipeline_layout_with_null_bind_group_layout,rendering')
.desc(
`
Tests that using a render pipeline created with a pipeline layout that has null bind group layout
works correctly.
`
)
.params(u =>
u
.combine('emptyBindGroupLayoutType', ['Null', 'Undefined', 'Empty'] as const)
.combine('emptyBindGroupLayoutIndex', [0, 1, 2, 3] as const)
)
.fn(t => {
const { emptyBindGroupLayoutType, emptyBindGroupLayoutIndex } = t.params;

const colors = [
[0.2, 0, 0, 0.2],
[0, 0.2, 0, 0.2],
[0, 0, 0.2, 0.2],
[0.4, 0, 0, 0.2],
] as const;
const outputColor = [0.0, 0.0, 0.0, 0.0];

let declarations = '';
let statement = 'return vec4(0.0, 0.0, 0.0, 0.0)';
const bindGroupLayouts: (GPUBindGroupLayout | null | undefined)[] = [];
const bindGroups: GPUBindGroup[] = [];
for (let bindGroupIndex = 0; bindGroupIndex < 4; ++bindGroupIndex) {
const bindGroupLayout = t.device.createBindGroupLayout({
entries: [
{
binding: 0,
visibility: GPUConst.ShaderStage.FRAGMENT,
buffer: {
type: 'uniform',
minBindingSize: 16,
},
},
],
});

const color = colors[bindGroupIndex];
const buffer = t.makeBufferWithContents(new Float32Array(color), GPUBufferUsage.UNIFORM);

// Still create and set the bind group when the corresponding bind group layout in the
// pipeline is null. The output color should not be affected by the buffer in this bind group
const bindGroup = t.device.createBindGroup({
layout: bindGroupLayout,
entries: [
{
binding: 0,
resource: {
buffer,
},
},
],
});
bindGroups.push(bindGroup);

// Set `null`, `undefined` or empty bind group layout in `bindGroupLayouts` which is used in
// the creation of pipeline layout
if (bindGroupIndex === emptyBindGroupLayoutIndex) {
switch (emptyBindGroupLayoutType) {
case 'Null':
bindGroupLayouts.push(null);
break;
case 'Undefined':
bindGroupLayouts.push(undefined);
break;
case 'Empty':
bindGroupLayouts.push(
t.device.createBindGroupLayout({
entries: [],
})
);
break;
}
continue;
}

// Set the uniform buffers used in the shader
bindGroupLayouts.push(bindGroupLayout);
declarations += `@group(${bindGroupIndex}) @binding(0) var<uniform> input${bindGroupIndex} : vec4f;\n`;
statement += ` + input${bindGroupIndex}`;

// Compute the expected output color
for (let i = 0; i < color.length; ++i) {
outputColor[i] += color[i];
}
}

const pipelineLayout = t.device.createPipelineLayout({
bindGroupLayouts,
});

const format = 'rgba8unorm';
const code = `
${declarations}
@vertex
fn vert_main() -> @builtin(position) vec4f {
return vec4f(0.0, 0.0, 0.0, 1.0);
}
@fragment
fn frag_main() -> @location(0) vec4f {
${statement};
}
`;
const shaderModule = t.device.createShaderModule({
code,
});
const renderPipeline = t.device.createRenderPipeline({
layout: pipelineLayout,
vertex: {
module: shaderModule,
},
fragment: {
module: shaderModule,
targets: [
{
format,
},
],
},
primitive: {
topology: 'point-list',
},
});

const renderTarget = t.createTextureTracked({
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.COPY_SRC,
size: [1, 1, 1],
format,
});
const commandEncoder = t.device.createCommandEncoder();
const renderPassEncoder = commandEncoder.beginRenderPass({
colorAttachments: [
{
view: renderTarget.createView(),
loadOp: 'load',
storeOp: 'store',
},
],
});
for (let i = 0; i < 4; ++i) {
renderPassEncoder.setBindGroup(i, bindGroups[i]);
}
renderPassEncoder.setPipeline(renderPipeline);
renderPassEncoder.draw(1);
renderPassEncoder.end();

t.queue.submit([commandEncoder.finish()]);

t.expectSingleColor(renderTarget, format, {
size: [1, 1, 1],
exp: { R: outputColor[0], G: outputColor[1], B: outputColor[2], A: outputColor[3] },
});
});

g.test('pipeline_layout_with_null_bind_group_layout,compute')
.desc(
`
Tests that using a compute pipeline created with a pipeline layout that has null bind group layout
works correctly.
`
)
.params(u =>
u
.combine('emptyBindGroupLayoutType', ['Null', 'Undefined', 'Empty'] as const)
.combine('emptyBindGroupLayoutIndex', [0, 1, 2, 3] as const)
)
.fn(t => {
const { emptyBindGroupLayoutType, emptyBindGroupLayoutIndex } = t.params;

let declarations = '';
let statement = 'output = 0u ';

const outputBuffer = t.createBufferTracked({
size: 4,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
});
let expectedValue = 0;

const bindGroupLayouts: (GPUBindGroupLayout | null | undefined)[] = [];
const bindGroups: GPUBindGroup[] = [];
let outputDeclared = false;
for (let bindGroupIndex = 0; bindGroupIndex < 4; ++bindGroupIndex) {
const inputBuffer = t.makeBufferWithContents(
new Uint32Array([bindGroupIndex + 1]),
GPUBufferUsage.UNIFORM
);

const bindGroupLayoutEntries: GPUBindGroupLayoutEntry[] = [];
const bindGroupEntries: GPUBindGroupEntry[] = [];
bindGroupLayoutEntries.push({
binding: 0,
visibility: GPUConst.ShaderStage.COMPUTE,
buffer: {
type: 'uniform',
minBindingSize: 4,
},
});
bindGroupEntries.push({
binding: 0,
resource: {
buffer: inputBuffer,
},
});

// Set `null`, `undefined` or empty bind group layout in `bindGroupLayouts` which is used in
// the creation of pipeline layout
if (bindGroupIndex === emptyBindGroupLayoutIndex) {
switch (emptyBindGroupLayoutType) {
case 'Null':
bindGroupLayouts.push(null);
break;
case 'Undefined':
bindGroupLayouts.push(undefined);
break;
case 'Empty':
bindGroupLayouts.push(
t.device.createBindGroupLayout({
entries: [],
})
);
break;
}

// Still create and set the bind group when the corresponding bind group layout in the
// compute pipeline is null. The value in the output buffer should not be affected by the
// buffer in this bind group
const bindGroup = t.device.createBindGroup({
layout: t.device.createBindGroupLayout({
entries: bindGroupLayoutEntries,
}),
entries: bindGroupEntries,
});
bindGroups.push(bindGroup);
continue;
}

declarations += `@group(${bindGroupIndex}) @binding(0) var<uniform> input${bindGroupIndex} : u32;\n`;
statement += ` + input${bindGroupIndex}`;

// Set the output storage buffer
if (!outputDeclared) {
bindGroupLayoutEntries.push({
binding: 1,
visibility: GPUConst.ShaderStage.COMPUTE,
buffer: {
type: 'storage',
minBindingSize: 4,
},
});
bindGroupEntries.push({
binding: 1,
resource: {
buffer: outputBuffer,
},
});
declarations += `@group(${bindGroupIndex}) @binding(1) var<storage, read_write> output : u32;\n`;
outputDeclared = true;
}

// Set the input uniform buffers
const bindGroupLayout = t.device.createBindGroupLayout({
entries: bindGroupLayoutEntries,
});
bindGroupLayouts.push(bindGroupLayout);

const bindGroup = t.device.createBindGroup({
layout: bindGroupLayout,
entries: bindGroupEntries,
});
bindGroups.push(bindGroup);

// Compute the expected output value in the output storage buffer
expectedValue += bindGroupIndex + 1;
}

const pipelineLayout = t.device.createPipelineLayout({
bindGroupLayouts,
});

const code = `
${declarations}
@compute @workgroup_size(1)
fn main() {
${statement};
}
`;
const module = t.device.createShaderModule({
code,
});
const computePipeline = t.device.createComputePipeline({
layout: pipelineLayout,
compute: {
module,
},
});

const commandEncoder = t.device.createCommandEncoder();
const computePassEncoder = commandEncoder.beginComputePass();
for (let i = 0; i < bindGroups.length; ++i) {
computePassEncoder.setBindGroup(i, bindGroups[i]);
}
computePassEncoder.setPipeline(computePipeline);
computePassEncoder.dispatchWorkgroups(1);
computePassEncoder.end();

t.queue.submit([commandEncoder.finish()]);

const expectedValues = new Uint32Array([expectedValue]);
t.expectGPUBufferValuesEqual(outputBuffer, expectedValues);
});
Loading