blob: 187710251102dde7d4a96c9c26ce3d6804ae146e [file] [log] [blame]
<!DOCTYPE html>
<meta charset=utf-8>
<title>Create a basic GPUBindGroup.</title>
<body>
<script src="js/webgpu-functions.js"></script>
<script src="../resources/testharness.js"></script>
<script src="../resources/testharnessreport.js"></script>
<script>
let tests = {};
const basicBufferShader = `
[numthreads(1, 1, 1)]
compute void compute_main(device int[] buffer : register(u0))
{
++buffer[0];
}
`;
let basicPipeline;
tests["Create and use a basic GPUBindGroup."] = async device => {
const bufferLayoutBinding = {
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "storage-buffer"
};
const bindGroupLayout = device.createBindGroupLayout({ bindings: [bufferLayoutBinding] });
const basicBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ });
const bufferBinding = { buffer: basicBuffer, size: 4 };
const bindGroupBinding = { binding: 0, resource: bufferBinding };
const bindGroup = device.createBindGroup({ layout: bindGroupLayout, bindings: [bindGroupBinding] });
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
const basicShaderModule = device.createShaderModule({ code: basicBufferShader });
basicPipeline = device.createComputePipeline({
layout: pipelineLayout,
computeStage: {
module: basicShaderModule,
entryPoint: "compute_main"
}
});
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(basicPipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatch(1, 1, 1);
passEncoder.endPass();
device.getQueue().submit([commandEncoder.finish()]);
const results = new Int32Array(await basicBuffer.mapReadAsync());
basicBuffer.unmap();
assert_equals(results[0], 1, "Storage buffer binding written to successfully.");
};
tests["Create and use many GPUBindGroups in a single compute pass."] = async device => {
const bufferLayoutBinding = {
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "storage-buffer"
};
const bindGroupLayout = device.createBindGroupLayout({ bindings: [bufferLayoutBinding] });
const basicBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ });
const bufferBinding = { buffer: basicBuffer, size: 4 };
const bindGroupBinding = { binding: 0, resource: bufferBinding };
const numGroups = 1000;
let bindGroups = new Array(numGroups);
for (let i = 0; i < numGroups; ++i)
bindGroups[i] = device.createBindGroup({ layout: bindGroupLayout, bindings: [bindGroupBinding] });
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
let j = 0;
for (; j < numGroups; ++j) {
passEncoder.setPipeline(basicPipeline);
passEncoder.setBindGroup(0, bindGroups[j]);
passEncoder.dispatch(1, 1, 1);
}
passEncoder.endPass();
device.getQueue().submit([commandEncoder.finish()]);
const results = new Int32Array(await basicBuffer.mapReadAsync());
basicBuffer.unmap();
assert_equals(results[0], j, "Storage buffer accessed successfully through multiple bind groups.");
};
const uniformBufferShader = `
[numthreads(1, 1, 1)]
compute void compute_main(constant int[] uniforms : register(b0), device int[] buffer : register(u1))
{
buffer[0] += uniforms[0];
}
`;
tests["Create and access a uniform-buffer in a GPUBindGroup."] = async device => {
const [uniformBuffer, writeArrayBuffer] = device.createBufferMapped({ size: 4, usage: GPUBufferUsage.UNIFORM });
new Int32Array(writeArrayBuffer).set([42]);
uniformBuffer.unmap();
const storageBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ });
const bindGroupLayout = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "uniform-buffer"
}, {
binding: 1,
visibility: GPUShaderStage.COMPUTE,
type: "storage-buffer"
}]
});
const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
bindings: [{
binding: 0,
resource: {
buffer: uniformBuffer,
size: 4
}
}, {
binding: 1,
resource: {
buffer: storageBuffer,
size: 4
}
}]
});
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
const shaderModule = device.createShaderModule({ code: uniformBufferShader });
const pipeline = device.createComputePipeline({
layout: pipelineLayout,
computeStage: {
module: shaderModule,
entryPoint: "compute_main"
}
});
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatch(1, 1, 1);
passEncoder.endPass();
device.getQueue().submit([commandEncoder.finish()]);
const results = new Int32Array(await storageBuffer.mapReadAsync());
storageBuffer.unmap();
assert_equals(results[0], 42, "Storage buffer binding written to successfully.");
};
const sampledTextureShader = `
[numthreads(1, 1, 1)]
compute void compute_main(Texture2D<uint> inputTexture : register(t0), sampler inputSampler : register(s1), device uint[] output : register(u2))
{
output[0] = Sample(inputTexture, inputSampler, float2(0, 0));
}
`;
tests["Create and access a sampled texture in a GPUBindGroup."] = async device => {
const [textureDataBuffer, textureArrayBuffer] = device.createBufferMapped({ size: 4, usage: GPUBufferUsage.COPY_SRC });
new Uint32Array(textureArrayBuffer).set([42]);
textureDataBuffer.unmap();
const textureSize = { width: 1, height: 1, depth: 1 };
const texture = device.createTexture({
size: textureSize,
format: "rgba8uint",
usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST
});
const outputBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ });
const bindGroupLayout = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "sampled-texture"
}, {
binding: 1,
visibility: GPUShaderStage.COMPUTE,
type: "sampler"
}, {
binding: 2,
visibility: GPUShaderStage.COMPUTE,
type: "storage-buffer"
}]
});
const bindGroup = device.createBindGroup({
layout: bindGroupLayout,
bindings: [{
binding: 0,
resource: texture.createDefaultView()
}, {
binding: 1,
resource: device.createSampler({})
}, {
binding: 2,
resource: {
buffer: outputBuffer,
size: 4
}
}]
});
const shaderModule = device.createShaderModule({ code: sampledTextureShader });
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
const pipeline = device.createComputePipeline({
layout: pipelineLayout,
computeStage: {
module: shaderModule,
entryPoint: "compute_main"
}
});
const commandEncoder = device.createCommandEncoder();
commandEncoder.copyBufferToTexture({
buffer: textureDataBuffer,
rowPitch: 4,
imageHeight: 0
}, { texture: texture }, textureSize);
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bindGroup);
passEncoder.dispatch(1, 1, 1);
passEncoder.endPass();
device.getQueue().submit([commandEncoder.finish()]);
const results = new Uint32Array(await outputBuffer.mapReadAsync());
outputBuffer.unmap();
assert_equals(results[0], 42, "Correct value sampled from a bound 2D texture.");
};
const comboShader = `
[numthreads(1, 1, 1)]
compute void compute_main(
Texture2D<uint> inputTexture : register(t0, space0),
sampler inputSampler : register(s0, space1),
constant uint[] input : register(b0, space2),
device uint[] output : register(u0, space3))
{
output[0] = input[0] + Sample(inputTexture, inputSampler, float2(0, 0));
}
`;
tests["Create and use multiple GPUBindGroups in a single dispatch."] = async device => {
const [textureDataBuffer, textureArrayBuffer] = device.createBufferMapped({ size: 4, usage: GPUBufferUsage.COPY_SRC });
new Uint32Array(textureArrayBuffer).set([17]);
textureDataBuffer.unmap();
const textureSize = { width: 1, height: 1, depth: 1 };
const texture = device.createTexture({
size: textureSize,
format: "rgba8uint",
usage: GPUTextureUsage.SAMPLED | GPUTextureUsage.COPY_DST
});
const [inputBuffer, inputArrayBuffer] = device.createBufferMapped({ size: 4, usage: GPUBufferUsage.UNIFORM });
new Uint32Array(inputArrayBuffer).set([25]);
inputBuffer.unmap();
const outputBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ });
const bgl0 = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "sampled-texture"
}]
});
const bgl1 = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "sampler"
}]
});
const bgl2 = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "uniform-buffer"
}]
});
const bgl3 = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "storage-buffer"
}]
})
const bg0 = device.createBindGroup({
layout: bgl0,
bindings: [{
binding: 0,
resource: texture.createDefaultView()
}]
});
const bg1 = device.createBindGroup({
layout: bgl1,
bindings: [{
binding: 0,
resource: device.createSampler({})
}]
});
const bg2 = device.createBindGroup({
layout: bgl2,
bindings: [{
binding: 0,
resource: {
buffer: inputBuffer,
size: 4
}
}]
});
const bg3 = device.createBindGroup({
layout: bgl3,
bindings: [{
binding: 0,
resource: {
buffer: outputBuffer,
size: 4
}
}]
});
const shaderModule = device.createShaderModule({ code: comboShader });
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bgl0, bgl1, bgl2, bgl3] });
const pipeline = device.createComputePipeline({
layout: pipelineLayout,
computeStage: {
module: shaderModule,
entryPoint: "compute_main"
}
});
const commandEncoder = device.createCommandEncoder();
commandEncoder.copyBufferToTexture({
buffer: textureDataBuffer,
rowPitch: 4,
imageHeight: 0
}, { texture: texture }, textureSize);
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(pipeline);
passEncoder.setBindGroup(0, bg0);
passEncoder.setBindGroup(1, bg1);
passEncoder.setBindGroup(2, bg2);
passEncoder.setBindGroup(3, bg3);
passEncoder.dispatch(1, 1, 1);
passEncoder.endPass();
device.getQueue().submit([commandEncoder.finish()]);
const results = new Uint32Array(await outputBuffer.mapReadAsync());
outputBuffer.unmap();
assert_equals(results[0], 42, "Correct value sampled from a bound 2D texture.");
};
tests["Bind a single GPUBuffer with different offsets in different GPUBindGroups"] = async device => {
const numInputs = 4;
const [uniformBuffer, writeArrayBuffer] = device.createBufferMapped({ size: 4 * numInputs, usage: GPUBufferUsage.UNIFORM });
new Int32Array(writeArrayBuffer).set([1, 2, 3, 36]);
uniformBuffer.unmap();
const storageBuffer = device.createBuffer({ size: 4, usage: GPUBufferUsage.STORAGE | GPUBufferUsage.MAP_READ });
const bindGroupLayout = device.createBindGroupLayout({
bindings: [{
binding: 0,
visibility: GPUShaderStage.COMPUTE,
type: "uniform-buffer"
}, {
binding: 1,
visibility: GPUShaderStage.COMPUTE,
type: "storage-buffer"
}]
});
let bindGroups = new Array(numInputs);
for (let i = 0; i < numInputs; ++i) {
bindGroups[i] = device.createBindGroup({
layout: bindGroupLayout,
bindings: [{
binding: 0,
resource: {
buffer: uniformBuffer,
offset: i * numInputs,
size: 4
}
}, {
binding: 1,
resource: {
buffer: storageBuffer,
size: 4
}
}]
});
}
const pipelineLayout = device.createPipelineLayout({ bindGroupLayouts: [bindGroupLayout] });
const shaderModule = device.createShaderModule({ code: uniformBufferShader });
const pipeline = device.createComputePipeline({
layout: pipelineLayout,
computeStage: {
module: shaderModule,
entryPoint: "compute_main"
}
});
const commandEncoder = device.createCommandEncoder();
const passEncoder = commandEncoder.beginComputePass();
passEncoder.setPipeline(pipeline);
for (let i = 0; i < numInputs; ++i) {
passEncoder.setBindGroup(0, bindGroups[i]);
passEncoder.dispatch(1, 1, 1);
}
passEncoder.endPass();
device.getQueue().submit([commandEncoder.finish()]);
const results = new Int32Array(await storageBuffer.mapReadAsync());
storageBuffer.unmap();
assert_equals(results[0], 42, "Storage buffer binding written to successfully.");
};
runTestsWithDevice(tests);
</script>
</body>