In a surprising turn of events, Filter Forge 4.0, initially planned as a boring release with some usability improvements, unexpectedly introduces a new component – and a paradigm – that adds a whole new dimension to filter creation:
Loop is immensely powerful: it brings two key concepts of programming – nested loops and recursions – into a visual environment of Filter Forge, in a way that doesn't require you to write any code. Arguably, this is the biggest addition to Filter Forge since version 1.0 in terms of image generation abilities.
However, proceed with caution. At the core of the Loop component lies recursion, a concept adapted from programming languages with all its benefits and hazards (such as potential exponential growth in rendering time). Unlike many other Filter Forge components that produce visible results no matter how you connect them, Loops require a more deliberate approach. That's why we put them into the new Advanced component category, together with Scripts.
In programming, a loop is a construct that repeats a certain sequence of commands multiple times, with possible variation from iteration to iteration. Similarly, the new Loop component in Filter Forge lets you render a subtree of components multiple times and combine results of all these iterations into a single output image.
Let's take a look at this simple example:
Here's how it works:
The Loop component cannot work without its slave components. Slave components are permanently linked to their master Loop and cannot be detached. Their outputs should be connected to the subtree of their master component, otherwise they will have no effect on the output of the Loop component.
The key thing to understand about slave components is that their output changes depending on the current Loop iteration. This means that you can use them to vary any number of parameters in the iterated subtree from iteration to iteration.
The Loop component has four slave components: Accumulated, Position, Iteration and Randomizer:
You can have multiple slave components linked to a single Loop (except Accumulated, which cannot have multiple copies). This example uses multiple Randomizers, each with their own color range and Variation (i.e. random seed) for randomizing multiple inputs of the Loop subtree:
Loops can be nested. In the example below, an inner loop creates a row of 5 circles, which is then duplicated 5 times by the outer loop. Note that slave components of the outer loop can affect components in the subtree of the inner loop:
Technically, all loops in Filter Forge are recursions. In programming, recursion occurs when a function calls itself once or multiple times. In Filter Forge, recursion means a subtree of components plugged into itself, i.e. one that takes its own output as an input.
That's exactly what the Loop component does: essentially, it provides user interface and internal logic (most importantly, the termination condition) for circular connections. Otherwise, such connections are not allowed by Filter Forge.
When we say "recursion" we mean it: internally, Filter Forge's loops are implemented via recursive calls to the sampling function of the subtree connected to the Accumulator input of the Loop component. So if you're not careful with the number of recursive calls (i.e. sampling calls to Accumulated), you get a recursion growing exponentially.
Consider, for example, this innocent-looking loop evaluating 3 components over 7 iterations:
If you unroll this loop by creating the corresponding component tree manually, here's what you get:
That's 3 * (1 + 2 + 4 + 8 + 16 + 32 + 64) = 381 components that must be evaluated in order to render the result. Increase the iteration count from 7 to 8 and boom, you get 3 * (1 + 2 + 4 + 8 + 16 + 32 + 64 + 128) = 765 components: the number of components basically doubles with every additional iteration! Wait, it gets worse: if you change the loop subtree so that it sends three samples to Accumulated instead of two as shown above, you'll get 3 * (1 + 3 + 9 + 27 + 81 + 243 + 729 + 2187) = 9840 components!
The technical term for this is exponential recursion. In programming, it occurs when a function calls itself more than once, and in Filter Forge it occurs when the Loop subtree requests more than one sample from the Accumulated slave component.
When you have an exponential recursion with a relatively high number of iterations in your filter (which can be as low as 7 or 8), the rendering times may grow so big that you may never see any rendered result. Thankfully, Filter Forge's user interface remains (somewhat) responsive during such explosions, so you can dial the number of loop iterations down.
Here are three rules of thumb for keeping exponential recursions in check:
1. Keep the number of Loop iterations as low as possible.
2. Minimize the number of sampling calls to Accumulated. That is, all components in the Loop subtree between the Accumulated slave component and the Accumulator input of the Loop should all have as few outgoing connections as possible:
To be more precise, this rule talks about samples requested from Accumulated at different coordinates, for example by components such as Noise Distortion, Rotation or Offset. If multiple samples hit Accumulated at the same coordinates, Filter Forge's sample cache may be able to prevent additional sample calls. However, sample cache implementation is different from component to component, so this solution may not work for some components.
3. To avoid exponential recursions altogether, limit the number of samples to Accumulated to one per loop, as shown below. Such recursions unroll into neat linear chains of components, as opposed to ever-branching trees of exponential recursions:
Also note that limiting the number of samples to Accumulated won't help you deal with components that sample their inputs multiple times at different coordinates, such as Edge Detector and Derivative. Having a single such component in a loop between Accumulated and Accumulator guarantees an exponential recursion.