Catalog / Technique
Feedback loop
Use when building an effect whose each frame is computed from the last one, as the Feedback TOP in TouchDesigner: trails, reaction-diffusion, cellular automata, advection, agent trails. Covers ping-pong targets in WebGL2, float formats, how such a loop ends, and which of these may sit on a content page.
- Loads as
technique-feedback-loop- Written for
- An agent, to follow
Feedback loop
An image with memory. Each frame reads the previous one, so the picture cannot be computed from a time value, cannot be scrubbed, and has no last frame unless one is designed in. That is the whole character and the whole cost.
When to use
- A field that grows, settles and stays: reaction-diffusion inside a word, an automaton filling a band.
- Something that answers the pointer and fades when it leaves: trails.
- A demonstration of a simulation, on a lab page, with a pause control.
Do not use it for:
- Anything scrubbed by scroll. A simulation only moves forward. The stateless cousin, a displacement that shrinks with progress, belongs to the one-shot harness.
- Behind or beside body text, ever. These are fine two-tone fields and stripe width is stroke width.
- A content page, if it has no ending (below).
Rules
- Decide the ending before writing a shader. One of three: it settles to a still; it runs only while driven and fades to exactly zero; it runs for a bounded time. None of these, no content page.
- A chain of operators is one shader. Transform is arithmetic on the lookup coordinate, Level on the colour, Composite at the end. Only a neighbourhood (blur, Laplacian) needs its own pass.
- State lives in
RGBA16ForRGBA8. Never filter or blendRGBA32F: on half of iOS the extensions for that cannot be enabled and the result is silently zero. - Test the framebuffer, not the extension list. Build one target, check it is complete. WebKit has advertised float support the hardware did not have.
- Store continuous state so that rest is zero. A half float is finest near zero. Gray-Scott with U near 1 grew at half the rate; stored as 1 - U it reached a bit-exact fixed point and stopped.
- A fade subtracts a floor. A multiplicative fade in 8 bits leaves a permanent ghost at 16/255; subtracting 1/255 a step takes the field to exactly zero, which is what lets a loop rest.
- Composite with
maxormix.addruns away past a gain of 1; clamp it if offered. - The simulation has its own size. Fixed texels, chosen for the look. Only the display pass knows the canvas size, so a phone at pixel ratio 3 runs the same simulation as a laptop.
- Count steps, cap them per frame, scale fades and rates by time. A step a frame is twice as fast on a 120 Hz screen.
- No
readPixelsin a recipe. It stalls the pipeline. Measure in a check, never in production. - End on the clock, slow into it, hand over, release. Ease the work per frame to zero over the last half second, copy the last frame to a 2D canvas in the same task, lose the context.
- No frame off screen, hidden, paused or at rest. The textures stay; the loop does not.
- A seed that must be the same everywhere is an integer hash in the shader. Canvas 2D anti-aliasing differs between renderers.
- Seed strongly enough, and in more than one place. A small dot at V 0.25 dies before it grows; at V 0.5 it grows. A lone spot at the spot-forming settings is stable and never divides. Confined to a word, seed the word itself, or letters are left empty by chance.
- A word used as a seed or a mask exists as text in the DOM. The canvas is decoration. Display sizes only; a stroke must be a pattern wavelength wide, and Turkish marks go first.
- A wake restarts the frame clock only from rest. A recipe that listens is woken by every pointer event; restarting the clock on each one gives every frame under a fast mouse a step of nothing, and the simulation freezes while the pointer moves. Reset the last frame time only when no frame is scheduled.
- An arrival starts disordered and a departure whole. A simulation that resolves to a seed must not begin as the seed: seed the dye with blots of noise and relax it toward the mark with a pull that rises as the cube of progress, so it is wild early and clean late. A departure begins as the mark and relaxes toward empty with the same curve, because advection conserves the dye: without a target of nothing, the word never leaves.
- A live heading may seed a simulation. One line at display size,
rasterised as the page renders it (the transform in the page’s
language, whitespace collapsed), registered through a Range and
the half-leading baseline, the real text visible under the canvas
throughout, and a crossfade at the end through the harness’s ease.
This replaces the research’s decision 12, which predates that
registration; see
ink-resolveandpoint-gather. - The kit draws points and makes 32-bit targets. A
PROGRAMSentry may be{ vertex, fragment }for a program with its own vertex shader;kit.points(program, uniforms, count, dst)draws that many vertices additively, blending on for the draw only; a target of kind 2 isRGBA32F, never filtered, for positions read bytexelFetch. A particle field is the feedback loop with the points as its source: seepoint-field. - Advect with gradient noise and a quintic fade. Value noise with
a cubic fade has a crease along every lattice line, and dye shears
along it into straight edges and right angles. This is what
“jagged” meant on
ink-resolve, and gradient noise removed it. - Dye is a concentration and is shown as absorbance. Over a light
ground,
T = exp(ln(col) c depth), written premultiplied asa = 1 - min(T),rgb = ground (T - 1 + a). A flat colour times alpha is smoke, not ink. - Over a dark ground, ink scatters. Absorbance there is invisible; use an opacity ramp in the token, chosen by the ground’s luminance.
- Seed density, not just coverage. Soft thresholds of two
octaves, multiplied by a finer noise; never one octave through a
narrow
smoothstep, which gives opaque puzzle pieces. - Diffuse a little in the step, explicitly, under 0.1. It is what
makes stretched filaments fade. Its reach is
sqrt(2 D n)texels; 0.25 smothers. - Ink that lives needs a moving field and a held pull. A fixed field with a constant pull settles to one image. Legibility is the stir-to-pull ratio, 0.05 to 0.13; liveliness is the drift, above 0.03. Anything that runs past five seconds owes a pause control.
- Rim darkening fires at every local maximum. Keep it at 0.5 or less (0.3 by default) and keep blot interiors smooth, or pools pebble and letters are outlined; a ring-mean gate did not help and goes negative on half float where the ring’s mean exceeds 1.
- The pull is per step, like the stir. Steps are counted in time; a frame’s pull split over its steps halves the pull per step on a 30 Hz screen and doubles the stir-to-pull ratio, past where a held word is legible.
- A uniform may not take a built-in’s name.
flooras a uniform fails the link, and the harness mounts nothing and logs nothing to the page. Time GPU passes closed by a one-texel read:gl.finishdoes not wait in Chrome.
Decisions
| Question | Choose | Because |
|---|---|---|
| Which ending? | Settles, if the dynamics allow; runs-while-driven for pointer effects; a bounded time as the backstop | The first two need no pause control; the third owes one past five seconds |
| Settle on a step count or a clock? | The clock, 4 s by default | A step count takes an unknown time on a slow device. Stopping early still leaves a still |
| Rest or release when idle? | A recipe the pointer can wake rests, at its tail and at its budget, and is released only by destroy; one it cannot wake is released by its budget | A field with its context released cannot answer the pointer that comes back |
| Float or 8 bits? | Half float for any continuous state, and for dye where the kit has it, with an 8-bit floor as the fallback; 8 bits for trails and automata | 8-bit Gray-Scott froze dead inside a thousand steps; 8-bit dye banded the washes and stalled the fade |
| Parameters or presets? | Presets for Gray-Scott, with the two dials only on the bench | The live region of the plane is a thin crescent |
| Free or confined? | Both, as a toggle. Confined is where it earns its place | Free growth is a familiar generative default |
| Pointer on a settling field? | Off by default; erase-and-heal as the option | On by default restarts the GPU on every mouse pass |
| Fluid or agents on a content page? | No. Lab page, behind paused |
Neither settles in the time it was run |
Tuning
Starting values are the lab’s. Ranges are from stills, not motion.
| Dial | Start | Range | One end | The other |
|---|---|---|---|---|
| Trails half-life (s) | 0.4 | 0.1 to 2.5 | A comet | A smear that takes seconds to clear |
| Trails zoom (a second) | 0.7 | 0.3 to 3 | Streams outward | Falls into a tunnel; 1 is a hard trail |
| Trails spin (rad/s) | 0.35 | -2 to 2 | Straight streaks | Visibly spinning |
| Gray-Scott steps a frame | 16 | 4 to 48 | Slow, visible growth | Fills in 2 s; more GPU a frame |
| Gray-Scott size (texels) | 256 | 128 to 512 | Bold, a quarter of the cost | Fine; four times the cost and the fill time |
| Gray-Scott diffusion | 1 | 0.25 to 1 | Features about half the size | The largest the model gives |
| Contour edge, softness | 0.2, 0.02 | 0.1 to 0.3, 0.005 to 0.2 | Hard two-tone | A soft relief |
| Automaton cell (CSS px) | 3 | 2 to 12 | Texture | Visible squares |
| Automaton rate (a second) | 20 | 6 to 60 | Readable steps | A shimmer; watch the flash rule |
| Automaton start density | 0.35 | 0.1 to 0.6 | Rule-dependent: Coral at 0.12 dies at once | Coral at 0.5 grows |
Rest state
A still: the last frame, as a plain 2D canvas, once the budget ends it.
For a trail, nothing: a transparent canvas at rest, released by the
budget or by destroy. Under reduced motion, without WebGL2, on a
software renderer, or without a float target where one is needed, the
element as found, or an img[data-still] the page supplies.
Cost
- To build: the harness once, medium; trails and an automaton small; Gray-Scott medium, mostly seeds, presets and the ending.
- To run, measured on one integrated GPU (AMD Radeon 610M, Chrome 154, ANGLE D3D11): Gray-Scott 0.06 to 0.09 ms a step at 256 by 256 in half float, so sixteen steps are about 1.5 ms of a 16.7 ms frame; trails and an automaton under a tenth of that. A software renderer is two hundred times slower and is refused. No phone has been measured. The stateful harness is 3.6 kB gzipped, a kilobyte over the one-shot, for the targets and passes. Memory: 8 bytes a texel in half float, 4 in 8 bits, for two textures; nothing after the handover.
- To the reader: a few seconds of motion in a region with no text, then a still. Never a pause button to find.
Checklist
- The ending is named in the recipe’s doc, and it is one of the three.
- The budget check passes.
-
npm run check:browser: under reduced motion no context is requested. -
npm run check:browser: hidden, no frame is asked for; back, it runs. -
npm run check:browser: the budget ends it with a plain still and no live context. -
npm run check:browser: thirty mounts and destroys at every stage leave nothing. - Looked at once on the stage at phone size, and once with the 30 fps preview.
- Any word used as a mask was looked at with
lang="tr"text. - Run once in Safari and once in Firefox before it is marked passed.
Recipes
feedback-trails: the pointer’s trail, resting when it leaves.reaction-diffusion: Gray-Scott, free or inside a word, freezing.automaton-fill: a Life-like automaton filling a band and freezing.point-field: points on a curl-noise field deposited into a fading trail, ending on a budget.ink-resolve: a word or a picture that resolves from stirred ink, or leaves as it; its Live version runs forever at budget 0 and owes the page a pause control.
Sources
derivative-feedback-top: the operator this family is named afterwebgl2fundamentals-gpgpu: ping-pong framebuffers in raw WebGL2sims-reaction-diffusion: the Gray-Scott equations and stencilmunafo-pearson-classes: the named pattern classes and their feed and killmdn-ext-color-buffer-float: which float formats become renderablewcag-pause-stop-hide: why anything that runs past five seconds owes a controlwcag-three-flashes: the flash rule a fast automaton can crossjenson-physarum: agent trails, the lab-only candidateharris-gpu-gems-fluid: the pressure solve, the other lab-only candidate