WebGPU Triangle Demo: Start, Fix, and Keep It Honest
WebGPU gets easier after the first triangle.
This is the combined guide for the minimal setup, rebuild fixes, and honesty checks.
You will render a triangle, verify the output, and keep the write up strict.
I cannot verify these steps from this blog repo. Treat this as a lab guide you can run locally.
The promise
By the end, you will:
- render a WebGPU triangle
- verify the render loop
- keep the write up aligned with the code
"One triangle is enough to learn the pipeline."
What this is
High level: WebGPU is a browser API for GPU rendering.
Low level: You request a device, configure a canvas, build a pipeline, and draw.
Key terms:
- Adapter: GPU capability entry point.
- Device: The object that creates GPU resources.
- Pipeline: The configuration that ties shaders to output.
What you need
- Chrome or Edge with WebGPU enabled
- A local static server
Start to Finish
Step 1: Create the HTML shell
Goal: Set up a canvas and script entry point.
Actions:
- File path:
index.html - Add:
<canvas id="gfx" width="640" height="360"></canvas><script type="module" src="main.js"></script>
Why: The canvas is the render target. The module script lets you use top level await. This keeps the demo minimal and modern. Without the canvas, nothing will render.
Verify:
- Open the HTML file in a browser.
- Expected: a blank page with no errors.
- This confirms the shell loads.
If it fails:
- Symptom: script not found.
Fix: ensuremain.jsexists in the same folder.
Step 2: Request the adapter and device
Goal: Confirm WebGPU is available and create a device.
Actions:
- File path:
main.js - Add:
const adapter = await navigator.gpu.requestAdapter();if (!adapter) throw new Error("No GPU adapter");const device = await adapter.requestDevice();
Why: The adapter check is the first gate. The device is required to create pipelines and buffers. This step prevents silent failures. It also tells you whether the browser supports WebGPU.
Verify:
- Open DevTools Console.
- Expected: no error thrown.
- This confirms WebGPU is available.
If it fails:
- Symptom:
No GPU adapter.
Fix: enable WebGPU in browser flags.
Step 3: Configure the canvas context
Goal: Bind the canvas to WebGPU and set the format.
Actions:
- File path:
main.js - Add:
const canvas = document.getElementById("gfx");const context = canvas.getContext("webgpu");const format = navigator.gpu.getPreferredCanvasFormat();context.configure({ device, format, alphaMode: "opaque" });
Why:
The context is how WebGPU draws to the canvas. The format must match the pipeline output. This is the most common source of blank screens. Setting opaque avoids transparency bugs.
Verify:
- Expected: no errors in console.
- This confirms the context is configured.
If it fails:
- Symptom:
contextis null.
Fix: confirm the canvas ID and WebGPU support.
Step 4: Build a minimal pipeline
Goal: Create a pipeline that draws one triangle.
Actions:
- File path:
main.js - Add a shader string:
const shaderCode = `@vertexfn vs_main(@builtin(vertex_index) vi: u32) -> @builtin(position) vec4f {var positions = array<vec2f, 3>(vec2f(0.0, 0.6),vec2f(-0.6, -0.6),vec2f(0.6, -0.6));return vec4f(positions[vi], 0.0, 1.0);}@fragmentfn fs_main() -> @location(0) vec4f {return vec4f(0.2, 0.6, 1.0, 1.0);}`;
- Create the pipeline:
const module = device.createShaderModule({ code: shaderCode });const pipeline = device.createRenderPipeline({layout: "auto",vertex: { module, entryPoint: "vs_main" },fragment: { module, entryPoint: "fs_main", targets: [{ format }] },primitive: { topology: "triangle-list" }});
Why: A minimal shader removes variables. The pipeline ties the shader to the render pass. This is the core of the demo. Without a valid pipeline, nothing will render.
Verify:
- Expected: no shader compile errors in console.
- This confirms the pipeline is valid.
If it fails:
- Symptom: shader compile error.
Fix: check entry point names and WGSL syntax.
Step 5: Draw the frame
Goal: Issue one draw call and render the triangle.
Actions:
- File path:
main.js - Add:
function frame() {const encoder = device.createCommandEncoder();const pass = encoder.beginRenderPass({colorAttachments: [{view: context.getCurrentTexture().createView(),clearValue: { r: 0.05, g: 0.05, b: 0.08, a: 1 },loadOp: "clear",storeOp: "store"}]});pass.setPipeline(pipeline);pass.draw(3);pass.end();device.queue.submit([encoder.finish()]);requestAnimationFrame(frame);}frame();
Why:
This is the full draw loop. It clears the screen and draws three vertices. requestAnimationFrame keeps it running. This is the most reliable baseline for WebGPU demos.
Verify:
- Expected: a blue triangle appears on a dark background.
- This confirms the render loop works.
If it fails:
- Symptom: blank canvas.
Fix: ensure the pipeline and context use the same format.
Verify it worked
- The triangle is visible.
- No console errors appear.
- The output is stable.
Common mistakes
-
Symptom:
navigator.gpuis undefined.
Cause: WebGPU disabled.
Fix: enable WebGPU in browser flags. -
Symptom: Triangle flickers.
Cause: missingloadOp: "clear".
Fix: add the clear operation. -
Symptom: Shader compile errors.
Cause: WGSL syntax issues.
Fix: check the shader code carefully.
Cheat sheet
- Request adapter and device.
- Configure the canvas context.
- Build a minimal pipeline.
- Draw three vertices.
Next steps
- Add resize handling.
- Add a uniform for color.
- Keep the write up aligned with the code.
Related links
Final CTA
Get one triangle working, then build one small improvement at a time.