What are [unroll] and [loop] ? When to use them?

What are [unroll] and [loop] ? When to use them?

these are hints for the hlsl compiler on how you want loop unrolling to be done. Unless you KNOW that you need these and why, you should never need to make use of them.

A slightly longer explanation:

[loop] tells the compiler to keep a for loop an actual loop. Basically, keep it working probably like you expected it to.

[unroll] tells the compiler to “unroll” a for loop, which means to write out each iteration as another line of code. So something like:

[unroll]
for (int i=0; i<4; i++)
    foo += bar[i];

gets turned into the equivalent of:

foo += bar[0];
foo += bar[1];
foo += bar[2];
foo += bar[3];

As for the “why”, loops have some overhead, so the unrolled case is generally faster. Certainly in the simple example above that’s true.

Cases where you want to keep it a dynamic loop would be if the operations being done within the loop are expensive and the additional overhead of a dynamic loop is less than the benefit of skipping those operations, or the number of iterations you need would generate a shader that’s excessive in size (several thousand operations).

An important aspect is how GPUs usually handle things like if statements, which is they often use a mask instead of an if in the compiled shader. What that means is the code within if will often still run even when the conditional fails.

For example:

float4 color = // some color
if (someValue > 0.5)
    color = // some expensive calculation

That will often get compiled into code that looks more like this:

float4 color = // some color
float4 tempValue = // some expensive calculation
if (someValue > 0.5)
    color = tempValue;

To extend that to unrolled dynamic loops, something like this:

[unroll(10)]
for (int i=0; i<baz; i++)
    foo += ExpensiveFunction(bar[i]);

Will get unrolled to this:

float temp0 = ExpensiveFunction(bar[0]);
if (0<baz)
    foo += temp0;
float temp1 = ExpensiveFunction(bar[1]);
if (1<baz)
    foo += temp1;
float temp2 = ExpensiveFunction(bar[2]);
if (2<baz)
    foo += temp2;
// ...
// pretend iterations 3 through 8 are here
// ...
float temp9 = ExpensiveFunction(bar[9]);
if (9<baz)
    foo += temp9;

Without an explicit number in the [unroll], the compiler will do it’s best guess as to how many iterations are needed. For example the bar array should have an explicit size when it’s defined in the shader code, so it’ll use that. If there’s no hint in the shader code that might mean it expands out to 100 iterations that always run even if you only ever need significantly fewer. Like if it’s a structured buffer that doesn’t have a fixed size in the shader code.

And that’s when [loop] comes into play. To tell the compiler that, no, just leave it as a loop so the number of iterations can change as needed.

24 Likes