Login - Create Account

Loops and Recursions

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:



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:

  • The Accumulated slave component outputs the combined result of all previous Loop iterations, which is then combined with the result of the current iteration and fed into Loop’s Accumulator input. This slave component is necessary for any Loop to work and cannot have multiple copies.
  • The optional Position slave component outputs the ‘completion percentage’ of the Loop as normalized iteration number converted to color.
  • The optional Iteration slave component outputs raw iteration number as an HDR color with RGB values corresponding to the number of current iteration (1, 2, 3...).
  • The optional Randomizer slave component produces a normalized random value (converted to color) that is unique for each loop iteration. Multiple Randomizers can have different Variation settings (i.e. random seeds).

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

  • The maximum number of nested loops is 16.
  • The maximum number of iterations in a single loop is 1000.
  • The maximum total number of iterations of all loops combined is 3000 when there are no Script components inside the loop, and 1000 when a Script component is present in the loop. If the limit is exceeded in the latter case, a Lua script error “C stack overflow” will occur.
  • Bitmap-based components such as Blur, Sharpen or High Pass cannot be used inside the loop. That would require rebuilding the entire input and output bitmap caches for each such component for each loop iteration, which would lead to insane memory requirements.
  • Numeric (gray) inputs of the subtree cannot receive values that differ from iteration to iteration. This is because all slave components of the Loop are of the Map type (green), and thus cannot be connected to numeric inputs.

Filter Forge's amazing node based editor has been invaluable in giving us the power to create textures that make the most of today's real-time pixel shaders.

Karl Wickens
Visual Effects Lead Artist
Electronic Arts

See All Testimonials

Get a 75% discount on any edition!
Expires soon!
Buy today – save up to $300!

Filter Forge is an excellent filter creation tool with a strong community spirit that will enhance your Photoshop creations.”

Nick Mead

“Filter Forge is an incredibly powerful new Photoshop Plugin. It's simply amazing. It gives you the power to create your own filters (you must run them from inside Filter Forge though) without any programming knowledge at all.”

Enrique Flouret

Follow filterforge on Twitter