Shader instruction limits & OpenGL fixed functionality

Hi, I’m trying to do a shader to replicate the OpenGL fixed functionality as a starting point to add new effects, but the compiler gives an error during linking about too many instructions.
I have a NVidia Quadro FX1400, 128MB of VRam.

I’ve taken the shader code from “ShaderGen” tool by 3dLabs and added a loop on the 8 lights to check if they are enabled (I pass two uniform vectors to the shader that hold the lights status).
If they are enabled then I call the appropriate function based on the light type.

It’s something like this (I omit the code generated by ShaderGen which does just the illumination computation in the standard way).

Vertex Shader

uniform vec4 lightStatus1; //holds status for lights 1-4
uniform vec4 lightStatus2; //holds the status for lights 5-8

void pointLight(in int i, in vec3 normal, in vec3 eye, in vec3 ecPosition3){
...
}

void directionalLight(in int i, in vec3 normal)
{
...
}

vec3 fnormal(void){
...
}
   
void flight(in vec3 normal, in vec4 ecPosition, float alphaFade)
{
...
//Cycle on all the lights and call the appropriate function
    for (i=0; i<4; i++){
        if (lightStatus1[i]!=0.0){
            if (gl_LightSource[i].position.w == 0.0) // Directional Light
                directionalLight(i, normal);
            else                                 // Point or spot light
                pointLight(i,normal,eye, ecPosition3);        
        }
        if (lightStatus2[i]!=0.0){
            if (gl_LightSource[i+4].position.w == 0.0) // Directional Light
                directionalLight(i+4, normal);
            else                                     // Point or spot light
                pointLight(i+4,normal,eye, ecPosition3);
        }
    } 
...
}

void main (void)
{
 ...
    flight(transformedNormal, ecPosition, alphaFade);
}

So it seems that I can’t replicate OpenGL’s fixed functionality with just one shader pass.
Am I doing something wrong or is it a limitation of my video card?

I thought that at least the fixed functionality behaviour should be reproducable through shaders…
Thanks for any help

that’s because you use loops and to many large if statements, most graphics cards (save for the very latest of them) can’t do branching very well , so instead they are compiled linearly.
That is why the instruction count went bananas.

and by the way, why do you want to replicate the fixed function anyway, isn’t it easier to just do your own, that way you can optimize it much further and it will look better.

AFAIK your GPU has fixed functionality + shaders. New GPU’s have only shaders, and fixed functionality is emulated by shaders.
But in case of your GPU, shaders don’t have to be sufficient to replace fixed functionality.
Another thing is that your loop is unrolled by the driver since your GPU doesn’t support such features as loops.
So you shader has about 8 times more instructions that pointLight() and directionalLight() functions put together.

There are a number of ways you can help the compiler out a bit. To begin with, use booleans for the lightstatus. This allows the compiler to use static branching without complex shader analysis. It may not matter, but I’d separate lightstatus1 and lightstatus2 loops, or make it one loop with an actual array of booleans instead of packing it. I’d also try to combine the pointlight and directional light into the same function by computing the lightvector as follows, which should work for both cases:

vec3 lightVec = gl_LightSource[i].position.xyz - eye * gl_LightSource[i].position.w;

Thanks,
I made it work with the suggestion by Humus to use an array of booleans to store the lights status.
I don’t understand why, though.
I also would like to understand what help could bring combining the pointlight and directional light functions together.
Thanks a lot

Here’s the code

uniform bool LightStatus[8];

void flight(in vec3 normal, in vec4 ecPosition, float alphaFade)
{
...
    int i;
    for (i=0; i<8; i++){
        if (LightStatus[i]){
            if (gl_LightSource[i].position.w == 0.0) // Directional Light
                directionalLight(i, normal);
            else                                    // Point or spot light
                pointLight(i,normal,eye, ecPosition3);        
        }
    }
...
}

