Skip to content

Instantly share code, notes, and snippets.

@greggman
Created May 30, 2023 05:57
Show Gist options
  • Save greggman/5f351f9fbb4562fff0fab4bf96fad07e to your computer and use it in GitHub Desktop.
Save greggman/5f351f9fbb4562fff0fab4bf96fad07e to your computer and use it in GitHub Desktop.
WebGPU Multiple Triangles w/split Storage Buffers (explicit bindGroupLayout)

WebGPU Multiple Triangles w/split Storage Buffers (explicit bindGroupLayout)

view on jsgist

@import url(https://webgpufundamentals.org/webgpu/resources/webgpu-lesson.css);
html, body {
margin: 0; /* remove the default margin */
height: 100%; /* make the html,body fill the page */
}
canvas {
display: block; /* make the canvas act like a block */
width: 100%; /* make the canvas fill its container */
height: 100%;
}
<canvas></canvas>
// WebGPU Multiple Triangles w/split Storage Buffers (minimal changes)
// from https://webgpufundamentals.org/webgpu/webgpu-simple-triangle-storage-split-minimal-changes.html
// A random number between [min and max)
// With 1 argument it will be [0 to min)
// With no arguments it will be [0 to 1)
const rand = (min, max) => {
if (min === undefined) {
min = 0;
max = 1;
} else if (max === undefined) {
max = min;
min = 0;
}
return min + Math.random() * (max - min);
};
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
fail('need a browser that supports WebGPU');
return;
}
// Get a WebGPU context from the canvas and configure it
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
});
const module = device.createShaderModule({
code: `
struct OurStruct {
color: vec4f,
offset: vec2f,
};
struct OtherStruct {
scale: vec2f,
};
@group(0) @binding(0) var<storage, read> ourStruct: OurStruct;
@group(0) @binding(1) var<storage, read> otherStruct: OtherStruct;
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> @builtin(position) vec4f {
var pos = array<vec2f, 3>(
vec2f( 0.0, 0.5), // top center
vec2f(-0.5, -0.5), // bottom left
vec2f( 0.5, -0.5) // bottom right
);
return vec4f(
pos[vertexIndex] * otherStruct.scale + ourStruct.offset, 0.0, 1.0);
}
@fragment fn fs() -> @location(0) vec4f {
return ourStruct.color;
}
`,
});
const bindGroupLayout = device.createBindGroupLayout({
entries: [
{ binding: 0, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage', }},
{ binding: 1, visibility: GPUShaderStage.VERTEX | GPUShaderStage.FRAGMENT, buffer: { type: 'read-only-storage', }},
],
});
const pipelineLayout = device.createPipelineLayout({
bindGroupLayouts: [ bindGroupLayout ],
});
const pipeline = device.createRenderPipeline({
label: 'storage buffer like uniform',
layout: pipelineLayout, //'auto',
vertex: {
module,
entryPoint: 'vs',
},
fragment: {
module,
entryPoint: 'fs',
targets: [{ format: presentationFormat }],
},
});
// create 2 buffers for the uniform values
const staticUniformBufferSize =
4 * 4 + // color is 4 32bit floats (4bytes each)
2 * 4 + // offset is 2 32bit floats (4bytes each)
2 * 4; // padding
const uniformBufferSize =
2 * 4; // scale is 2 32bit floats (4bytes each)
// offsets to the various uniform values in float32 indices
const kColorOffset = 0;
const kOffsetOffset = 4;
const kScaleOffset = 0;
const kNumObjects = 100;
const objectInfos = [];
for (let i = 0; i < kNumObjects; ++i) {
const staticUniformBuffer = device.createBuffer({
label: `static uniforms for obj: ${i}`,
size: staticUniformBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
// These are only set once so set them now
{
const uniformValues = new Float32Array(staticUniformBufferSize / 4);
uniformValues.set([rand(), rand(), rand(), 1], kColorOffset); // set the color
uniformValues.set([rand(-0.9, 0.9), rand(-0.9, 0.9)], kOffsetOffset); // set the offset
// copy these values to the GPU
device.queue.writeBuffer(staticUniformBuffer, 0, uniformValues);
}
// create a typedarray to hold the values for the uniforms in JavaScript
const uniformValues = new Float32Array(uniformBufferSize / 4);
const uniformBuffer = device.createBuffer({
label: `changing uniforms for obj: ${i}`,
size: uniformBufferSize,
usage: GPUBufferUsage.STORAGE | GPUBufferUsage.COPY_DST,
});
const bindGroup = device.createBindGroup({
label: `bind group for obj: ${i}`,
layout: bindGroupLayout, // pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: staticUniformBuffer }},
{ binding: 1, resource: { buffer: uniformBuffer }},
],
});
objectInfos.push({
scale: rand(0.2, 0.5),
uniformBuffer,
uniformValues,
bindGroup,
});
}
const renderPassDescriptor = {
label: 'our basic canvas renderPass',
colorAttachments: [
{
// view: <- to be filled out when we render
clearValue: [0.3, 0.3, 0.3, 1],
loadOp: 'clear',
storeOp: 'store',
},
],
};
function render() {
// Get the current texture from the canvas context and
// set it as the texture to render to.
renderPassDescriptor.colorAttachments[0].view =
context.getCurrentTexture().createView();
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
// Set the uniform values in our JavaScript side Float32Array
const aspect = canvas.width / canvas.height;
for (const {scale, bindGroup, uniformBuffer, uniformValues} of objectInfos) {
uniformValues.set([scale / aspect, scale], kScaleOffset); // set the scale
device.queue.writeBuffer(uniformBuffer, 0, uniformValues);
pass.setBindGroup(0, bindGroup);
pass.draw(3); // call our vertex shader 3 times
}
pass.end();
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
}
const observer = new ResizeObserver(entries => {
for (const entry of entries) {
const canvas = entry.target;
const width = entry.contentBoxSize[0].inlineSize;
const height = entry.contentBoxSize[0].blockSize;
canvas.width = Math.min(width, device.limits.maxTextureDimension2D);
canvas.height = Math.min(height, device.limits.maxTextureDimension2D);
// re-render
render();
}
});
observer.observe(canvas);
}
function fail(msg) {
alert(msg);
}
main();
{"name":"WebGPU Multiple Triangles w/split Storage Buffers (explicit bindGroupLayout)","settings":{},"filenames":["index.html","index.css","index.js"]}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment