K

Stream data to a shader

Push realtime data to a GPU visual each block.

To feed a shader a stream of live data (a spectrogram, a waterfall, a scope history), declare a bucket, fill it each block in process, and read it by name in the shader.

Declare a bucket in describe

A ring is a scrolling history: frames rows of width texels, newest row each block. FMT_R16F is one half-float per texel, a quarter the bytes of full RGBA.

index.ts
m.ring("spectro", 256, 128, FMT_R16F); // 256 freq columns x 128 time rows

m.texture(name, w, h, format?) and m.array(name, len, format?) are the other shapes.

Fill it each block

Write the newest column. For FMT_*32F pass a Float32Array to ctx.writeBucket; for a packed format, pack the bytes yourself (f32ToF16) and use ctx.writeBucketBytes.

index.ts
for (let i = 0; i < WIDTH; i++) {
  const h = f32ToF16(level[i]); // map magnitude to 0..1, then to f16
  this.row[i * 2] = <u8>(h & 0xff);
  this.row[i * 2 + 1] = <u8>((h >> 8) & 0xff);
}
ctx.writeBucketBytes(0, this.row); // appends one ring row

Read it in the shader

Declare the name in opts.images and sample it with image("name", uv).

canvases/viz/shader.ts
export default defineShader(
  (f, _g) => {
    "use gpu";
    const level = image("spectro", f.uv).x; // R16F lands in .x
    return vec4f(level, level * 0.4, 1.0 - level, 1.0);
  },
  { images: ["spectro"] },
);

The host rotates the ring and uploads only changed buckets, and only while the canvas is on screen. See the buckets reference for shapes and formats, and the GPU shader canvas for the reading side.