import { GpuComputeNode, GpuFragmentParams } from '../../gpu/contract'; import { ComputeStep } from '../../gpu/compute'; /** The GPU device handle the build callback receives, for creating the buffers and passes your stages use. */ export type GridRoot = NonNullable['root']; /** What the renderer reports each frame: the pointer in uv, the frame delta in seconds, the canvas size in pixels. */ export interface GridFrameParams { pointer?: { x: number; y: number; }; deltaTime?: number; dimensions?: { width: number; height: number; }; } /** One frame, as every stage sees it: the clamped delta, the clock, and your props. */ export interface GridFrame { /** Seconds since the last frame, never more than `clampDt`. */ dt: number; /** Wall-clock milliseconds this frame. */ now: number; /** A numeric prop's current value, or `fallback` when it is not a number this frame. */ num(key: string, fallback: number): number; /** Any prop's current value. */ getCpuValue(key: string): unknown; frameParams: GridFrameParams; } /** * One stage of the frame. Build them with `op.*` rather than by hand. * * Each frame runs every stage's `ready`, then every `tick`, then every `skip`, then every * `emit`, in list order. */ export interface GridStage { /** Return false to run nothing this frame (an input the stage needs has not arrived yet). */ ready?(): boolean; /** Bookkeeping before any gate: pointer smoothing, activity flags. */ tick?(f: GridFrame): void; /** Return true to skip the whole frame. The last published texture stays on screen. */ skip?(f: GridFrame): boolean; /** Add this stage's GPU passes and value writes to the frame. */ emit?(f: GridFrame, nodes: ComputeStep[]): void; } /** What a GPU grid simulation declares: its outputs, its frame-delta clamp, and its stages. */ export interface GridSimConfig { /** The textures the fragment samples, by the name it reads them under in `computeOutputs`. */ outputs: Record; /** Receive the child's picture once it exists (it is allocated after the layer composes). */ bindInputs?: (resolve: (key: string) => { texture: unknown; } | undefined) => void; /** Longest frame step accepted, in seconds. 0.016–0.05 keeps a tab switch from jumping the field. */ clampDt: number; /** What to do on a frame with no elapsed time: run anyway (default) or skip it. */ zeroDt?: 'run' | 'skip'; /** The frame program, in order. */ stages: GridStage[]; } /** * A field of cells advanced every frame by an ordered list of stages. Spread it into a * definition next to a `gpu.fragment` that samples one of its outputs. * * The build callback runs once per instance of the layer, with the GPU device, and returns the * outputs, the frame-delta clamp and the stages. Return null to run nothing (a required child is * missing). Without a GPU the fragment should fall back to the child or to transparent. * * @example * ```ts * ...gridSim((params, root) => ({outputs: {state}, clampDt: 0.05, stages: [op.values('write', (f) => paramsU.write({dt: f.dt, radius: f.num('radius', 0.4)})), op.pass(update), op.publish(() => output)]})) * ``` * @tip Everything created in the build callback (buffers, passes, trackers) belongs to that layer instance. Create nothing at module scope that holds per-frame state. * @see op, hostGridProgram, simulate, fluidSim, feedbackSim */ export declare function gridSim(build: (params: GpuFragmentParams, root: GridRoot) => GridSimConfig | null): { compute: GpuComputeNode; }; /** A sort chain's stage, which also reports which of its two buffers holds the current result. */ export interface SortPassStage extends GridStage { side(): 'A' | 'B'; } /** * The stages a grid frame is built from. List them in `stages` in the order they should run. * * Bookkeeping: `host` (before any gate), `values` (write this frame's values), `readyWhen` * (wait for an input), `settle` (sleep once the field is still). Work: `pass` (one GPU pass), * `cache` (a pass picked per frame), `seedOnce` (a one-shot reset), `iterate` (a pass N times), * `sortPass` (a compare-swap chain), `publish` (write the result the layer samples). * * @example * ```ts * stages: [op.readyWhen(() => ready), op.values('write', (f) => paramsU.write({dt: f.dt})), op.iterate({count: () => 8, step: (i, nodes) => nodes.push(react.with(i % 2 ? groupBA : groupAB))}), op.publish(() => output)] * ``` * @tip A `values` stage sits before the passes that read it. Stages run in list order. * @see gridSim */ export declare const op: { /** * A named step that runs before any gate: pointer smoothing, activity flags, anything a * later `settle` needs to know. * * @example * ```ts * op.host('cursor', (f) => { active = (f.frameParams.pointer?.x ?? 0.5) !== lastX }) * ``` * @see values, settle */ readonly host: (name: string, run: (f: GridFrame) => void) => GridStage; /** * A named step that writes this frame's values for the passes after it. * * @example * ```ts * op.values('write', (f) => paramsU.write({dt: f.dt, radius: f.num('radius', 0.4)})) * ``` * @tip Read props here with `f.num('prop', fallback)` so a prop that is briefly not a number cannot reach the GPU as `undefined`. * @see host, pass */ readonly values: (name: string, run: (f: GridFrame) => void) => GridStage; /** * Sleep the simulation once nothing has driven it for `settleMs` of simulated time. Frames * are skipped and the last published texture stays on screen, so a still field costs nothing. * * `activeWhen` says whether something drove the field this frame. `settleMs` is how long the * field takes to fade after the last activity, in milliseconds. * * @example * ```ts * op.settle({activeWhen: () => moving, settleMs: (f) => 4000 / Math.max(f.num('decay', 1), 0.1)}) * ``` * @tip Simulated time, not wall-clock: a hidden tab does not count, so the field is still fading when the user comes back. * @see host */ readonly settle: (opts: { activeWhen: (f: GridFrame) => boolean; settleMs: (f: GridFrame) => number; }) => GridStage; /** * Run nothing until `fn` returns true, typically once the child's picture has arrived * through `bindInputs`. * * @example * ```ts * op.readyWhen(() => lumaReady) * ``` * @see cache */ readonly readyWhen: (fn: () => boolean) => GridStage; /** * One GPU pass, the same every frame. * * @example * ```ts * op.pass(update) * ``` * @see cache, iterate, publish */ readonly pass: (step: ComputeStep) => GridStage; /** * A GPU pass chosen per frame, for a pass whose input is rebound after the frame starts. * * @example * ```ts * op.cache(() => lumaPipeline) * ``` * @see readyWhen, pass */ readonly cache: (step: (f: GridFrame) => ComputeStep) => GridStage; /** * The last stage: write the state into the texture the layer samples. Chosen per frame so it * can follow whichever buffer holds the current result. * * @example * ```ts * op.publish(() => outputPass.with(sort.side() === 'A' ? outputBgA : outputBgB)) * ``` * @see pass, sortPass */ readonly publish: (step: (f: GridFrame) => ComputeStep) => GridStage; /** * A one-shot reset run before the rest of the frame whenever `staleWhen` says the state is * stale: on the first frame, or when a seed prop changes. * * @example * ```ts * op.seedOnce({pass: seed, staleWhen: () => needsSeed, onSeed: () => { needsSeed = false }}) * ``` * @see iterate */ readonly seedOnce: (opts: { pass: ComputeStep; staleWhen: (f: GridFrame) => boolean; onSeed?: () => void; }) => GridStage; /** * Run a step `count` times this frame. Your `step` adds the pass for iteration `i` and picks * which buffer it reads and writes. * * @example * ```ts * op.iterate({count: (f) => Math.round(f.num('speed', 8)), step: (i, nodes) => nodes.push(react.with(i % 2 ? groupBA : groupAB))}) * ``` * @tip A reaction-diffusion field reads smoother when you raise the iteration count than when you raise the step size. * @see pass, sortPass */ readonly iterate: (opts: { count: (f: GridFrame) => number; step: (i: number, nodes: ComputeStep[], f: GridFrame) => void; }) => GridStage; /** * A compare-and-swap sort that converges a little more every frame: `passes` swaps per frame, * alternating odd and even pairs and the two buffers. `side()` tells `publish` which buffer * holds the current order. * * @example * ```ts * const sort = op.sortPass({passes: () => 1 + Math.round(strength * 4), pass: (parity, side) => (parity ? swap1 : swap0).with(side === 'A' ? groupAB : groupBA)}) * ``` * @tip The alternation continues across frames instead of restarting, which is what makes the sort settle bit by bit. * @see publish, iterate */ readonly sortPass: (opts: { passes: (f: GridFrame) => number; pass: (parity: 0 | 1, side: "A" | "B") => ComputeStep; }) => SortPassStage; }; /** * The canvas size in pixels, kept current across resizes, in the two forms a stage wants. * * `tracked()` is the size at the last resize, for a pass that works in the canvas's own pixels. * `safe(fp)` prefers the size this frame reports and never returns less than 1, for an aspect * ratio. * * @example * ```ts * const viewport = trackedViewport(params) * op.values('write', (f) => { const s = viewport.safe(f.frameParams); paramsU.write({aspect: s.width / s.height}) }) * ``` * @see gridSim */ export declare function trackedViewport(params: GpuFragmentParams): { tracked(): { width: number; height: number; }; safe(fp: GridFrameParams): { width: number; height: number; }; }; /** One frame of a JavaScript-stepped grid: the clamped delta, the clock and the pointer in uv. */ export interface HostFrame { /** Seconds since the last frame, never more than `clampDt`. */ dt: number; now: number; pointer: { x: number; y: number; }; frameParams: unknown; } /** One named JavaScript step. Return `'skip'` to end the frame early. */ export interface HostStep { name: string; run(f: HostFrame): void | 'skip'; } /** * One named step of a JavaScript-stepped grid. Return `'skip'` to end the frame here, which is * how an idle gate is written. * * @example * ```ts * hostStep('dtGuard', (f) => (f.dt <= 0 ? 'skip' : undefined)) * ``` * @see hostGridProgram */ export declare const hostStep: (name: string, run: (f: HostFrame) => void | "skip") => HostStep; /** * A frame program for a small grid stepped in plain JavaScript: the steps run in order every * frame until one returns `'skip'`. Hand the result to `onBeforeRender`. * * Use it when the field is small (a 32×32 flow grid) and the arithmetic reads better as loops. * Publish the cells with `hostFieldTexture` in the last step. * * @example * ```ts * onBeforeRender(hostGridProgram({clampDt: 0.016, steps: [hostStep('advect', (f) => advect(f.dt)), hostStep('publish', () => { pack(field.texData); field.upload() })]})) * ``` * @see hostStep, hostFieldTexture, gridSim */ export declare function hostGridProgram(opts: { clampDt: number; steps: HostStep[]; }): (fp: unknown) => void; /** * The texture a JavaScript-stepped grid is read through. Pack the cells into `texData` (four * half-floats per cell, row by row), call `upload()`, and sample `texture` in the fragment. * * `size` is the grid's side in cells. The texture filters smoothly, so a coarse grid reads as * a continuous field. * * @example * ```ts * const field = hostFieldTexture(params, {size: 32, label: 'flow'}) * ``` * @tip Values go in as half-floats: convert each number with `toHalfFloat` before writing it into `texData`. * @see hostGridProgram */ export declare function hostFieldTexture(params: GpuFragmentParams, opts: { size: number; label: string; }): { texData: Uint16Array; texture: ReturnType; upload(): void; }; //# sourceMappingURL=grids.d.ts.map