# glsl uniform int as index of const array

I have a const array that define a few materials for directionnal light. I'd like to change the material using an uniform int as index of that array. Doing that leads to severe framerate drop.

I know that this could be fixed by just keeping the array in the cpu and send the material as uniform. But why does this not lead to framerate drop and the other do?

It seems that even with 2 elements in my array I have framerate drops. But accessing the array with a constant doesn't make me lag ( I presume glsl drivers optimize this by replacing the line with the correct element in the array ).

struct Material
{
vec3 ambiant, diffuse, specular;
float shininess;
};

const Material materials[] = Material[]
(
Material( vec3( 0.05375, 0.05, 0.06625 ), vec3( 0.18275, 0.17, 0.22525 ), vec3(  0.332741, 0.328634, 0.346435 ), 0.3 * 128 ),
Material( vec3( 0.1, 0.18725, 0.1745 ), vec3( 0.396, 0.74151, 0.69102 ), vec3( 0.297254, 0.30829, 0.306678 ), 0.1 * 128 ),
Material( vec3( 0.25, 0.25, 0.25 ), vec3( 0.4, 0.4, 0.4 ), vec3( 0.774597, 0.774597, 0.774597 ), 0.6 ),
Material( vec3( 0.19125, 0.0735, 0.0225 ), vec3( 0.7038, 0.27048, 0.0828 ), vec3( 0.256777, 0.137622, 0.086014 ), 0.1 * 128 ),
Material( vec3( 0.24725, 0.1995, 0.0745 ), vec3( 0.75164, 0.60648, 0.22648 ), vec3( 0.628281, 0.555802, 0.366065 ), 0.4 * 128 ),
Material( vec3( 0.0, 0.0, 0.0 ), vec3( 0.5, 0.5, 0.0 ), vec3( 0.60, 0.60, 0.50 ), 0.25 * 128 ),
Material( vec3( 0.05, 0.0, 0.0 ), vec3( 0.5, 0.4, 0.4 ), vec3( 0.7, 0.04, 0.04 ), 0.078125 * 128 )
);

void main()
{
Material mat = materials[ index ];

vec3 lightColor = vec3( 1.0, 1.0, 1.0 );

float diffuseFactor = max( dot( normalize( Normal ), lightPos ), 0.0 );

vec3 reflectedRay = reflect( -lightPos, normalize( Normal ) );
float specularFactor = pow( max( dot( reflectedRay, normalize( cameraPos - Position ) ), 0.0 ), mat.shininess );

vec3 ambiantColor = lightColor * mat.ambiant;
vec3 diffuseColor = diffuseFactor * lightColor * mat.diffuse;
vec3 specularColor = specularFactor * lightColor * mat.specular;

color = vec4( ambiantColor + diffuseColor + specularColor, 1.0f );
}

• If this is in a fragment shader, you could try doing the lookup in the vertex shader. Feb 23 '15 at 11:56

That would be because the GPU is optimized for massive parallel execution of the same program with multiple data, and accessing a non-constant index of an array in a shader (especially the fragment shader) does not count as "the same program" in GPU terms.

If you have seven materials, how about passing an array of seven floats, each defining a weight for each material, and mixing them instead using only one 1 and six 0s. Or in pseudocode, instead of doing this:

Material mat = materials[ index ];


You add a uniform array of seven floats like:

uniform float weights[7];


And then do this:

vec3 blend_ambient = vec3(0.0, 0.0, 0.0);
vec3 blend_diffuse = vec3(0.0, 0.0, 0.0);
vec3 blend_specular = vec3(0.0, 0.0, 0.0);
float blend_shininess = 0;

// If you're still having trouble, unroll this loop manually
for (float k = 0; k < 7; k++)
{
blend_ambient += materials[k].blend_ambient * weights[k];
blend_diffuse += materials[k].blend_diffuse * weights[k];
blend_specular += materials[k].blend_specular * weights[k];
blend_shininess += materials[k].blend_shininess * weights[k];
}

Material mat = Material(blend_ambient, blend_diffuse, blend_specular, blend_shininess);


If you want to use say, material 2, then you pass weights as {0, 0, 1, 0, 0, 0, 0}.

You will notice that this algorithm is much slower if you think in CPU terms, but is much faster in GPU terms.

• Indeed, it run smoothly. May you explain what is "not a same program" for the gpu? A program with variable instructions numbers? Feb 23 '15 at 19:53
• The GPU works best when all the instances of the program take the same branches and use the same memory addresses. Using a non-constant index to access an array means that the GPU doesn't know which memory address will have to be fetched until the program actually reaches the place where the reference is made, therefore stalling the entire pipeline. Feb 24 '15 at 3:17

Because your work would be done on the CPU, and not the GPU your framerates are unaffected. The more calculations you do in the shader, the more the GPU has to work. Some calculations are necessary such as applying transformations, since they can't be done at compile-time, but rather in run-time.

If anything, I recommend avoiding the declaration of const data because it's just a waste on the GPU side, when you can easily pass in the materials by uniform. What's more, is that if you pass by uniform you have to pass in the material before you render the object. To make use of uniforms, I recommend the following:

struct Material
{
...
};
uniform Material mat;


Then wherever you do the drawing, you would do something like:

id = glUniformLocation(prog, "mat.ambient");
glUniform3f(id, 0.1, 0.1, 0.1);


In the end, the choices are yours.

• As said in the question, I know how to make it works. What I want to know is why do I get that much framerate drop. Feb 23 '15 at 2:52