VOOZH about

URL: https://dev.to/dev48v/webgpu-i-simulated-250000-particles-entirely-on-the-gpu-in-the-browser-2o0f

⇱ WebGPU: I Simulated 250,000 Particles Entirely on the GPU (in the Browser) - DEV Community


This is Day 50 — the finale of my "a new tech every day, built from scratch" series. I wanted to end on the most "wait, this runs in a browser?" thing I know: WebGPU, simulating AND drawing 250,000 particles entirely on the GPU.

👉 Full code: https://github.com/dev48v/webgpu-from-zero

Why WebGPU matters

Your CPU has a handful of fast, clever cores. Your GPU has thousands of simple ones. For a job like "move 250,000 particles," the CPU grinds through them roughly one after another; the GPU does thousands at the exact same instant.

WebGPU is the browser's modern, safe API to that hardware — the successor to WebGL. The killer feature: compute shaders, general-purpose GPU programs, not just graphics.

1. Adapter → Device

const adapter = await navigator.gpu.requestAdapter();
const device = await adapter.requestDevice();
const ctx = canvas.getContext("webgpu");
ctx.configure({ device, format: navigator.gpu.getPreferredCanvasFormat() });

The device is your handle to the GPU. Every buffer, shader, and command goes through it.

2. Particles live in a GPU buffer

const data = new Float32Array(N * 4); // x, y, vx, vy per particle
const buffer = device.createBuffer({
 size: data.byteLength,
 usage: GPUBufferUsage.STORAGE | GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST,
});
device.queue.writeBuffer(buffer, 0, data);

Upload once, keep it on the GPU across frames.

3. A compute shader updates ONE particle (WGSL)

@group(0) @binding(0) var<storage, read_write> p: array<vec4f>;

@compute @workgroup_size(64)
fn main(@builtin(global_invocation_id) id: vec3u) {
 let i = id.x;
 var pos = p[i].xy; var vel = p[i].zw;
 let angle = sin(pos.x*0.008) + cos(pos.y*0.008); // flow field
 vel += vec2f(cos(angle), sin(angle)) * 0.06;
 p[i] = vec4f(pos + vel, vel * 0.95); // move + drag
}

You write the code for one particle; the GPU runs it on all 250,000 simultaneously.

4. Dispatch one thread per particle

const pass = encoder.beginComputePass();
pass.setPipeline(computePipeline);
pass.setBindGroup(0, computeBind);
pass.dispatchWorkgroups(Math.ceil(N / 64)); // N/64 groups × 64 threads
pass.end();

5. Render the same buffer

The render pass reads that same particle buffer in a vertex shader and draws N points. The data never leaves the GPU between the compute step and the draw step — that single fact is why it flies, and why it scales to a million particles at 60fps.

device.queue.submit([encoder.finish()]);
requestAnimationFrame(frame);

50 days, looking back

Web frameworks, backends, databases, testing, realtime, AI/RAG, mobile — and now the GPU. The format never changed: a real project, from zero, with commits you can read one concept at a time. WebGPU is becoming the foundation for in-browser GPU compute and even local ML, so it felt like the right note to end on.

If you followed along even for a few days — thank you. 🙏 The whole archive stays live: https://dev48v.infy.uk/techfromzero.php