Skip to content

Instantly share code, notes, and snippets.

@greggman
Last active May 7, 2024 01:22
Show Gist options
  • Save greggman/295e38eeedf5957ac50179308666d98b to your computer and use it in GitHub Desktop.
Save greggman/295e38eeedf5957ac50179308666d98b to your computer and use it in GitHub Desktop.
WebGPU write to canvas texture in compute shader
@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>
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
if (!adapter) {
fail('need a browser that supports WebGPU');
}
// bgra8unorm as a storage texture is an optional feature so
// if it's supported then we don't care if presentationFormat is
// bgra8unorm or rgba8unorm but if the feature does not exist
// then we must use rgba8unorm
let presentationFormat = adapter.features.has('bgra8unorm-storage')
? navigator.gpu.getPreferredCanvasFormat()
: 'rgba8unorm';
const device = await adapter?.requestDevice({
requiredFeatures: presentationFormat === 'bgra8unorm'
? ['bgra8unorm-storage']
: [],
});
if (!device) {
return;
}
// Get a WebGPU context from the canvas and configure it
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
context.configure({
device,
format: presentationFormat,
// This is what's required to be able to write to a texture from a compute shader
usage: GPUTextureUsage.RENDER_ATTACHMENT | GPUTextureUsage.STORAGE_BINDING,
});
const module = device.createShaderModule({
code: `
@group(0) @binding(0) var tex: texture_storage_2d<${presentationFormat}, write>;
@compute @workgroup_size(1) fn cs(
@builtin(global_invocation_id) id: vec3u
) {
let color = vec4f(fract(vec2f(id.xy) / 32.0), 0, 1);
textureStore(tex, id.xy, color);
}
`,
});
const pipeline = device.createComputePipeline({
label: 'checkboard pipeline',
layout: 'auto',
compute: {
module,
entryPoint: 'cs',
},
});
function render() {
// Get the current texture from the canvas context
const canvasTexture = context.getCurrentTexture();
const bindGroup = device.createBindGroup({
layout: pipeline.getBindGroupLayout(0),
entries: [
{ binding: 0, resource: canvasTexture.createView() },
],
});
const encoder = device.createCommandEncoder({ label: 'our encoder' });
const pass = encoder.beginComputePass();
pass.setPipeline(pipeline);
pass.setBindGroup(0, bindGroup);
pass.dispatchWorkgroups(canvasTexture.width, canvasTexture.height);
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.max(1, Math.min(width, device.limits.maxTextureDimension2D));
canvas.height = Math.max(1, Math.min(height, device.limits.maxTextureDimension2D));
// re-render
render();
}
});
observer.observe(canvas);
}
function fail(msg) {
// eslint-disable-next-line no-alert
alert(msg);
}
main();
{"name":"WebGPU write to canvas texture in compute shader","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