Separate Shader Objects
Advanced
Previous to OpenGL Version 4.1 (and not considering extensions), only a single shader program could be bound at any one time in an application’s execution. This was inconvenient if your application used multiple fragment shaders for a collection of geometry that was all transformed using the same vertex shader. This caused you to need to have multiple programs around that duplicated the same vertex shader, wasting resources and duplicating code.
Separate shader objects allows shader stages (e.g., vertex shading) from various programs to be combined into a program pipeline.
The first step is to create a shader program that’s usable in a shader pipeline. This is done by calling glProgramParameteri() with the parameter GL_PROGRAM_SEPARABLE before linking the shader program. This marks the shader program as eligible to be used in a program pipeline. To simplify this process, a new command glCreateShaderProgramv() was added that encapsulates the shader-compilation process, including marking the program as sharable (as discussed earlier) and linking it to produce the final object.
Once your collection of shader programs is combined, you need to use the new shader pipeline constructs to combine shader stages from multiple programs into a usable program pipeline. As with most objects in OpenGL, there is a gen-bind-delete sequence of calls to make. A shader pipeline is created by calling glCreateProgramPipelines(), which will create an unused program pipeline identifier that you pass into glBindProgramPipeline(), making that program available for use. Similarly to other generated objects, program pipelines are deleted with glDeleteProgramPipelines().
Once you’ve bound a program pipeline, you can attach program objects that have been marked as separable to the pipeline by calling glUseProgramStages(), which takes a bitfield describing which stages from the provided program should be employed when this pipeline is used to process geometry and shade fragments. The older glUseProgram() when called with a program will replace the current program pipeline binding.
The interfaces between shader stages, the in and out variables, must match in order for the pipeline to work. As compared to using a non-separate shader object, where those interfaces can be verified during program linkage, shader pipelines with separate program objects need to be checked at draw-call issue. If the interfaces don’t match correctly, all varying values (out variables) are undefined.
The built-in gl_PerVertex block must be redeclared in shaders to explicitly indicate what subset of the fixed pipeline interface will be used. This will be necessary when using multiple programs to complete your pipeline.
For example:
out gl_PerVertex { vec4 gl_Position; // makes gl_Position is part of interface float gl_PointSize; // makes gl_PointSize is part of interface }; // no more members of gl_PerVertex are used
This establishes the output interface the shader will use with the following pipeline stage. It must be a subset of the built-in members of gl_PerVertex. If a built-in block interface is formed across shaders in different programs, the shaders must all redeclare the built-in block in the same way.
Because separable shader objects can each have their individual set of program uniforms, two methods are provided for assigning uniform variable values. First, you can select an active shader program with glActiveShaderProgram(), which causes calls to glUniform*() and glUniformMatrix*() to assign values to that particular shader program’s uniform variables. A preferred alternative is to call glProgramUniform*() and glProgramUniformMatrix*(), which take an explicit program object in addition to the other parameters used to identify the program’s uniform variable.