GLSL Error C1502 (Nvidia): "index must be constant expression"

Started by
13 comments, last by Matias Goldberg 8 years, 6 months ago


Thanks, sadly the same error still occurs.

Changing the version to "#version 420 core" also didn't help.

If you have a cycle whose condition expression is evaluated entirely from uniform variables, you should be ok even in the down deep versions of GLSL, even in pixel function.

Quite complicative is your break instruction inside the loop. Though this instruction has in condition an uniform variable again, it still could confuse NVIDIA driver too much, you can try to rewrite the code to this to emit the same instructions:


const int MAX_LIGHTS = 8; // Maximum amount of lights
uniform int numLights; // Actual amount of lights (Cannot exceed MAX_LIGHTS)
.
.
.

void Test()
{
    int shadejobcount= min(MAX_LIGHTS,numLights);
    for(int i=0;i<shadejobcount;i++)
    {
       /* if(i >= numLights)
        	break;  */
        vec3 pos = LightSources[i].position; // Causes "index must be constant expression" error on Nvidia cards
        [...]
    }
}

Might help.

If still would not, you may be facing a "safe gpu code" kind of alert from the NVIDIA driver, refusing to compile a shader in which it does not know the (maximum) amount of instructions at compile time. In that case, I cannot think of better limitting method than a min() with a compile constant and the uniform :)

Advertisement


Thanks, sadly the same error still occurs.
Have you tried what Mathias suggested? Redefining the array inside the block, not outside?

layout (std140) uniform LightSourceBlock
{
    LightSource lights[MAX_LIGHTS];
} LightSources;

This works. Its how I define UBOs. Tried it on nVidia hardware (GT21x, Fermi), and AMD (HD 5xxx, HD 4xxx), GLSL 330.

"I AM ZE EMPRAH OPENGL 3.3 THE CORE, I DEMAND FROM THEE ZE SHADERZ AND MATRIXEZ"

My journals: dustArtemis ECS framework and Making a Terrain Generator


Thanks, sadly the same error still occurs.
Have you tried what Mathias suggested? Redefining the array inside the block, not outside?

layout (std140) uniform LightSourceBlock
{
    LightSource lights[MAX_LIGHTS];
} LightSources;

This works. Its how I define UBOs. Tried it on nVidia hardware (GT21x, Fermi), and AMD (HD 5xxx, HD 4xxx), GLSL 330.

I wanted to give both approaches a shot, but after trying Mathias' solution it worked immediately. Thanks!

However, I've stumbled over another shader error on Nvidia, which doesn't occur on my AMD-card:


error C5208: Sampler needs to be a uniform (global or parameter to main), need to inline function or resolve conditional expression

The line it gives me just points me to my main function which does the processing, which doesn't help me at all. The error doesn't make much sense to me either, all of my samplers are uniforms.

The only possible cause I could think of are my shadow samplers, which are a uniform array:

uniform sampler2DShadow shadowMaps[MAX_LIGHTS];

The sampler for each light is then accessed by shadowMaps[LightSources.lights.shadowMapID]. (shadowMapID is an int)

Could this be the cause? If so, is there a simple way to get around it?

I apologize for the sparse information, I would just do some tests myself, but I can only get access to the Nvidia machine for testing sporadically, and the shaders work fine on my AMD-setup.

Maybe someone has encountered this error before?

The sampler for each light is then accessed by shadowMaps[LightSources.lights.shadowMapID]. (shadowMapID is an int)
Could this be the cause? If so, is there a simple way to get around it?

Yes. That's the problem.
Indexing a sampler like that needs GL4 hardware, actually pretty modern like version 430.

There are two possible workarounds:

1. Since you have up to 8 lights, sort them in CPU.
So lights[0] corresponds to shadowMaps[0], lights[1] corresponds to shadowMaps[1], and so on. This is what a lot of people do. Even on modern hardware, it also saves you an indirection GPU side so GPU performance should go up (in exchange for a little of CPU power for sorting the lights/samplers).

2. Use texture arrays. Declare your sampler as a single sampler2DArrayShadow shadowMaps and pass the shadowMapID as the array slice. The only problem with this approach is that the shadow maps must all be of the same resolution and format.
Use this instead:
struct LightSource
{
vec3 position;
[...]
};

layout (std140) uniform LightSourceBlock
{
LightSource lights[MAX_LIGHTS];
} LightSources;

//Index like this: LightSources.lights
This uses one constant buffer and places all lights in that single buffer. That's way more efficient, more compatible, and the method everyone uses.

One more thing I forgot to ask:

Previously I had one buffer per light, each containing the respective light data, and then I just bound (Using glBindBufferBase) the buffers of the visible lights to the slots of the LightSources array.

How would I do that with this approach? I'd need the opposite of glBindBufferRange (To bind a buffer to a range of an indexed buffer target), but that doesn't seem to exist.

Do I have to use a single buffer for all light data? That would leave me with two alternatives:

- Store ALL lights within a large buffer and impose a limit of max lights allowed in a level (The MAX_LIGHTS only referred to max visible(in the view frustum)/active lights thus far), or

- Use a smaller buffer (sizeof(lightData) *MAX_LIGHTS) and re-upload the light data of all visible lights whenever there's a change.

I don't really like either solution, is there a third option I'm missing?

Welcome to game development! Where there's no best choice and your options suck!

You just (re)discovered the problems of traditional Forward rendering (as opposed to Deferred Shading, or Forward+, or Clustered Forward). Best way to learn is the hard way.

What you're doing is called Forward shading, and no, you're not missing anything.

You either:

  1. Create a buffer with a fixed maximum number of lights (e.g. 8), use the same lights for all objects.
  2. Create a megabuffer of N*L where N is the number of objects, and L the max number or lights per object (e.g. 1000 objects, 8 lights per object = 8000)
  3. Use a non-forward solution

Forward+ and clustered forward for example, create a single buffer with all lights in scene; then via a special algorithm, it generates a second buffer with an index of the lights being used by that tile or cluster. e.g. Send all 500 lights in scene; then a special pass determines that a tile of pixels (or a 'cluster') uses lights 0, 12 and 485. Then the whole tile/cluster is shaded by those three lights:

for( int i=0; int i<lightsInTile; ++i )
{
     colour += shade( lights[tileList[i]] );
}

This topic is closed to new replies.

Advertisement