22 aprile 2015
Posted by Shanee Nishry, Games Developer Advocate
Uniforms variables in GLSL are crucial for passing data between the game code on the CPU and the shader program on the graphics card. Unfortunately, up until the availability of OpenGL ES 3.1, using uniforms required some preparation which made the workflow slightly more complicated and wasted time during loading.
Let us examine a simple vertex shader and see how OpenGL ES 3.1 allows us to improve it:
#version 300 es layout(location = 0) in vec4 vertexPosition; layout(location = 1) in vec2 vertexUV; uniform mat4 matWorldViewProjection; out vec2 outTexCoord; void main() { outTexCoord = vertexUV; gl_Position = matWorldViewProjection * vertexPosition; }
Note: You might be familiar with this shader from a previous Game Performance article on Layout Qualifiers. Find it here.
We have a single uniform for our world view projection matrix:
uniform mat4 matWorldViewProjection;
The inefficiency appears when you want to assign the uniform value.
You need to use glUniformMatrix4fv or glUniform4f to set the uniform’s value but you also need the handle for the uniform’s location in the program. To get the handle you must call glGetUniformLocation.
GLuint program; // the shader program float matWorldViewProject[16]; // 4x4 matrix as float array GLint handle = glGetUniformLocation( program, “matWorldViewProjection” ); glUniformMatrix4fv( handle, 1, false, matWorldViewProject );
That pattern leads to having to call glGetUniformLocation for each uniform in every shader and keeping the handles or worse, calling glGetUniformLocation every frame.
Warning! Never call glGetUniformLocation every frame! Not only is it bad practice but it is slow and bad for your game’s performance. Always call it during initialization and save it somewhere in your code for use in the render loop.
This process is inefficient, it requires you to do more work and costs precious time and performance.
Also take into consideration that you might have multiple shaders with the same uniforms. It would be much better if your code was deterministic and the shader language allowed you to explicitly set the locations of your uniforms so you don’t need to query and manage access handles. This is now possible with Explicit Uniform Locations.
You can set the location for uniforms directly in the shader’s code. They are declared like this
layout(location = index) uniform type name;
For our example shader it would be:
layout(location = 0) uniform mat4 matWorldViewProjection;
This means you never need to use glGetUniformLocation again, resulting in simpler code, initialization process and saved CPU cycles.
This is how the example shader looks after the change. Changes are marked in bold:
#version 310 es layout(location = 0) in vec4 vertexPosition; layout(location = 1) in vec2 vertexUV; layout(location = 0) uniform mat4 matWorldViewProjection; out vec2 outTexCoord; void main() { outTexCoord = vertexUV; gl_Position = matWorldViewProjection * vertexPosition; }
As Explicit Uniform Locations are only supported from OpenGL ES 3.1 we also changed the version declaration to 310.
Now all you need to do to set your matWorldViewProjection uniform value is call glUniformMatrix4fv for the handle 0:
const GLint UNIFORM_MAT_WVP = 0; // Uniform location for WorldViewProjection float matWorldViewProject[16]; // 4x4 matrix as float array glUniformMatrix4fv( UNIFORM_MAT_WVP, 1, false, matWorldViewProject );
This change is extremely simple and the improvements can be substantial, producing cleaner code, asset pipeline and improved performance. Be sure to make these changes If you are targeting OpenGL ES 3.1 or creating multiple APKs to support a wide range of devices.
To learn more about Explicit Uniform Locations check out the OpenGL wiki page for it which contains valuable information on different layouts and how arrays are represented.