Lazy Foo' Productions

SDL Forums external link SDL Tutorials Articles OpenGL Tutorials OpenGL Forums external link
Follow BlueSky Follow Facebook Follow Twitter Follow Threads
Donate
News FAQs Contact Bugs

Loading Text File Shaders

Loading Text File Shaders screenshot

Last Updated: Oct 19th, 2014

In the last tutorial we hard coded the GLSL shader code in our OpenGL application for the sake of simplicity. But when you're trying to develop a shader program, having to recompile your application each time to make changes gets to be a pain. In this tutorial, our OpenGL application will load the GLSL source code on the fly.
From LShaderProgram.h
        GLuint loadShaderFromFile( std::string path, GLenum shaderType );
        /*
        Pre Condition:
         -None
        Post Condition:
         -Returns the ID of a compiled shader of the specified type from the specified file
         -Reports error to console if file could not be found or compiled
        Side Effects:
         -None
        */
The base shader class gets a new function to load a text GLSL source file and compile it to the type of shader you want (vertex, fragment, geometry, etc).
From LShaderProgram.cpp
GLuint LShaderProgram::loadShaderFromFile( std::string path, GLenum shaderType )
{
    //Open file
    GLuint shaderID = 0;
    std::string shaderString;
    std::ifstream sourceFile( path.c_str() );

    //Source file loaded
    if( sourceFile )
    {
        //Get shader source
        shaderString.assign( ( std::istreambuf_iterator< char >( sourceFile ) ), std::istreambuf_iterator< char >() );
To compile a text source file at runtime, you need to get the text from the file as a single string. After opening the file as an ifstream, we can use the input stream buffer iterator to read it in as one line.

The standard string function assign() just gives the string a value. The first argument tells it to start reading the source file from the beginning. The second argument tells it to keep reading the source file until it encounters a null character.
From LShaderProgram.cpp
        //Create shader ID
        shaderID = glCreateShader( shaderType );

        //Set shader source
        const GLchar* shaderSource = shaderString.c_str();
        glShaderSource( shaderID, 1, (const GLchar**)&shaderSource, NULL );

        //Compile shader source
        glCompileShader( shaderID );

        //Check shader for errors
        GLint shaderCompiled = GL_FALSE;
        glGetShaderiv( shaderID, GL_COMPILE_STATUS, &shaderCompiled );
        if( shaderCompiled != GL_TRUE )
        {
            printf( "Unable to compile shader %d!\n\nSource:\n%s\n", shaderID, shaderSource );
            printShaderLog( shaderID );
            glDeleteShader( shaderID );
            shaderID = 0;
        }
    }
    else
    {
        printf( "Unable to open file %s\n", path.c_str() );
    }

    return shaderID;
}
The rest of this function should all look familiar because it's a generalized version of the vertex/fragment shader compilation code from the last tutorial.
From LPlainPolygonProgram2D.cpp
bool LPlainPolygonProgram2D::loadProgram()
{
    //Generate program
    mProgramID = glCreateProgram();

    //Load vertex shader
    GLuint vertexShader = loadShaderFromFile( "30_loading_text_file_shaders/LPlainPolygonProgram2D.glvs", GL_VERTEX_SHADER );

    //Check for errors
    if( vertexShader == 0 )
    {
        glDeleteProgram( mProgramID );
        mProgramID = 0;
        return false;
    }

    //Attach vertex shader to program
    glAttachShader( mProgramID, vertexShader );


    //Create fragment shader
    GLuint fragmentShader = loadShaderFromFile( "30_loading_text_file_shaders/LPlainPolygonProgram2D.glfs", GL_FRAGMENT_SHADER );

    //Check for errors
    if( fragmentShader == 0 )
    {
        glDeleteShader( vertexShader );
        glDeleteProgram( mProgramID );
        mProgramID = 0;
        return false;
    }

    //Attach fragment shader to program
    glAttachShader( mProgramID, fragmentShader );

    //Link program
    glLinkProgram( mProgramID );

    //Check for errors
    GLint programSuccess = GL_TRUE;
    glGetProgramiv( mProgramID, GL_LINK_STATUS, &programSuccess );
    if( programSuccess != GL_TRUE )
    {
        printf( "Error linking program %d!\n", mProgramID );
        printProgramLog( mProgramID );
        glDeleteShader( vertexShader );
        glDeleteShader( fragmentShader );
        glDeleteProgram( mProgramID );
        mProgramID = 0;
        return false;
    }

    //Clean up excess shader references
    glDeleteShader( vertexShader );
    glDeleteShader( fragmentShader );

    return true;
}
Here we have the shader program loading code with our new condensed vertex shader loading function.

You'll see that source file for the vertex shader is LPlainPolygonProgram2D.glvs. The source file has the matching file name LPlainPolygonProgram2D.glvs because it makes it easier to keep track of the files. The extensions glvs and glfs correspond to the vertex and fragment shader also because it makes the files easier to manage. The file name and extension don't mean anything to the program because it just assumes they're ASCII text anyways.

About the calls to glDeleteShader(): you'll notice that if the fragment shader load fails we delete the vertex shader because it's useless to us if the fragment shader doesn't compile. We delete the vertex and the fragment shader if they fail to link because if they don't link they're useless on their own. What you may think is strange is that we delete the vertex and fragment shader if the program links successfully. Don't worry, all we're deleting when we delete a shader from an existing program is the spare references to the shaders. The OpenGL context is smart enough to know that if a shader program is using the shader to not delete it.
From LUtil.cpp
bool loadGP()
{
    //Load basic shader program
    if( !gPlainPolygonProgram2D.loadProgram() )
    {
        printf( "Unable to load basic shader!\n" );
        return false;
    }

    //Bind basic shader program
    gPlainPolygonProgram2D.bind();

    return true;
}

bool loadMedia()
{
    return true;
}

void update()
{

}

void render()
{
    //Clear color buffer
    glClear( GL_COLOR_BUFFER_BIT );

    //Reset transformations
    glLoadIdentity();

    //Solid cyan quad in the center
    glTranslatef( SCREEN_WIDTH / 2.f, SCREEN_HEIGHT / 2.f, 0.f );
    glBegin( GL_QUADS );
        glColor3f( 0.f, 1.f, 1.f );
        glVertex2f( -50.f, -50.f );
        glVertex2f(  50.f, -50.f );
        glVertex2f(  50.f,  50.f );
        glVertex2f( -50.f,  50.f );
    glEnd();

    //Update screen
    glutSwapBuffers();
}
As you can see our main functions haven't changed much yet we get this when we run the program:
preview

Let's look at our GLSL source files to see why.
From LPlainPolygonProgram2D.glvs
void main()
{
    //Process vertex
    gl_Position = gl_Vertex;
}
So it looks like we're still using untranformed vertices to render.
From LPlainPolygonProgram2D.glfs
void main()
{
    //Set fragment
    gl_FragColor = vec4( 0.0, 1.0, 0.0, 1.0 );
}
And the reason it's green now is because all fragments are getting the value red 0, green 1, blue 0, and alpha 1. Fortunately now we can mess with the GLSL code without a recompile.