K

GPU shader canvas

Draw a canvas with a fragment shader instead of paint.ts.

For a GPU-drawn visual, declare a canvas with type: "webgpu" and write a fragment shader instead of a paint.ts. Author it in TGSL (TypeScript) at canvases/<id>/shader.ts, or raw WGSL at canvases/<id>/shader.wgsl. Msh compiles it to WGSL (WebGPU) and GLSL ES 3.00 (WebGL2 fallback), so it runs everywhere. Editing it hot-reloads only that canvas.

// ui.ts
Canvas({ id: "viz", type: "webgpu", width: "fill", height: Theme.sizing(20) });
// 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);
});

defineShader(fn, opts?). The body must begin with the "use gpu" directive.

Arguments

ArgIs
fFragment input. f.uv is 0..1 across the canvas, origin top-left.
gPer-frame globals (below).
pYour declared params, each a live f32 (0..1, modulation-folded).

Globals (g)

time (seconds, animate with this), resolution, dpr, pointer, pointerActive, transport (bpm, beat, beatPhase, playing), and the node palette (accent, background, foreground).

Reading device data

Declare names in opts, read them by name in the body.

opts fieldDeclared withRead with
params: [...]m.addParamp.name
channels: [...]m.channelchannel("name", x) (filtered) or channelTexel("name", i) (exact bin)
images: [...]m.texture / ring / arrayimage("name", uv)vec4f
feedback: trueprev(uv) → the previous frame, for trails and glow
compute: truea sibling compute.tscompute(i) → the compute pass's f32 at index i

Channel names match the roles: "spectrum", "spectrumPost", "preSpectrum", "curve". See Data buckets for images, and Compute pass for a canvas for compute.

export default defineShader(
  (f, g, p) => {
    "use gpu";
    const level = image("spectro", f.uv).x; // a ring bucket
    return vec4f(level * p.gain, level * 0.4, g.beatPhase, 1.0);
  },
  { params: ["gain"], images: ["spectro"] },
);

Math

Import from @msh/device-sdk/device/shader: constructors (vec2f, vec3f, vec4f, f32, ...) and std functions (add, sub, mul, div, dot, cross, normalize, length, mix, clamp, smoothstep, step, sin, cos, pow, fract, min, max, abs, ...). You can write a + b on vectors inside a "use gpu" body, but TypeScript can't model that, so the std functions keep the editor green.

Rules

  • Start the body with "use gpu".
  • Import only from @msh/device-sdk/device/shader, never typegpu.
  • The fragment body itself must be valid in both WGSL and GLSL ES 3.00: no storage buffers, no compute in the fragment, no unbounded loops. The compiler rejects these up front. To precompute per-frame data on the GPU, run a separate compute pass (compute.ts) and read it with compute(i).

Raw WGSL

Prefer hand-written WGSL? Use canvases/<id>/shader.wgsl with one @fragment entry taking @location(0) uv: vec2f and returning @location(0) vec4f.

How-to: Write a GPU shader. To run DSP on the GPU instead of visuals, see GPU audio.