Originally posted by Pody:
Thanks,
I made it work with the suggestion by Humus to use an array of booleans to store the lights status.
I don’t understand why, though.

When the booleans are used the compiler should be able to create special shader instances for various used combinations of the booleans. In each such instance it can throw away code that calculates lights, which are not enabled in it. Without the booleans it will likely generate code like:


I also would like to understand what help could bring combining the pointlight and directional light functions together.

On many cards the code

if (gl_LightSource[i].position.w == 0.0) // Directional Light
     directionalLight(i, normal);
else  // Point or spot light 
     pointLight(i,normal,eye, ecPosition3);      

will be translated to something like:

directional_result = directionalLight( i, normal )
pointlight_result = pointLight(i,normal,eye, ecPosition3); 
result = ( gl_LightSource[i].position.w == 0.0 )  ? directional_result : pointlight_result

so lighting for both types of lights will be calculated and the correct one will be selected latter. The shader code will also contains more instructions than it needs to have.

I tried to use just one function for the light calculation:

void generalLight(in int i, in vec3 normal, in vec3 eye, in vec3 ecPosition3)
{
   float nDotVP;       // normal . light direction
   float nDotHV;       // normal . light half vector
   float pf;           // power factor
   float spotDot;      // cosine of angle between spotlight
   float spotAttenuation; // spotlight attenuation factor
   float attenuation = 1.0;  // computed attenuation factor   
   float d;            // distance from surface to light source
   vec3  VP;           // direction from surface to light position
   vec3  VPnorm;           // direction from surface to light position
   vec3  halfVector;   // direction of maximum highlights

   // Compute vector from surface to light position
   VP = vec3 (gl_LightSource[i].position) - (ecPosition3 * gl_LightSource[i].position.w);
   
   // Normalize the vector from surface to light position
   VPnorm = normalize(VP);
   
   if (gl_LightSource[i].position.w != 0.0) { //Spot or Point Light Sources

        // Compute distance between surface and light position
        d = length(VP);

        // Compute attenuation
        attenuation = 1.0 / (gl_LightSource[i].constantAttenuation +
            gl_LightSource[i].linearAttenuation * d +
            gl_LightSource[i].quadraticAttenuation * d * d);
            
        // SPOT LIGHT
        if (gl_LightSource[i].spotCutoff == 180.0){
                //See if point on surface is inside cone of illumination
                spotDot = dot(-VPnorm, gl_LightSource[i].spotDirection);
                if (spotDot < gl_LightSource[i].spotCosCutoff)
                    spotAttenuation = 0.0; //Light adds no contribution
                else
                    spotAttenuation = pow(spotDot, gl_LightSource[i].spotExponent);
                    
                // Combine the spotlight and distance attenuation
                attenuation *= spotAttenuation;    
        }
           
        halfVector = normalize(VPnorm + eye);   
        nDotHV = max(0.0, dot(normal, halfVector));
   }
   else { //directional light
        nDotHV = max(0.0, dot(normal, vec3 (gl_LightSource[i].halfVector)));
   }
   
   nDotVP = max(0.0, dot(normal, VPnorm));

   if (nDotVP == 0.0)
   {
       pf = 0.0;
   }
   else
   {
       pf = pow(nDotHV, gl_FrontMaterial.shininess);

   }
   Ambient  += gl_LightSource[i].ambient * attenuation;
   Diffuse  += gl_LightSource[i].diffuse * nDotVP * attenuation;
   Specular += gl_LightSource[i].specular * pf * attenuation;
}

Loop on the lights:

for (int i=0; i<8; i++){
    if (LightStatus[i])
         generalLight(i,...);
}

but I get some strange lighting results (for example if no light is on then I see my sphere fully lit).
If I make the “for” loop go upto 7 instead of 8, then the lights behave correctly.
If I keep the functions separate this problem does not happen.
Could it be again a problem related to instructions count, even if the compiler dosen’t give an error?

This topic was automatically closed 183 days after the last reply. New replies are no longer allowed.