Once we have compiled our shaders and before we can actually install them into the OpenGL pipeline, we need to link them together into a shader program. Among other things, the linking step involves making the connections between input variables from one shader to output variables of another, and making the connections between the input/output variables of a shader to appropriate locations in the OpenGL environment.
Linking involves steps that are similar to those involved in compiling a shader. We attach each shader object to a new shader program object and then tell the shader program object to link (making sure that the shader objects are compiled before linking).
![](https://static.packt-cdn.com/products/9781782167020/graphics/7020OS_01_02.jpg)
For this recipe, we'll assume that you've already compiled two shader objects whose handles are stored in the variables vertShader
and fragShader
.
For this and a few other recipes in this Chapter, we'll use the following source code for the fragment shader:
#version 430 in vec3 Color; out vec4 FragColor; void main() { FragColor = vec4(Color, 1.0); }
For the vertex shader, we'll use the source code from the previous recipe, Compiling a shader.
In our OpenGL initialization function, and after the compilation of shader objects referred to by vertShader
and fragShader
, use the following steps:
Create the program object using the following code:
GLuint programHandle = glCreateProgram(); if( 0 == programHandle ) { fprintf(stderr, "Error creating program object.\n"); exit(1); }
Attach the shaders to the program object as follows:
glAttachShader( programHandle, vertShader ); glAttachShader( programHandle, fragShader );
Link the program:
glLinkProgram( programHandle );
Verify the link status:
GLint status; glGetProgramiv( programHandle, GL_LINK_STATUS, &status ); if( GL_FALSE == status ) { fprintf( stderr, "Failed to link shader program!\n" ); GLint logLen; glGetProgramiv(programHandle, GL_INFO_LOG_LENGTH, &logLen); if( logLen > 0 ) { char * log = new char[logLen]; GLsizei written; glGetProgramInfoLog(programHandle, logLen, &written, log); fprintf(stderr, "Program log: \n%s", log); delete [] log; } }
If linking is successful, install the program into the OpenGL pipeline:
else { glUseProgram( programHandle ); }
We start by calling glCreateProgram
to create an empty program object. This function returns a handle to the program object, which we store in a variable named programHandle
. If an error occurs with program creation, the function will return 0. We check for that, and if it occurs, we print an error message and exit.
Next, we attach each shader to the program object using glAttachShader
. The first argument is the handle to the program object, and the second is the handle to the shader object to be attached.
Then, we link the program by calling glLinkProgram
, providing the handle to the program object as the only argument. As with compilation, we check for the success or failure of the link, with the subsequent query.
We check the status of the link by calling glGetProgramiv
. Similar to glGetShaderiv
, glGetProgramiv
allows us to query various attributes of the shader program. In this case, we ask for the status of the link by providing GL_LINK_STATUS
as the second argument. The status is returned in the location pointed to by the third argument, in this case named status
.
The link status is either GL_TRUE
or GL_FALSE
indicating the success or failure of the link. If the value of status is GL_FALSE
, we retrieve and display the program information log, which should contain additional information and error messages. The program log is retrieved by the call to glGetProgramInfoLog
. The first argument is the handle to the program object, the second is the size of the buffer to contain the log, the third is a pointer to a GLsizei
variable where the number of bytes written to the buffer will be stored (excluding the null terminator), and the fourth is a pointer to the buffer that will store the log. The buffer can be allocated based on the size returned by the call to glGetProgramiv
with the parameter GL_INFO_LOG_LENGTH
. The string that is provided in log
will be properly null terminated.
Finally, if the link is successful, we install the program into the OpenGL pipeline by calling glUseProgram
, providing the handle to the program as the argument.
With the simple fragment shader from this recipe and the vertex shader from the previous recipe compiled, linked, and installed into the OpenGL pipeline, we have a complete OpenGL pipeline and are ready to begin rendering. Drawing a triangle and supplying different values for the Color
attribute yields an image of a multi-colored triangle where the vertices are red, green, and blue, and inside the triangle, the three colors are interpolated, causing a blending of colors throughout.
![](https://static.packt-cdn.com/products/9781782167020/graphics/7020OS_01_03.jpg)
You can use multiple shader programs within a single OpenGL program. They can be swapped in and out of the OpenGL pipeline by calling glUseProgram
to select the desired program.
If a program is no longer needed, it can be deleted from OpenGL memory by calling glDeleteProgram
, providing the program handle as the only argument. This invalidates the handle and frees the memory used by the program. Note that if the program object is currently in use, it will not be immediately deleted, but will be flagged for deletion when it is no longer in use.
Also, the deletion of a shader program detaches the shader objects that were attached to the program but does not delete them unless those shader objects have already been flagged for deletion by a previous call to glDeleteShader
.