PDA

View Full Version : Shader instruction limits & OpenGL fixed functionality



Pody
10-19-2006, 08:01 AM
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

zeoverlord
10-19-2006, 08:33 AM
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.

k_szczech
10-19-2006, 08:38 AM
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.

Humus
10-19-2006, 01:11 PM
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;

Pody
10-19-2006, 11:50 PM
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);
}
}
...
}

Komat
10-20-2006, 01:23 AM
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_resultso 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.

Pody
10-20-2006, 07:41 AM
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?