Skip to content

Instantly share code, notes, and snippets.

@bacher
Forked from greggman/README.md
Created May 11, 2024 11:04
Show Gist options
  • Save bacher/67fa38ee638423c304962ff087685b6e to your computer and use it in GitHub Desktop.
Save bacher/67fa38ee638423c304962ff087685b6e to your computer and use it in GitHub Desktop.
WebGPU Simple Triangle - write to storage buffer
@import url(https://webgpufundamentals.org/webgpu/resources/webgpu-lesson.css);
<canvas></canvas>
// WebGPU Simple Triangle
// from https://webgpufundamentals.org/webgpu/webgpu-simple-triangle.html
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({
label: 'our hardcoded red triangle shaders',
code: `
@vertex fn vs(
@builtin(vertex_index) vertexIndex : u32
) -> @builtin(position) vec4f {
let pos = array(
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], 0.0, 1.0);
}
@group(0) @binding(0) var<storage, read_write> s: array<vec2f>;
@fragment fn fs(@builtin(position) pos: vec4f) -> @location(0) vec4f {
let offset = i32(pos.x) + i32(pos.y) * 300;
let offset2 = 300 * 150 - offset;
s[offset] = pos.yx;
s[offset2] = pos.xy;
return vec4f(1, 0, 0, 1);
}
`,
});
const pipeline = device.createRenderPipeline({
label: 'our hardcoded red triangle pipeline',
layout: 'auto',
vertex: {
module,
entryPoint: 'vs',
},
fragment: {
module,
entryPoint: 'fs',
targets: [{ format: presentationFormat }],
},
});
const storageBuffer = device.createBuffer({
size: 300 * 150 * 4 * 2,
usage: GPUBufferUsage.COPY_SRC | GPUBufferUsage.STORAGE,
});
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: { buffer: storageBuffer } },
],
});
const resultBuffer = device.createBuffer({
size: 300 * 150 * 4 * 2,
usage: GPUBufferUsage.MAP_READ | GPUBufferUsage.COPY_DST,
});
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',
},
],
};
async 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();
// make a command encoder to start encoding commands
const encoder = device.createCommandEncoder({ label: 'our encoder' });
// make a render pass encoder to encode render specific commands
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.draw(3); // call our vertex shader 3 times.
pass.end();
encoder.copyBufferToBuffer(storageBuffer, 0, resultBuffer, 0, storageBuffer.size);
const commandBuffer = encoder.finish();
device.queue.submit([commandBuffer]);
await resultBuffer.mapAsync(GPUMapMode.READ);
const results = new Float32Array(resultBuffer.getMappedRange());
// show the results in a 2D canvas
const ctx = document.createElement('canvas').getContext('2d');
document.body.appendChild(ctx.canvas);
const rgb = (r, g, b) => `rgb(${r}, ${g}, ${b})`;
for (let y = 0; y < 150; ++y) {
for (let x = 0; x < 300; ++x) {
const off = (y * 300 + x) * 2;
ctx.fillStyle = rgb(results[off], results[off + 1], 0);
ctx.fillRect(x, y, 1, 1);
}
}
}
render();
}
function fail(msg) {
// eslint-disable-next-line no-alert
alert(msg);
}
main();
{"name":"WebGPU Simple Triangle - write to storage buffer","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