Grid programs

A grid simulation is a fixed-size field of cells (a displacement per cell, two chemical concentrations, a sort offset) that changes a little every frame. gridSim runs it: you describe the frame as an ordered list of stages built with op.*, and the engine runs that list every frame, owning the clock, the frame-delta clamp, the ready gate, the settle gate and the outputs the layer samples. A stage is bookkeeping (op.host, op.values, op.readyWhen, op.settle) or work (op.pass, op.cache, op.seedOnce, op.iterate, op.sortPass), and every frame ends with op.publish, which writes the state into the texture the fragment reads.

The build callback runs once per instance of the layer and returns the outputs, the clamp and the stages; anything it creates (buffers, passes, trackers) belongs to that instance. A settled field costs nothing: once op.settle sees nothing driving it, frames are skipped and the last published picture stays on screen.

Two shortcuts sit on top. The pointer fields in effects.pointerFields are whole grid programs you spread as ...gridSim(springLatticeField({...})) and read with a warp map. And for a small field whose arithmetic reads better as JavaScript loops, hostGridProgram runs the same named steps on the CPU and hostFieldTexture uploads the cells as a texture. When a wave field and a displacement are all you need, declare them with simulate.grid instead.

import {sim} from 'shaders/std'

Reach for it when

When you needUse
liquid or fabric that ripples after the cursor pushes itgridSim with springLatticeField and a warp map
a rule that must run N times per frame (reaction-diffusion)op.iterate after op.values, then op.publish
a sort that settles a little more every frameop.sortPass, with op.publish reading its side
the state must be reset on the first frame or a seed changeop.seedOnce
a pass that needs the child's picture, which arrives lateop.readyWhen and op.cache
the simulation should sleep once nothing drives itop.settle
this frame's values written before the passes read themop.values
the canvas size or aspect inside a stagetrackedViewport
a small field stepped in plain JavaScripthostGridProgram with hostStep, published by hostFieldTexture

Example

import {defineShader, p, sim, effects, warps, transformEdges} from 'shaders/std'

const {gridSim} = sim.grids
const {springLatticeField} = effects.pointerFields

// Liquify: a spring lattice the cursor pushes, published as a displacement field the warp map reads.
// The pointer field packages the whole stage list (pointer tracking, values, the step passes, the
// settle gate, publish); this definition only names the props and the output.
export const Liquify = defineShader({
  name: 'Liquify',
  usesPointer: true,
  props: {
    intensity: {default: 10},
    stiffness: {default: 3},
    damping: {default: 3},
    radius: {default: 1},
    edges: {default: 'stretch', transform: transformEdges, compileTime: true},
  },
  ...gridSim(springLatticeField({
    stiffness: p('stiffness'), damping: p('damping'), radius: p('radius'),
    output: 'displacement',
  })),
  // Bend the incoming coordinate by the field, scaled by intensity, then handle the edges.
  map: warps.liquidDisplace({intensity: p('intensity'), output: 'displacement'}),
  // Before the field exists (no GPU yet), the warp passes the layer through untouched.
  uvRemapIdentityWhen: ({computeOutputs}) => !computeOutputs?.displacement,
})

Types

TypeWhat it is
GridFrameOne frame, as every stage sees it: the clamped delta, the clock, and your props.
GridFrameParamsWhat the renderer reports each frame: the pointer in uv, the frame delta in seconds, the canvas size in pixels.
GridRootThe GPU device handle the build callback receives, for creating the buffers and passes your stages use.
GridSimConfigWhat a GPU grid simulation declares: its outputs, its frame-delta clamp, and its stages.
GridStageOne stage of the frame.
HostFrameOne frame of a JavaScript-stepped grid: the clamped delta, the clock and the pointer in uv.
HostStepOne named JavaScript step.
SortPassStageA sort chain's stage, which also reports which of its two buffers holds the current result.

Words