The default workflow for developing shaders with vgpu

Follow these eight steps in order every time you write or change a shader. Each step has one exact command and one thing to check before you move on. Do not skip a step because the shader "looks fine": every claim about pixels must come from pixels you read.

1. Discover the docs

Bash
1
2
npx vgpu docs ls          # top-level paths: /guides plus one entry per package
npx vgpu docs ls /guides  # every guide, by file name

The corpus ships inside the package, so it works offline. Subcommands: ls, cat, grep, find, path, symbols. Full reference: npx vgpu docs cat cli.docs.md.

2. Read getting started

Bash
1
npx vgpu docs cat getting-started.md

ls /guides lists the file as getting-started.docs.md; both getting-started.md and /guides/getting-started.docs.md resolve to it. This is the only page that shows the current API end to end — read it before writing any init() call.

3. Read the concepts you actually need

Bash
1
2
3
npx vgpu docs cat concepts-effects.md   # fullscreen fragment work and set()
npx vgpu docs cat concepts-passes.md    # frame.pass, multi-pass work
npx vgpu docs cat concepts-frames.md    # frame batching and animation loops

Single fullscreen shader: read effects only. Anything with more than one target — blur pyramids, G-buffers, transmission — read passes and frames too.

4. Validate every WGSL file

Bash
1
npx vgpu check ./shaders/glass.wgsl

The command is check, not wgsl. It validates without running and prints the shader's reflection as JSON — binding names, groups, entry points — so you can confirm the names you will pass to set(). It exits non-zero with the validation errors when the shader is wrong. Run it on every .wgsl file, including pure helper modules.

5. Verify this machine can render

Bash
1
npx vgpu doctor --pretty

doctor acquires a real adapter and renders a real frame, then prints a JSON verdict. When it is unhealthy, apply the fix it prescribes literally — usually npx vgpu install-software-renderer, sometimes a VK_ICD_FILENAMES export. Re-run until it reports healthy. If it still fails after its own fixes, use the Docker fallback in Debugging shaders by extracting internal values.

6. Render a static frame in Node and look at it

Do not open a browser to see whether a shader draws. Render one frame headless, read the pixels, and write a PNG you can open:

TypeScript
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import { writeFileSync } from "node:fs";
import { PNG } from "pngjs";
import { init, effect, target } from "vgpu/node";
 
const width = 320;
const height = 180;
const gpu = await init();
const colorTarget = target(gpu, { size: [width, height] });
 
effect(gpu, `
  @fragment fn fs_main(@location(0) uv: vec2f) -> @location(0) vec4f {
    return vec4f(uv, 0.5, 1.0);
  }
`).draw(colorTarget);
 
const pixels = await colorTarget.read();          // RGBA bytes, row-major, no padding
const png = new PNG({ width, height });
png.data.set(pixels);
writeFileSync("frame.png", PNG.sync.write(png));
gpu.dispose();                                // stops Dawn's polling so the process exits

Keep the target small (a few hundred pixels wide) so the loop stays fast on a CPU renderer. Assert on pixels when you know the expected value; open the PNG when you need to judge composition. See Getting started for the same pattern without PNG encoding.

7. Connect it to the browser when the shader ships to a browser

Port the same code to vgpu (not vgpu/node) with surface(gpu, canvas, ...) and drive it from frameLoop(gpu, ...). Test it the way users run it, with the public API and deterministic frame submission: Browser testing with Playwright WebGPU.

8. Validate visually with agent-browser

Bash
1
2
3
4
agent-browser --session shader-check --webgpu --headed open http://localhost:3001/preview/glass
agent-browser --session shader-check --webgpu --headed wait 6000
agent-browser --session shader-check --webgpu --headed screenshot glass.png
agent-browser --session shader-check close

On Linux always pass --webgpu --headed: headless Chrome captures the canvas as black even when the render succeeded. Always check the pixels of the capture, never just its existence. Full recipe, including error-overlay checks: WebGPU screenshots with agent-browser.

Escalate to extraction, never to another guess

Stop iterating by eye and switch to numeric extraction the moment any of these is true:

  • the shader is multi-pass, or composes several passes (pyramids, G-buffers, ping-pong);
  • the math is non-trivial (refraction, dispersion, LOD selection, importance sampling);
  • a user reports a visual bug you cannot reproduce as a single wrong number.

In those cases apply Debugging shaders by extracting internal values from the start, before your first fix attempt. The evidence: while building the transmission example, two rounds of by-eye fixes changed the image without fixing it. Encoding the shader's internals as pixels and diffing them against a CPU reference found both root bugs — a clamped blur LOD and a G-buffer storing the wrong ray's surface — in a single pass.