Understanding EXT_separate_shader_objects (OpenGLES 2.0.x)
Traditionally, an OpenGLES shader-program (SP) consisted of 2 shader-objects (SO) precisely one vertex shader and one fragment shader which get attached and linked into some GPU executable format, if either SO is missing or invalid the SP can’t be used. More annoyingly, in many situations a common vertex shader is used in combination with different fragment shaders, but a program must exist for each unique pair of VPs and FPs.
This new extension in its current form allows two new changes that relax this requirement to make re-use and organisation of shaders at runtime easier for developers, to paraphrase the spec it allows developers to “mix-and-match” stages to create more flexible shader pipelines.
Seperable Programs
The first change is that a SP doesn’t need to have both vertex & fragment shaders bound at link time for it to be considered valid, however this means that an SP that only has either a vertex shader or fragment shader bound (and not both) may not be consdered “complete” in a semantic sense; as-per the spec, using a SP that is missing a stage will produce results that are undefined for the missing stages.
Setting a SP to be “seperable” is done by setting it’s GL_PROGRAM_SEPARABLE_EXT parameter to GL_TRUE (just before linking):
glProgramParameteriEXT( shaderProgramId, GL_PROGRAM_SEPARABLE_EXT, GL_TRUE );
otherwise the SP object behaves as per the unextended spec, i.e. both the vertex and fragment stages must be specified before linking the SP.
SIDE NOTE: The glCreateShaderProgramvEXT API is provided to greatly reduce boilerplate and simplifies creation of a seperable shader, and should be your preferred way of creating seperable SP’s.
Program Pipeline Object
The second offering of this extension is the Program Pipeline Object (PPO). After linking a set of SP’s these are consolidated and named by a PPO to allow you to bind a permutation of SP’s using one named OpenGL object – the PPO. The PPO also allows uniforms to be set while bypassing the currently bound program object. The glProgramUniform* set of functions take a PPO name as an argument, and the program to apply the uniform to.
GLSL level attribute binding
Supplemental improvements to the GLSL model included in this extension are the ability to set (and override application set) vertex attribute stream locations in GLSL source text. Without this extension, developers currently have to know the attribute identifier used in GLSL code to correctly bind it at runtime e.g.:
// GLSL code:
attribute vec4 myPostion;
attribute vec3 myNormal;
void main() { ... }
// Application Code:
const int POSITION_ATTRIB_INDEX = 0;
const int NORMAL_ATTRIB_INDEX = 1;
...
glBindAttribLocation( prog, POSITION_ATTRIB_INDEX, "myPosition" );
glBindAttribLocation( prog, NORMAL_ATTRIB_INDEX, "myNormal" );
...
glVertexAttribPointer( POSITION_ATTRIB_INDEX, ... )
glVertexAttribPointer( NORMAL_ATTRIB_INDEX, ... )
...
When the EXT_seperate_shader_objects extension is available, the location index can be specified in shader source text, and also takes precedence over any glBindAttribLocation calls in application code, the above code is simplified by not needing the glBindAttribLocation calls and few changes in the GLSL text e.g.:
// GLSL code:
#extension GL_EXT_separate_shader_objects : enable
layout(location = 0) attribute vec4 myPostion;
layout(location = 1) attribute vec3 myNormal;
void main() { ... }
// Application Code:
const int POSITION_ATTRIB_INDEX = 0;
const int NORMAL_ATTRIB_INDEX = 1;
...
glVertexAttribPointer( POSITION_ATTRIB_INDEX, ... )
glVertexAttribPointer( NORMAL_ATTRIB_INDEX, ... )
...
The main caveat to this is while is simplifies application code, developers will need to be consistent across several shader files for attribute location binding, though this isn’t hard and any conflicting layout(location) indexes will cause a GLSL-linker error.
Example Usage (C++)
const GLchar* VertexProgramCode =
{
//tell the driver we need to enable particular GLSL extensions
//note that the \n is important here
"#extension GL_EXT_separate_shader_objects : enable\n"
//use the layout(location) syntax extension
"layout(location = 0) attribute vec4 position;"
"uniform mat4 mvp;"
"uniform vec4 custom_color;"
"varying lowp vec4 color;"
"void main() {"
" color = custom_color;"
" gl_Position = mvp * position;"
"}"
};
const GLchar* FragmentProgramCode =
{
"varying lowp vec4 color;"
"void main() {"
" gl_FragColor = color;"
"}"
};
class Effect
{
protected:
void log_status(GLuint prog)
{
int len(0);
const bool is_ppo(glIsProgramPipelineEXT(prog) == GL_TRUE);
//if this looks odd, it's just an ternary function pointer select
(is_ppo ? glGetProgramPipelineivEXT : glGetProgramiv)(prog, GL_INFO_LOG_LENGTH, &len);
if( len > 0 ) {
GLchar log[len];//gcc extension: non-const array size
(is_ppo ? glGetProgramPipelineInfoLogEXT : glGetProgramInfoLog)(prog, len, &len, log);
std::cerr << log << "\n";
}
}
GLuint m_vp;
GLuint m_fp;
GLuint m_ppo;
//vertex program variable locations
GLint m_mvp_location;
GLint m_custom_color_location;
public:
float m_mvp[16];
float m_custom_color[4];
Effect() : m_vp(0), m_fp(0), m_ppo(0)
{
//identity
for(int i=0; i<16; ++i)
m_mvp[i] = (i%5==0)?1.0f:0.0f;
//default to red
m_custom_color[0] = 1;
m_custom_color[1] = 0;
m_custom_color[2] = 0;
m_custom_color[3] = 1;
}
~Effect() {
unload_all();
}
bool load_shaders()
{
//in a well designed system you would pick these out of an e.g. vertex/fragment effect pool
m_vp = glCreateShaderProgramvEXT(GL_VERTEX_SHADER, 1, &VertexProgramCode);
log_status( m_vp );
//bind the vertex program variables we need
m_mvp_location = glGetUniformLocation( m_vp, "mvp");
m_custom_color_location = glGetUniformLocation( m_vp, "custom_color");
assert(m_mvp_location > -1);
assert(m_custom_color_location > -1);
m_fp = glCreateShaderProgramvEXT(GL_FRAGMENT_SHADER, 1, &FragmentProgramCode);
log_status( m_fp );
//generate and construct our PPO
glGenProgramPipelinesEXT(1, &m_ppo);
//this is where you would "mix-and-match" using the bitmask
//in the 2nd parameter
glUseProgramStagesEXT(m_ppo, GL_VERTEX_SHADER_BIT_EXT, m_vp);
glUseProgramStagesEXT(m_ppo, GL_FRAGMENT_SHADER_BIT_EXT, m_fp);
glValidateProgramPipelineEXT(m_ppo);
log_status( m_ppo );
int status(0);
glGetProgramPipelineivEXT(m_ppo, GL_VALIDATE_STATUS, &status);
if( status == 0 )
std::cerr << "unable to create program pipeline\n";
return status != 0;
}
void unload_all()
{
glUseProgram(0);
glBindProgramPipelineEXT(0);
glDeleteProgramPipelinesEXT(1, &m_ppo);
glDeleteProgram(m_vp);
glDeleteProgram(m_fp);
}
void bind_pipeline()
{
assert(m_ppo);
glBindProgramPipelineEXT(m_ppo);
glProgramUniformMatrix4fvEXT(m_vp, m_mvp_location, 1, 0, &m_mvp[0]);
glProgramUniform4fvEXT(m_vp, m_custom_color_location, 1, &m_custom_color[0]);
}
};

Thanks, this helped me when fixing a bug in my OpenGL abstraction. FWIW OT I’d just like to comment if using GLSL >= 4.10 gl_FragColor and gl_FragData are now marked as deprecated.
No probs mate, glad its been of some help
thanks for the tip about gl_Frag* -Danu