K

Compute pass for a canvas

Precompute per-frame data for a shader canvas.

When a GPU shader needs data that is expensive to compute per pixel, a particle sim, a spectral display, a physics step, precompute it in a compute pass. Author compute.ts next to the canvas's shader.ts. It runs on the shared graphics device each frame, before the fragment shader, and writes a storage buffer the fragment reads with the compute(i) macro.

Note

This is the visual pipeline: it runs per frame, best-effort, on the graphics device. It is not the audio GPU stage. For DSP on the GPU, see Process audio on the GPU.

Write the compute pass

fn(gid, g) runs once per gid.x, an output index. Guard against size. The body must begin with "use gpu". g is the same per-frame Globals the fragment shader gets: g.time, g.resolution, g.pointer, transport (g.bpm, g.beat, g.accent, ...). Write into output.$[i].

canvases/viz/compute.ts
import {
  defineCompute,
  output,
  f32,
  sin,
} from "@msh/device-sdk/device/gpu/compute";

export default defineCompute(
  (gid, g) => {
    "use gpu";
    const i = gid.x;
    if (i >= 1024) return;
    output.$[i] = 0.5 + 0.5 * sin(g.time + f32(i) * 0.1);
  },
  { size: 1024 },
);

opts.size is the output length in f32s, max 262144. opts.workgroupSize defaults to 64.

Read it in the fragment shader

Declare { compute: true } in the shader's defineShader opts, then read a slot with compute(i).

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

export default defineShader(
  (f, _g) => {
    "use gpu";
    const i = i32(f.uv.x * 1024.0);
    const v = compute(i);
    return vec4f(v, v * 0.4, 1.0 - v, 1.0);
  },
  { compute: true },
);

The canvas now has two files: compute.ts produces the buffer, shader.ts consumes it. Msh runs the compute pass first each frame, then the fragment. Editing either hot-reloads the canvas.

For the reading side of the fragment shader and its globals, see the GPU shader canvas reference. To stream data from process into a shader bucket instead, see Stream data to a shader.