Small WebGPU demos break for boring reasons.

This post shows a clean fix pass and how to verify the rebuild.

You will isolate one issue at a time and prove the fix works.

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:

  • fix a broken triangle render
  • clean up the render loop
  • verify the output is stable

"Fix one thing, then verify it, then move on."

What this is

High level: This is a minimal rebuild pass for a broken WebGPU demo.

Low level: You verify the adapter, fix the pipeline, and confirm the draw call runs.

Key terms:

  • Render loop: A function that draws each frame.
  • Pipeline: The config for shaders and output.

What you need

  • A WebGPU capable browser
  • A minimal WebGPU demo project

Start to Finish

Step 1: Confirm the adapter and device

Goal: Ensure WebGPU is available before debugging anything else.

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: If the adapter is missing, nothing else matters. This is the first hard check. It reduces false debugging. It also tells you whether the browser supports WebGPU.

Verify:

  • Run the demo and check the console.
  • Expected: no "No GPU adapter" error.
  • This confirms WebGPU is available.

If it fails:

  • Symptom: adapter is null.
    Fix: enable WebGPU in the browser flags.

Step 2: Rebuild the pipeline

Goal: Create a minimal pipeline that can draw a triangle.

Actions:

  • File path: main.js
  • Add:
    const pipeline = device.createRenderPipeline({
    layout: "auto",
    vertex: { module, entryPoint: "vs_main" },
    fragment: { module, entryPoint: "fs_main", targets: [{ format }] },
    primitive: { topology: "triangle-list" }
    });

Why: If the pipeline is wrong, nothing draws. A minimal pipeline reduces the number of moving parts. This also makes shader issues easier to spot. It is the fastest route back to a working demo.

Verify:

  • Run the demo and look for a triangle.
  • Expected: a triangle appears.
  • This confirms the pipeline works.

If it fails:

  • Symptom: blank canvas.
    Fix: verify shader entry point names.

Step 3: Clean the render loop

Goal: Ensure each frame clears and draws correctly.

Actions:

  • File path: main.js
  • Use this loop:
    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: A clean render loop removes flicker and blank frames. It also makes the draw call obvious. This is the simplest loop that still updates every frame. It is the most reliable baseline.

Verify:

  • Expected: the triangle stays visible without flicker.
  • This confirms the loop works.

If it fails:

  • Symptom: triangle flickers.
    Fix: ensure you use loadOp: "clear" and submit the command buffer.

Verify it worked

  • A triangle appears.
  • The render loop runs without errors.
  • The output is stable.

Common mistakes

  • Symptom: Nothing renders.
    Cause: Shader entry points mismatch.
    Fix: align entry point names in code and shader.

  • Symptom: Canvas is black.
    Cause: No clear color or draw call.
    Fix: add clearValue and draw(3).

  • Symptom: Device lost errors.
    Cause: driver issues.
    Fix: reload and try again.

Cheat sheet

  • Check adapter and device.
  • Use a minimal pipeline.
  • Use a clean render loop.

Next steps

  • Add resize handling.
  • Add a uniform for color.

Related links

Final CTA

Fix one issue at a time and verify after every change.