YOUR ACCOUNT

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:

Loops and Recursions

Loop Component

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.

How Loop Works

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:

Multistar

Multistar.ffxml

Here's how it works:

  1. The number of iterations, 5 for this example, is set in the properties of the Loop component.
  2. The Accumulated slave component provides the combined result of all previous iterations to the loop subtree.
  3. The loop subtree, consisting of a single Free Polygon component in this example, modifies the accumulated result of all previous iterations to produce the current iteration. A Position slave component is used to make the star rotation angle different from iteration to iteration.
  4. After being formed by the loop subtree, the current iteration is sent to Loop;
  5. And on the next iteration, it will appear at the output of Accumulated. Repeat from step 2 four more times.

Slave Components

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:

Multiple Randomizers

Multiple Randomizers.ffxml

Nested Loops

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:

Nested Loops

Nested Loops.ffxml

Loops Are Recursions

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.

Caution: Recursions Are Explosive!

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:

Innocent-looking loop

If you unroll this loop by creating the corresponding component tree manually, here's what you get:

Unroll the loop

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:

Minimize the number of sampling calls to Accumulated

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:

Limit the number of samples to Accumulated

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.

Limitations of Loops