I’ve encountered a problem which I believe is related to optimization of GLSL compilation of PowerVR GPUs. On Adreno and Tegra GPUs fragment shader works just fine, but on PowerVR (Motorola Droid) it produces incorrect result in conditional statement.
I’ve fixed the problem by changing conditional statement in fragment shader code. Instead of calling return in block for if statement I’ve added else block and it works OK on PowerVR now.
Logic of both shaders is absolutely identical, gl_FragColor is set in both cases.
Please explain this behavior of PowerVR OpenGL driver so I can avoid problems in future. Why does it handle conditional statements this way?
Here is the old fragment shader, which works incorrectly on PowerVR GPUs:
precision mediump float;
varying vec3 vNormal;
varying vec3 vViewVec;
varying vec2 vTextureCoord;
uniform sampler2D sTexturePumpkin;
void main(void)
{
const float sheen = 0.68;
const float noiseScale = 0.05;
const float furriness = 10.0;
const vec4 lightDir = vec4(0.267260, 0.267260, -0.925820, 0.0);
vec4 color = texture2D(sTexturePumpkin, vTextureCoord/*vec2(0.0,0.0)*/);
if(vTextureCoord.y > 0.7) { // in this case PowerVR displays incorrect color
gl_FragColor = color;
return;
}
float diffuse = 0.5 * (1.0 + dot(vNormal, vec3(lightDir.x, lightDir.y, -lightDir.z)));
float cosView = clamp(dot(normalize(vViewVec), vNormal), 0.0, 1.0);
float shine = pow(1.0 - cosView * cosView, furriness);
gl_FragColor = (color + sheen * shine) * diffuse; // in this case PowerVR works correctly
}
The new fragment shader code, which works fine on both Adreno and PowerVR GPU:
precision mediump float;
varying vec3 vNormal;
varying vec3 vViewVec;
varying vec2 vTextureCoord;
uniform sampler2D sTexturePumpkin;
void main(void)
{
const float sheen = 0.68;
const float noiseScale = 0.05;
const float furriness = 10.0;
const vec4 lightDir = vec4(0.267260, 0.267260, -0.925820, 0.0);
vec4 color = texture2D(sTexturePumpkin, vTextureCoord/*vec2(0.0,0.0)*/);
if(vTextureCoord.y > 0.7) {
gl_FragColor = color;
}
else {
float diffuse = 0.5 * (1.0 + dot(vNormal, vec3(lightDir.x, lightDir.y, -lightDir.z)));
float cosView = clamp(dot(normalize(vViewVec), vNormal), 0.0, 1.0);
float shine = pow(1.0 - cosView * cosView, furriness);
gl_FragColor = (color + sheen * shine) * diffuse;
}
}
OK so after deeper investigation I have found that it’s not a bug of shader compiler but a specific way of processing fragment shaders execution. It is generally a bad idea to put a simple
discard;orreturn;statement with some code coming after it. It still can be executed and cause unpredictable result.There are some articles that explains this behavior applicable to
discard;statement, and as I see the similar behavior can happen withreturn;too.Please read one here: http://people.freedesktop.org/~idr/OpenGL_tutorials/03-fragment-intro.html#infinite-loop
As is it said here, in certain cases you can even achieve infinite loop by incorrect usage of
discard;.Fragment shaders are executed by GPU not for a single texel at a time, usually in batches of 2×2 pixels. And this parallel running of fragment shader can cause code after
return;.So, for fragment shader to handle
ifstatements correctly, you have to always useelseinifoperators, not simply exiting function byreturn;ordiscard;. This is exactly what I’ve done.