Compiling, Attaching and Linking Shaders

Before your Vertex and Fragment shader programs can be integrated into your application, you must perform the following steps:

  1. Create one or more shader objects by using glCreateShader.
  2. Provide source code for these shaders by calling glShaderSource.
  3. Compile each of the shaders with glCompileShader.
  4. Create a program object by calling glCreateProgram.
  5. Attach the shader objects by calling glAttachShader.
  6. Link the program object by calling glLinkProgram.
  7. To use the program object as part of OpenGL’s current state call glUseProgram.

When these operations are done, OpenGL will be able to use the code in your shader program.

Here is a good illustration for the steps necessary to use shaders in your application:

Figure 1. Creating, attaching and linking shader programs

Shader Compilation

The first step to compile a shader program is to create a shader object. A shader object is a data structure that stores the source code for a shader. A shader object is created with a call to glCreateShader(). We are going to create two shader objects for each source code as shown in listing 1, line 1 below.

Once a shader object has been created, it can accept source code for the vertex/fragment shader. The source code is provided to the shader object with a call to glShaderSource(). Line 2 shows a call to a method named loadShaderFile(). This is a helper method that loads in the source code written for the shaders (see listing 2). If you notice, this method calls another method called loadShaderSrc(), which ultimately calls glShaderSource().

In line 2a, we check if the source code for each shader was loaded successfully.

The shader objects are then compiled with a call to glCompileShader() (line 3). We check if the objects were compiled successfully in line 3a.

Once the shader object has been compiled. It must be attached to a Program Object. A Program Object is a container for shader objects and is created with the function glCreateProgram() as shown in line 4.

The shader object is then attached to the program object with a call to glAttachShader() (line 5). Once the shader object is attached to the program object, the shader object is linked with glLinkProgram (line 6). We check if the link was successful as shown in line 6a.

Finally, a call to glUseProgram is required to make the program object part of OpenGL’s current state (line 7).

Listing 1. Compiling, attaching and linking a shader
void OpenGLExample::loadShaders(const char* uVertexShaderProgram, const char* uFragmentShaderProgram){

// Temporary Shader objects
GLuint VertexShader;
GLuint FragmentShader;

//1. Create shader objects
VertexShader = glCreateShader(GL_VERTEX_SHADER);
FragmentShader = glCreateShader(GL_FRAGMENT_SHADER);

//2. Load both vertex & fragment shader files

//Usually you want to check the return value of the loadShaderFile function, if
//it returns true, then the shaders were found, else there was an error.

if(loadShaderFile(uVertexShaderProgram, VertexShader)==false){

glDeleteShader(VertexShader);
glDeleteShader(FragmentShader);
fprintf(stderr, "The shader at %s could not be found.\n", uVertexShaderProgram);

}else{
fprintf(stderr,"Vertex Shader was loaded successfully\n");
}

if(loadShaderFile(uFragmentShaderProgram, FragmentShader)==false){

glDeleteShader(VertexShader);
glDeleteShader(FragmentShader);
fprintf(stderr, "The shader at %s could not be found.\n", uFragmentShaderProgram);

}else{
fprintf(stderr,"Fragment Shader was loaded successfully\n");
}

//3. Compile both shader objects
glCompileShader(VertexShader);
glCompileShader(FragmentShader);

//3a. Check for errors in the compilation
GLint testVal;

//3b. Check if vertex shader object compiled successfully
glGetShaderiv(VertexShader, GL_COMPILE_STATUS, &testVal);
if(testVal == GL_FALSE)
{
char infoLog[1024];
glGetShaderInfoLog(VertexShader, 1024, NULL, infoLog);
fprintf(stderr, "The shader at %s failed to compile with the following error:\n%s\n", uVertexShaderProgram, infoLog);
glDeleteShader(VertexShader);
glDeleteShader(FragmentShader);

}else{
fprintf(stderr,"Vertex Shader compiled successfully\n");
}

//3c. Check if fragment shader object compiled successfully
glGetShaderiv(FragmentShader, GL_COMPILE_STATUS, &testVal);
if(testVal == GL_FALSE)
{
char infoLog[1024];
glGetShaderInfoLog(FragmentShader, 1024, NULL, infoLog);
fprintf(stderr, "The shader at %s failed to compile with the following error:\n%s\n", uFragmentShaderProgram, infoLog);
glDeleteShader(VertexShader);
glDeleteShader(FragmentShader);

}else{
fprintf(stderr,"Fragment Shader compiled successfully\n");
}

//4. Create a shader program object
programObject = glCreateProgram();

//5. Attach the shader objects to the shader program object
glAttachShader(programObject, VertexShader);
glAttachShader(programObject, FragmentShader);

//6. Link both shader objects to the program object
glLinkProgram(programObject);

// Make sure link had no errors
glGetProgramiv(programObject, GL_LINK_STATUS, &testVal);
if(testVal == GL_FALSE)
{
char infoLog[1024];
glGetProgramInfoLog(programObject, 1024, NULL, infoLog);
fprintf(stderr,"The programs %s and %s failed to link with the following errors:\n%s\n",
uVertexShaderProgram, uFragmentShaderProgram, infoLog);
glDeleteProgram(programObject);

}else{
fprintf(stderr,"Shaders linked successfully\n");
}

// These are no longer needed
glDeleteShader(VertexShader);
glDeleteShader(FragmentShader);

//7. Use the program
glUseProgram(programObject);
}
Listing 2. Helper function to load the shader file
bool OpenGLExample::loadShaderFile(const char *szFile, GLuint shader)
{
    GLint shaderLength = 0;
    FILE *fp;

    // Open the shader file
    fp = fopen(szFile, "r");
    if(fp != NULL)
    {
        // See how long the file is
        while (fgetc(fp) != EOF)
            shaderLength++;

        // Allocate a block of memory to send in the shader
        //assert(shaderLength < MAX_SHADER_LENGTH);   // make me bigger!
        if(shaderLength > MAX_SHADER_LENGTH)
        {
            fclose(fp);
            return false;
        }

        // Go back to beginning of file
        rewind(fp);

        // Read the whole file in
        if (shaderText != NULL)
            fread(shaderText, 1, shaderLength, fp);

        // Make sure it is null terminated and close the file
        shaderText[shaderLength] = '\0';
        fclose(fp);
    }
    else
        return false;

    // Load the string
    loadShaderSrc((const char *)shaderText, shader);

    return true;
}
Listing 3. Helper function to load the shader source
// Load the shader from the source text
void OpenGLExample::loadShaderSrc(const char *szShaderSrc, GLuint shader)
{
    GLchar *fsStringPtr[1];

    fsStringPtr[0] = (GLchar *)szShaderSrc;
    glShaderSource(shader, 1, (const GLchar **)fsStringPtr, NULL);
}

Finally, you would simply call loadShaders() with both appropriate shader files. The function will then compile, attach and link each shader program into the application.

Listing 3. Helper function to load the shader source
//Assume Shader.vsh and Shader.fsh represent the files for the vertex and fragment shader, respectively.

loadShaders("Shader.vsh", "Shader.fsh");

Harold Serrano

Computer Graphics Enthusiast. Currently developing a 3D Game Engine.