K

Draw with a shader

Paint a canvas on the GPU with a fragment shader.

For a GPU-drawn visual, declare a canvas with type: "webgpu" and write a fragment shader instead of a paint file. Msh compiles it to WGSL and a WebGL2 fallback, so it runs everywhere. Editing the shader hot-reloads only that canvas.

Declare a GPU canvas in ui.ts

ui.ts
Canvas({ id: "viz", type: "webgpu", width: "fill", height: Theme.sizing(20) });

Write the shader

Make canvases/viz/shader.ts. The body must begin with "use gpu". f.uv is 0..1 across the canvas; g is the per-frame globals.

canvases/viz/shader.ts
import { defineShader, vec4f } from "@msh/device-sdk/device/shader";

export default defineShader((f, g) => {
  "use gpu";
  return vec4f(f.uv.x, f.uv.y, g.time, 1.0); // g.time animates
});

Note

Import only from @msh/device-sdk/device/shader, never typegpu. It re-exports defineShader, the constructors (vec2f/vec4f/...), and the std math functions.

The globals are all named: g.time, g.resolution, g.dpr, g.pointer / g.pointerActive, transport (g.bpm, g.beat, g.beatPhase, g.playing), and the node palette (g.accent, g.background, g.foreground).

Read the device's params (audio-reactive shaders) by declaring them in opts.params and taking the third argument p. Each is a live f32, the param's modulation-folded value.

export default defineShader(
  (f, g, p) => {
    "use gpu";
    return vec4f(p.gain * f.uv.x, p.cutoff, g.time, 1.0);
  },
  { params: ["gain", "cutoff"] },
);

A shader can also sample channels (opts.channels, read with channel("name", x)), buckets (opts.images, read with image("name", uv)), and the previous frame for feedback (opts.feedback: true, read with prev(uv)). To stream data into a bucket, see Stream data to a shader.

Full globals, macros, and rules in the GPU shader canvas reference.