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].
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).
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.