Skip to content

Instantly share code, notes, and snippets.

@greggman
Created July 8, 2024 07:29
Show Gist options
  • Save greggman/3c7b8482850252ec1d8b0b848393913f to your computer and use it in GitHub Desktop.
Save greggman/3c7b8482850252ec1d8b0b848393913f to your computer and use it in GitHub Desktop.
WebGPU: Points from mousemove
html, body { margin: 0; height: 100% }
canvas { width: 100%; height: 100%; display: block; }
<canvas></canvas>
/* global GPUBufferUsage */
/* global GPUTextureUsage */
async function main() {
const adapter = await navigator.gpu?.requestAdapter();
const device = await adapter?.requestDevice();
if (!device) {
fail('need a browser that supports WebGPU');
return;
}
const canvas = document.querySelector('canvas');
const context = canvas.getContext('webgpu');
const presentationFormat = navigator.gpu.getPreferredCanvasFormat();
context.configure({
device,
format: presentationFormat,
alphaMode: 'premultiplied',
});
const code = `
struct MyVSInput {
@location(0) position: vec4f,
};
@vertex
fn myVSMain(v: MyVSInput) -> @builtin(position) vec4f {
return v.position;
}
@fragment
fn myFSMain() -> @location(0) vec4f {
return vec4f(1, 0, 0, 1);
}
`;
const module = device.createShaderModule({code});
const pipeline = device.createRenderPipeline({
layout: 'auto',
vertex: {
module,
buffers: [
{
arrayStride: 2 * 4,
attributes: [
{ shaderLocation: 0, offset: 0, format: 'float32x2' },
],
},
],
},
fragment: {
module,
targets: [
{format: presentationFormat},
],
},
primitive: {
topology: 'point-list',
},
});
const buffers = [];
const pointsPerBuffer = 128; // should make this much larger (16k) but keeping it small for testing.
let numPoints = 0;
const addPoint = (x, y) => {
const bufferNdx = numPoints / pointsPerBuffer | 0;
const ndx = numPoints % pointsPerBuffer;
if (ndx === 0) {
const buffer = device.createBuffer({
size: pointsPerBuffer * 2 * 4,
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
buffers.push(buffer);
}
const buffer = buffers[bufferNdx];
device.queue.writeBuffer(buffer, ndx * 2 * 4, new Float32Array([x, y]));
++numPoints;
};
const renderPassDescriptor = {
colorAttachments: [
{
// view: undefined, // Assigned later
clearValue: [ 0.2, 0.2, 0.2, 1.0 ],
loadOp: 'clear',
storeOp: 'store',
},
],
};
function render() {
const canvasTexture = context.getCurrentTexture();
renderPassDescriptor.colorAttachments[0].view = canvasTexture.createView();
const encoder = device.createCommandEncoder();
const pass = encoder.beginRenderPass(renderPassDescriptor);
pass.setPipeline(pipeline);
buffers.forEach((buffer, i) => {
pass.setVertexBuffer(0, buffer);
const base = i * pointsPerBuffer;
const numToDraw = Math.min(numPoints - base, pointsPerBuffer);
pass.draw(numToDraw);
})
pass.end();
device.queue.submit([encoder.finish()]);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('mousemove', e => {
const rect = canvas.getBoundingClientRect();
const x = (e.clientX - rect.left) / rect.width * 2 - 1;
const y = (e.clientY - rect.top) / rect.height * -2 + 1;
addPoint(x, y);
})
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));
}
});
observer.observe(canvas);
}
function fail(msg) {
const elem = document.querySelector('#fail');
elem.style.display = '';
elem.children[0].textContent = msg;
}
main();
{"name":"WebGPU: Points from mousemove ","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