Skip to content

The Visual Graph

A visual in resonon is a node in a graph, whether it is a shader you wrote or a ready-made operator like gfx.Blur. Nodes are wired with the same two operators that route audio, and the graph is turned into a sequence of GPU passes every time it changes. This page explains the rules of that graph and why they are shaped the way they are.

Every visual is made with a name: gfx.Blur("soft"), gfx.load_shader("plasma", "shaders/plasma.glsl"). The name is its identity for the whole session. The first time a name is used, the operator is built; after that, the same name hands back the same operator.

This is what makes a visual editable while it plays. Load an edited plasma.glsl again and the operator already on screen takes the new source — its wiring and its bound uniforms stay, because nothing about which operator it is has changed. It is the same idea as a track: running AudioTrack("drums") again gives you the drums, not a second drum track.

Two consequences follow:

  • A name outlives the variable holding it. Rebind soft to something else, and gfx.Blur("soft") still has its radius when you come back to it. Only a Reset forgets names.
  • Using a name for a different kind of operator — gfx.Edge("soft") after gfx.Blur("soft") — replaces it. The new operator starts with no inputs and no bindings, and whatever read from the old one reads from the new one.

A helper that builds nodes with fixed names would hand every caller the same nodes, so a helper takes the names as arguments.

>> fills the main input. a >> b feeds a into b’s primary input — the sampler iChannel0 — and evaluates to b. So a >> b >> c reads left to right, a chain is itself a value, and >> screen at the end puts the result on the display. >> never changes a: the upstream node can be wired into any number of other places.

<< binds anything else, by name. What it binds depends on the value on its right:

  • a node becomes a texture input — the second input of Composite, the displacement field of Displace;
  • a number, a signal, a meter feed or a short array becomes a uniform.

These are the same two operators as in the audio graph — >> routes, << sets — and they mean the same there: a track >> a bus, a parameter << a signal.

A node used by several consumers is one node, not several copies: the graph is de-duplicated by identity. When a field feeds two blurs, the noise is drawn once per frame and both blurs sample that one result. Fan-out costs nothing beyond the consumers themselves.

Within a frame the graph is acyclic: every edge points from a node drawn earlier to one drawn later. That is what lets the whole graph be drawn in one ordered pass list.

A graph that fed its own output back in would be a cycle, and a cycle has no drawing order. Feedback breaks the cycle in time instead: gfx.feedback(fn(prev) { … }) gives the graph inside it prev, the output of the previous frame.

The renderer keeps two buffers for the node the closure returns: it draws into one while prev reads the other, then swaps them. That swap is the only thing feedback adds — everything inside the closure is an ordinary acyclic graph. gfx.feedback has no name of its own, because prev is not an operator: the buffer pair belongs to the named node the closure returns.

Because the buffers belong to a name, a trail survives your edits. Editing that operator’s shader, rewiring it, or inserting an operator upstream keeps the trail flowing. It also carries across a section cut when the next section draws the same name. When a section’s screen lane stops drawing the operator — over a gap, or in a section without it — the trail holds still and goes on from there when the operator is drawn again, as an uncooked TouchDesigner TOP keeps its image. It starts over from black when the name becomes another kind of operator, when the operator is no longer routed to screen at all, when the render size or screen.quality changes, when the visuals window closes, and on a Reset.

To start a trail over on purpose, call reset() on that operator: look.reset() clears its buffers on the next frame, like the Reset pulse on a TouchDesigner Feedback TOP. Nothing else in the graph changes and nothing recompiles, and the trail builds again from black.

Every edit to a visual that reaches screen — a bind, a new edge, a rewire — takes effect when the block you ran finishes. Only the block’s end state is drawn or checked, so a block may pass through a half-wired graph on its way to a complete one.

An edit that would break the graph — a cycle outside gfx.feedback, say — keeps the last good picture on screen. The error is shown on the block that caused it and stays until a later block fixes the graph.