I'm doing some basic optimization on a fragment shader for an iOS app. I want to assign one of a few colors to gl_FragColor
. My first attempt used the ternary operator. This displays things correctly on both the simulator and iPhone 5C:
lowp float maxC = max(color.r, max(color.g, color.b);
lowp float minC = min(color.r, min(color.g, color.b);
gl_FragColor.rgb = (maxC > 1.0 ? result0 : (minC < 0.0 ? result1 : result2));
I tried to replace the ternary operators with a combination of mix
and step
to see if I can replicate the above the logic with less branching. Unfortunately, this works in the simulator but not on the iOS device:
lowp float step0 = step(0.0, minC);
lowp float step1 = step(1.001, maxC);
lowp vec3 mix0 = mix(result1, result2, step0);
gl_FragColor.rgb = mix(mix0, result0, step1);
Specifically, some white areas of the screen that draw correctly in the simulator are drawn incorrectly as black areas on the device. Everything else looks fine.
What are some reasons for the above combination of step
and mix
not reproducing the same results as the approach using ternary operators?
The exact details of floating point operations in an OpenGL shader can be difficult to understand. What is likely going on is that the ternary operator is being optimized into a multiply by zero operation by the GLSL compiler. Have a look at avoiding-shader-conditionals for info about how folding conditionals into multiply or other simple operations. The most useful utility function is:
float when_eq(float x, float y) {
return 1.0 - abs(sign(x - y));
}
An example of use would be:
float comp = 0.0;
comp += when_eq(vTest, 0.0) * v1;
comp += when_eq(vTest, 1.0) * v2;
The code above will set comp to either v1 or v2 depending on the current value of vTest. Be sure to test this explicit implementation vs the ternary impl generated by the compiler to see which one is faster.