# Avoid if statements in DirectX 10 shaders?

I have heard that if statements should be avoid in shaders, because both parts of the statements will be execute, and than the wrong will be dropped (which harms the performance).

It's still a problem in DirectX 10? Somebody told me, that in it only the right branch will be execute.

For the illustration I have the code:

float y1 = 5; float y2 = 6; float b1 = 2; float b2 = 3;

if(x>0.5){
x = 10 * y1 + b1;
}else{
x = 10 * y2 + b2;
}


Is there an other way to make it faster?

If so, how do it?

Both branches looks similar, the only difference is the values of "constants" (y1, y2, b1, b2 are the same for all pixels in Pixel Shader).

• Honestly, that's very premature optimization, just don't change them until you benchmarked your code and are 100% that shader is a bottleneck. – pwny Dec 11 '12 at 22:55

Many rules for micro-optimising shaders are the same as for traditional CPUs with vector extensions. Here are a few hints:

• there are built-in test functions (test, lerp/mix)
• adding two vectors has the same cost as adding two floats
• swizzling is free

It is true that branches are cheaper on modern hardware than they used to be, but it is still better to avoid them if possible. By using swizzling and test functions you can rewrite your shader without tests:

/* y1, y2, b1, b2 */
float4 constants = float4(5, 6, 2, 3);

float2 tmp = 10 * constants.xy + constants.zw;
x = lerp(tmp[1], tmp[0], step(x, 0.5));


Using step and lerp is a very common idiom for choosing between two values.

Generally it's OK. Shaders will execute in groups of vertices or pixels (different vendors have different terminology for these so I'm keeping away from that) and if all vertices or pixels in a group take the same path then branching cost is negligible.

You also need to trust the shader compiler. The HLSL code you write should not be seen as a direct representation of the bytecode or even assembly that it will compile down to, and the compiler is perfectly free to convert it to something that's equivalent but avoids the branch (e.g. a lerp may sometimes be a preferred conversion). On the other hand, if the compiler determines that performing a branch is actually the faster path, it will compile it down to a branch. Viewing the generated assembly in PIX or a similar tool can be very helpful here.

Finally, the old wisdom still holds here - profile it, determine if it's actually a performance problem for you, and tackle it then, not before. Assuming that something may be a performance problem and acting according to that assumption will only incur a huge risk of bigger problems later on.

Quote from the link/article posted by Robert Rouhani:

"Condition codes (predication) are used in older architectures to emulate true branching. If-then statements compiled to these architectures must evaluate both taken and not taken branch instructions on all fragments. The branch condition is evaluated and a condition code is set. The instructions in each part of the branch must check the value of the condition code before writing their results to registers. As a result, only instructions in taken branches write their output. Thus, in these architectures all branches cost as much as both parts of the branch, plus the cost of evaluating the branch condition. Branching should be used sparingly on such architectures. NVIDIA GeForce FX Series GPUs use condition-code branch emulation in their fragment processors."

As mh01 suggested ("Viewing the generated assembly in PIX or a similar tool can be very helpful here."), you should use a compiler tool to examine the output. In my experience, nVidia's Cg tool (Cg is still widely used today because of its cross platform capabilities) gave a perfect illustration of the behavior mentioned in the GPU gems condition codes (predication) paragraph. Thus, regardless of the trigger value, both branches were evaluated on a per fragment basis, and only at the end, the right one was placed in the output registry. Nevertheless, the computation time was wasted. Back then, I thought that branching will help performance, especially because all fragments in that shader relied on an uniform value to decide on the right branch - that did not happen as intended. So, a major caveat here ( e.g. avoid ubershaders - possibly the greatest source of branching hell).

If you're not already having performance issues, this is fine. The cost for comparison against a constant is still extremely cheap. Here's a good read about GPU branching: http://http.developer.nvidia.com/GPUGems2/gpugems2_chapter34.html

Regardless, here's a snippet of code that's going to preform much worse than the if statement (and is far less readable/maintainable), but still gets rid of it:

int fx = floor(x);
int y = (fx * y2) + ((1- fx) * y1);
int b = (fx * b2) + ((1 -fx) * b1);

x = 10 * y + b;


Note that I'm making the assumption that x is limited to the range [0, 1]. This won't work if x >= 2 or x < 0.

What that snipped does is convert x to either 0 or 1 and multiply the wrong one by 0 and the other one by 1.

• Since the original test is if(x<0.5) the value for fx should be round(x) or floor(x + 0.5). – sam hocevar Jan 7 '17 at 11:02

There are multiple instruction able to do conditions without branching;

vec4 when_eq(vec4 x, vec4 y) {
return 1.0 - abs(sign(x - y));
}

vec4 when_neq(vec4 x, vec4 y) {
return abs(sign(x - y));
}

vec4 when_gt(vec4 x, vec4 y) {
return max(sign(x - y), 0.0);
}

vec4 when_lt(vec4 x, vec4 y) {
return max(sign(y - x), 0.0);
}

vec4 when_ge(vec4 x, vec4 y) {
return 1.0 - when_lt(x, y);
}

vec4 when_le(vec4 x, vec4 y) {
return 1.0 - when_gt(x, y);
}


Plus some logical operators;

vec4 and(vec4 a, vec4 b) {
return a * b;
}

vec4 or(vec4 a, vec4 b) {
return min(a + b, 1.0);
}

vec4 xor(vec4 a, vec4 b) {
return (a + b) % 2.0;
}

vec4 not(vec4 a) {
return 1.0 - a;
}