Shader Subroutines
Advanced
While GLSL allows you to define functions in shaders, the call flow of those functions was always static. To dynamically select between multiple functions, you either created two distinct shaders or used an if statement to make a runtime selection, as demonstrated in Example 2.5.
Example 2.5 Static Shader Control Flow
#version 330 core void func_1() { ... } void func_2() { ... } uniform int func; void main() { if (func == 1) func_1(); else func_2(); }
Shader subroutines are conceptually similar to function pointers in C for implementing dynamic subroutine selection. In your shader, you specify a subroutine type and use that type when declaring the set of subroutines eligible for dynamic use. Then you choose which subroutine from the set to execute in the shader by setting a subroutine uniform variable.
GLSL Subroutine Setup
When you want to use subroutine selection inside of a shader, there are three steps required to set up the pool of subroutines:
Define the subroutine type using the subroutine keyword
subroutine returnType subroutineType(type param, ...);
where returnType is any valid type that a function can return, and subroutineType is any valid name. As with function prototypes, only the parameter types are required; the parameter names are optional. (Hint: Think of this like a typedef in C, with subroutineType as the newly defined type.)
Using the subroutineType you just defined, define the set of subroutines that you would like to dynamically select from using the subroutine keyword. The prototype for a subroutine function looks like this:
subroutine (subroutineType) returnType functionName(...);
Finally, specify the subroutine uniform variable that will hold the function pointer for the subroutine you’ve selected in your application:
subroutine uniform subroutineType variableName;
Demonstrating those steps together, in Example 2.6 we dynamically select between ambient and diffuse lighting.
Example 2.6 Declaring a Set of Subroutines
subroutine vec4 LightFunc(vec3); // Step 1 subroutine (LightFunc) vec4 ambient(vec3 n) // Step 2 { return Materials.ambient; } subroutine (LightFunc) vec4 diffuse(vec3 n) // Step 2 (again) { return Materials.diffuse * max(dot(normalize(n), LightVec.xyz), 0.0); } subroutine uniform LightFunc materialShader; // Step 3
A subroutine is not restricted to being a single type of subroutine (e.g., LightFunc in Example 2.6). If you have defined multiple types of subroutines, you can associate any number of the types with a subroutine by adding the type to the list when defining the subroutine, as shown here:
subroutine void Type_1(); subroutine void Type_2(); subroutine void Type_3(); subroutine (Type_1, Type_2) Func_1(); subroutine (Type_1, Type_3) Func_2(); subroutine uniform Type_1 func_1; subroutine uniform Type_2 func_2; subroutine uniform Type_3 func_3;
For this example, func_1 could use either Func_1 or Func_2 because of Type_1 appearing in each of their subroutine lines. However, func_2, for example, would be limited to using only Func_1, and similarly, func_3 could use only Func_2.
Selecting Shader Subroutines
Once you have all your subroutine types and functions defined in your shaders, you need only query a few values from the linked shader program and then use those values to select the appropriate function.
In step 3 described on page 80, a subroutine uniform value was declared, and we will need its location in order to set its value. As compared to other shader uniforms, subroutine uniforms use glGetSubroutineUniformLocation() to retrieve their locations.
Once we have the subroutine uniform to assign values to, we need to determine the indices of the subroutines inside of the shader. For that, we can call glGetSubroutineIndex().
Once you have both the available subroutine indices and subroutine uniform location, use glUniformSubroutinesuiv() to specify which subroutine should be executed in the shader. All active subroutine uniforms for a shader stage must be initialized.
Assembling those steps, the following code snippet demonstrates the process for the vertex shader described in Example 2.6.
GLint materialShaderLoc; GLuint ambientIndex; GLuint diffuseIndex; glUseProgram(program); materialShaderLoc = glGetSubroutineUniformLocation( program, GL_VERTEX_SHADER, "materialShader"); if (materialShaderLoc < 0) { // Error: materialShader is not an active subroutine // uniform in the shader. } ambientIndex = glGetSubroutineIndex(program, GL_VERTEX_SHADER, "ambient"); diffuseIndex = glGetSubroutineIndex(program, GL_VERTEX_SHADER, "diffuse"); if (ambientIndex == GL_INVALID_INDEX || diffuseIndex == GL_INVALID_INDEX) { // Error: the specified subroutines are not active in // the currently bound program for the GL_VERTEX_SHADER // stage. } else { GLsizei n; glGetIntegerv(GL_MAX_SUBROUTINE_UNIFORM_LOCATIONS, &n); GLuint *indices = new GLuint[n]; indices[materialShaderLoc] = ambientIndex; glUniformSubroutinesuiv(GL_VERTEX_SHADER, n, indices); delete [] indices; }