GLSL : common mistakes

Revision as of 12:52, 8 October 2012 by Nick Wiggill (talk | contribs) (glGetUniformLocation & glGetActiveUniform)

Jump to: navigation, search

The following article discusses common mistakes made in the OpenGL Shading Language, GLSL.


How to use glUniform

If you look at all the glUniform functions (glUniform1fv, glUniform2fv, glUniform3fv, glUniform4fv, glUniform1iv, glUniform2iv, glUniform3iv, glUniform4iv, glUniformMatrix4fv and the many others), there is a parameter called count.

What's wrong with this code? Would it cause a crash?

 //Vertex Shader
 uniform vec4 LightPosition;
 //In your C++ code
 float light[4];
 glUniform4fv(MyShader, 4, light);

The problem is that for count, you set it to 4 while it should be 1 because you are sending 1 vec4 to the shader.
What's wrong with this code? Would it cause a crash?

 //Vertex Shader
 uniform vec2 Exponents;
 //In your C++ code
 float Exponents[2];
 glUniform2fv(MyShader, 2, Exponents);

The problem is that for count, you set it to 2 while it should be 1 because you are sending 1 vec2 to the shader.
What's wrong with this code? Would it cause a crash?

 //Vertex Shader
 uniform vec2 Exponents[5];
 //In your C++ code
 float Exponents[10];
 glUniform2fv(MyShader, 5, Exponents);

There is nothing wrong with it. We want to send 5 values of vec2.

glUniform doesn't work

You probably did not bind the correct shader first. Call glUseProgram(myprogram) first.

glGetUniformLocation & glGetActiveUniform

Although not strictly a mistake, some wonder why glGetUniformLocation returns -1. If there is a uniform that you are not using, the driver will optimize your uniform out. Drivers are really good at optimizing code. If you are using your uniform and it is clear that the uniform will never contribute to the given output (directly or indirectly), the uniform will get optimized out. For example, if you are calculating a non-output value, such as position rather than gl_Position in a vertex shader (which is the given output for such shaders), this problem will manifest itself.

Typically this is not a problem, since if you pass -1 instead of a valid uniform location to the glUniformxxx calls, they will quietly do nothing anyway. But you will also get -1 if you misspell the variable name to glGetUniformLocation—just something to bear in mind.

We can use glGetActiveUniform to determine which uniforms were retained, and which optimised out, during the compile/link process.


When should you call glUseProgram?

glUseProgram needs to be called before you setup a uniform. There are several versions of the glUniform* function depending if your variable is a single float, vec2, vec3, vec4, a matrix, etc. Notice that the glUniform* functions do not take the program ID (your shader) as a parameter.

What if you want to get the location of a uniform? Notice that glGetUniformLocation takes the program ID (your shader) as a parameter. No, you do not need to call glUseProgram before calling glGetUniformLocation.

What if you want to render? glUseProgram needs to be called before you use glDrawArrays or glDrawElements or glDrawRangeElements or whatever draw function you are using. It may seem obvious that you need to bind your shader before you render your object but some newcomers seem to call glUseProgram after glDrawArrays or glDrawElements or glDrawRangeElements or whatever.

Uniform Names in VS, GS and FS

So what happens if you have the same exact uniform name in both the vertex shader and geometry shader and fragment shader?

Yes, it is legal to have the same uniform name in all shaders.

When you call glGetUniformLocation, it will return one location. When you update the uniform with a call to glUniform, the driver takes care of sending the value for each stage (vertex shader, geometry shader, fragment shader).

This is because a GLSL program contains all of the shader stages at once. Programs do not consider uniforms in a vertex shader to be different from uniforms in a fragment shader.

Keep in mind that this applies to all uniforms : float, vec2, vec3, vec4, mat3, mat4, bool, sampler2D, sampler3D and the many others.


Enable Or Not To Enable

With fixed pipeline, you needed to call glEnable(GL_TEXTURE_2D) to enable 2D texturing. You needed to call glEnable(GL_LIGHTING). Since shaders override these functionalities, you don't need to glEnable/glDisable. If you don't want texturing, you either need to write another shader that doesn't do texturing or you can attach a all white or all black texture, depending on your needs. You can also write one shader that does lighting and one that doesn't.

Things that are not overriden by shaders, like the alpha test, depth test, stencil test... calling glEnable/glDisable will have an effect.

Binding A Texture

NVIDIA and Types

nVidia drivers are more relaxed. For example:

float myvalue = 0;

The above is not legal according to the GLSL specification 1.10, due to the inability to automatically convert from integers (numbers without decimals) to floats (numbers with decimals). Use 0.0 instead. With GLSL 1.20 and above, it is legal because it will be converted to a float.

float myvalue1 = 0.5f;
float myvalue2 = 0.5F;

The above is not legal according to the GLSL specification 1.10. With GLSL 1.20, it becomes legal.

float texel = texture2D(tex, texcoord);

The above is wrong since texture2D returns a vec4. Do one of these instead:

float texel = texture2D(tex, texcoord).r;
float texel = texture2D(tex, texcoord).x;

Functions inputs and outputs

Functions should look like this

vec4 myfunction(inout float value1, in vec3 value2, in vec4 value3)

instead of

vec4 myfunction(float value1, vec3 value2, vec4 value3)

Not Used

In the vertex shader

gl_TexCoord[0] = gl_MultiTexCoord0;

and in the fragment shader

vec4 texel = texture2D(tex, gl_TexCoord[0].xy);

zw isn't being used in the fs.
Keep in mind that for GLSL 1.30, you should define your own vertex attribute.
This means that instead of gl_MultiTexCoord0, define AttrMultiTexCoord0.
Also, do not use gl_TexCoord[0]. Define your own varying and call it VaryingTexCoord0.

Sampling and Rendering to the Same Texture

Normally, you should not sample a texture and render to that same texture at the same time. This would give you undefined behavior. It might work on some GPUs and with some driver version but not others.

The extension GL_NV_texture_barrier can be used to avoid this in certain ways. Specifically, you can use the barrier to ping-pong between two regions of the same texture without having to switch textures or buffers or anything. You still don't get to read and write to the same location in a texture at the same time unless there is only a single read and write of each texel, and the read is in the fragment shader invocation that writes the same texel.