atelier

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

  1. 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.
  2. 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.
  3. State lives in RGBA16F or RGBA8. Never filter or blend RGBA32F: on half of iOS the extensions for that cannot be enabled and the result is silently zero.
  4. Test the framebuffer, not the extension list. Build one target, check it is complete. WebKit has advertised float support the hardware did not have.
  5. 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.
  6. 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.
  7. Composite with max or mix. add runs away past a gain of 1; clamp it if offered.
  8. 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.
  9. 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.
  10. No readPixels in a recipe. It stalls the pipeline. Measure in a check, never in production.
  11. 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.
  12. No frame off screen, hidden, paused or at rest. The textures stay; the loop does not.
  13. A seed that must be the same everywhere is an integer hash in the shader. Canvas 2D anti-aliasing differs between renderers.
  14. 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.
  15. 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.
  16. 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.
  17. 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.
  18. 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-resolve and point-gather.
  19. The kit draws points and makes 32-bit targets. A PROGRAMS entry 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 is RGBA32F, never filtered, for positions read by texelFetch. A particle field is the feedback loop with the points as its source: see point-field.
  20. 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.
  21. Dye is a concentration and is shown as absorbance. Over a light ground, T = exp(ln(col) c depth), written premultiplied as a = 1 - min(T), rgb = ground (T - 1 + a). A flat colour times alpha is smoke, not ink.
  22. Over a dark ground, ink scatters. Absorbance there is invisible; use an opacity ramp in the token, chosen by the ground’s luminance.
  23. 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.
  24. 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.
  25. 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.
  26. 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.
  27. 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.
  28. A uniform may not take a built-in’s name. floor as 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.finish does 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 after
  • webgl2fundamentals-gpgpu: ping-pong framebuffers in raw WebGL2
  • sims-reaction-diffusion: the Gray-Scott equations and stencil
  • munafo-pearson-classes: the named pattern classes and their feed and kill
  • mdn-ext-color-buffer-float: which float formats become renderable
  • wcag-pause-stop-hide: why anything that runs past five seconds owes a control
  • wcag-three-flashes: the flash rule a fast automaton can cross
  • jenson-physarum: agent trails, the lab-only candidate
  • harris-gpu-gems-fluid: the pressure solve, the other lab-only candidate