-
Notifications
You must be signed in to change notification settings - Fork 86
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add operation test on the creation of pipeline layout with null bind …
…group layout (#4116) * Test build checks * Add tests on render and compute pipeline * Small fix * Also set bind group on null bind group layout * Test empty bind group layouts * Address reviewers' comments
- Loading branch information
1 parent
077ffee
commit b5532d4
Showing
1 changed file
with
324 additions
and
0 deletions.
There are no files selected for viewing
324 changes: 324 additions & 0 deletions
324
...webgpu/api/operation/pipeline/pipeline_layout_created_with_null_bind_group_layout.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
}); |