A Simple C++ OpenGL Shader Loader

Update: There’s a re-worked and improved version of this shader loading code here: https://r3dux.org/2015/01/a-simple-c-opengl-shader-loader-improved/ – you should probably use that instead of this.


I’ve been doing a bunch of OpenGL programming recently and wanted to create my own shader classes to make setting up shaders as easy as possible – so I did ;-) To create vertex and fragment shaders and tie them into a shader program you can just import the Shader.hpp and ShaderProgram.hpp classes and use code like the following:

// Set up vertex shader
Shader vertexShader(GL_VERTEX_SHADER);
vertexShader.loadFromFile("MyVertexShader.vert");
vertexShader.compile();
 
// Set up fragment shader
Shader fragmentShader(GL_FRAGMENT_SHADER);
fragmentShader.loadFromFile("MyFragmentShader.frag");
fragmentShader.compile();
 
// Set up shader program
shaderProgram = new ShaderProgram();
shaderProgram->attachShader(vertexShader);
shaderProgram->attachShader(fragmentShader);
shaderProgram->linkProgram();

There’s also a loadFromString(some-string-containing-GLSL-source-code) method, if that’s your preference.

The ShaderProgram class uses a string/int map as a key/value pair, so to add attributes or uniforms you just specify their name and they’ll have a location assigned to them:

// Add the shader attributes
shaderProgram->addAttribute("vVertex");
// ...
 
// Add the shader uniforms
shaderProgram->addUniform("pMatrix");
// ...

The ShaderProgram class then uses two methods called attribute and uniform to return the bound locations (you could argue that I should have called these methods getAttribute and getUniform – but I felt that just attribute and uniform were cleaner in use. Feel free to mod if you feel strongly about it). When binding vertex attribute pointers you can use code like this:

// Set up a vertex buffer object to hold the vertex position data
GLuint vertexBufferId;
glGenBuffers(1, &vertexBufferId);
glBindBuffer(GL_ARRAY_BUFFER, vertexBufferId);
glBufferData(GL_ARRAY_BUFFER, model.getVertexDataSizeBytes(), model.getVertexData(), GL_STATIC_DRAW);
 
// Set up the vertex attribute pointer for the vVertex attribute
glVertexAttribPointer(
    shaderProgram->attribute("vVertex"),  // Attribute location
    VERTEX_COMPONENTS,                    // Number of elements per vertex, here (x,y,z), so 3
    GL_FLOAT,                             // Data type of each element
    GL_FALSE,                             // Normalised?
    0,                                    // Stride
    0                                     // Offset
);

Finally, when drawing your geometry you can get just enable the shader program, provide the location and data for bound uniforms, and then disable it like this (I’m using the GL Mathematics library for matrices – you can use anything you fancy):

shaderProgram->use();
 
    // Provide uniform data
    glUniformMatrix4fv( shaderProgram->uniform("mMatrix"), 1, GL_FALSE, glm::value_ptr(mMatrix) );
    // ...
 
    // Draw stuff
 
shaderProgram->disable();

That’s pretty much it – nice and simple. I haven’t done anything with geometry shaders yet so I’ve no idea if there’s anything else you’ll need, but if so it likely won’t be too tricky a job to implement it yourself. Anyways, you can look at the source code for the classes themselves below, and I’ll put the two classes in a zip file here: ShaderHelperClasses.zip.

As a final note, you can’t create anything shader-y without having a valid OpenGL rendering context (i.e. a window to draw stuff to) or the code will segfault – that’s just how it works. The easiest way around this if you want to keep a global ShaderProgram object around is to create it as a pointer (i.e. ShaderProgram *shaderProgram;) and then initialise it later on when you’ve got the window open with shaderProgram = new ShaderProgram(); like I’ve done above.

Cheers! =D

Shader.hpp

#ifndef SHADER_HPP
#define SHADER_HPP
 
#include <iostream>
#include <string>
#include <sstream>
#include <fstream>
 
using std::cout;
using std::endl;
using std::string;
using std::stringstream;
using std::ifstream;
 
class Shader
{
	private:
		GLuint id;         // The unique ID / handle for the shader
		std::string typeString; // String representation of the shader type (i.e. "Vertex" or such)
		std::string source;     // The shader source code (i.e. the GLSL code itself)
 
	public:
		// Constructor
		Shader(const GLenum &type)
		{
			// Get the type of the shader
			if (type == GL_VERTEX_SHADER)
			{
				typeString = "Vertex";
			}
			else if (type == GL_FRAGMENT_SHADER)
			{
				typeString = "Fragment";
			}
			else
			{
				typeString = "Geometry";
			}
 
			// Create the vertex shader id / handle
			// Note: If you segfault here you probably don't have a valid rendering context.
			id = glCreateShader(type);
		}
 
		GLuint getId()
		{
			return id;
		}
 
		string getSource()
		{
			return source;
		}
 
		// Method to load the shader contents from a file
		void loadFromString(const string &sourceString)
		{
			// Keep hold of a copy of the source
			source = sourceString;
 
			// Get the source as a pointer to an array of characters
			const char *sourceChars = source.c_str();
 
			// Associate the source with the shader id
			glShaderSource(id, 1, &sourceChars, NULL);
		}
 
		// Method to load the shader contents from a string
		void loadFromFile(const string &filename)
		{
			std::ifstream file;
 
			file.open( filename.c_str() );
 
			if (!file.good() )
			{
				std::cout << "Failed to open file: " << filename << std::endl;
				exit(-1);
			}
 
			// Create a string stream
			std::stringstream stream;
 
			// Dump the contents of the file into it
			stream << file.rdbuf();
 
			// Close the file
			file.close();
 
			// Convert the StringStream into a string
			source = stream.str();
 
			// Get the source string as a pointer to an array of characters
			const char *sourceChars = source.c_str();
 
			// Associate the source with the shader id
			glShaderSource(id, 1, &sourceChars, NULL);
		}
 
 
		// Method to compile a shader and display any problems if compilation fails
		void compile()
		{
			// Compile the shader
			glCompileShader(id);
 
			// Check the compilation status and report any errors
			GLint shaderStatus;
			glGetShaderiv(id, GL_COMPILE_STATUS, &shaderStatus);
 
			// If the shader failed to compile, display the info log and quit out
			if (shaderStatus == GL_FALSE)
			{
				GLint infoLogLength;
				glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
 
				GLchar *strInfoLog = new GLchar[infoLogLength + 1];
				glGetShaderInfoLog(id, infoLogLength, NULL, strInfoLog);
 
				std::cout << typeString << " shader compilation failed: " << strInfoLog << std::endl;
				delete[] strInfoLog;
				glfwTerminate();
			}
			else
			{
				cout << typeString << " shader compilation OK" << endl;
			}
		}
};
 
#endif

ShaderProgram.hpp

#ifndef SHADER_PROGRAM_HPP
#define SHADER_PROGRAM_HPP
 
#include <iostream>
#include <map>
 
#include "Shader.hpp"
 
using std::map;
using std::string;
using std::cout;
using std::endl;
 
 
class ShaderProgram
{
	private:
		GLuint programId;   // The unique ID / handle for the shader program
		GLuint shaderCount; // How many shaders are attached to the shader program
 
		// Map of attributes and their binding locations
		std::map<string,int> attributeLocList;
 
		// Map of uniforms and their binding locations
		std::map<string,int> uniformLocList;
 
	public:
		// Constructor
		ShaderProgram()
		{
			// Generate a unique Id / handle for the shader program
			// Note: We MUST have a valid rendering context before generating
			// the programId or it causes a segfault!
			programId = glCreateProgram();
 
			// Initially, we have zero shaders attached to the program
			shaderCount = 0;
		}
 
 
		// Destructor
		~ShaderProgram()
		{
			// Delete the shader program from the graphics card memory to
			// free all the resources it's been using
			glDeleteProgram(programId);
		}
 
 
		// Method to attach a shader to the shader program
		void attachShader(Shader shader)
		{
			// Attach the shader to the program
			// Note: We identify the shader by its unique Id value
			glAttachShader( programId, shader.getId() );
 
			// Increment the number of shaders we have associated with the program
			shaderCount++;
		}
 
 
		// Method to link the shader program and display the link status
		void linkProgram()
		{
			// If we have at least two shaders (like a vertex shader and a fragment shader)...
			if (shaderCount >= 2)
			{
				// Perform the linking process
				glLinkProgram(programId);
 
				// Check the status
				GLint linkStatus;
				glGetProgramiv(programId, GL_LINK_STATUS, &linkStatus);
				if (linkStatus == GL_FALSE)
				{
					std::cout << "Shader program linking failed." << std::endl;
					glfwTerminate();
				}
				else
				{
					cout << "Shader program linking OK." << endl;
				}
			}
			else
			{
				cout << "Can't link shaders - you need at least 2, but attached shader count is only: " << shaderCount << endl;
				glfwTerminate();
			}
		}
 
 
		// Method to enable the shader program
		void use()
		{
			glUseProgram(programId);
		}
 
 
		// Method to disable the shader program
		void disable()
		{
			glUseProgram(0);
		}
 
 
		// Returns the bound location of a named attribute
		GLuint attribute(const string &attribute)
		{
			// You could do this function with the single line:
			//
			//		return attributeLocList[attribute];
			//
			// BUT, if you did, and you asked it for a named attribute
			// which didn't exist, like, attributeLocList["ThisAttribDoesn'tExist!"]
			// then the method would return an invalid value which will likely cause
			// the program to segfault. So we're making sure the attribute asked
			// for exists, and if it doesn't we can alert the user and stop rather than bombing out later.
 
			// Create an iterator to look through our attribute map and try to find the named attribute
			std::map<string, int>::iterator it = attributeLocList.find(attribute);
 
			// Found it? Great -return the bound location! Didn't find it? Alert user and halt.
			if ( it != attributeLocList.end() )
			{
				return attributeLocList[attribute];
			}
			else
			{
				std::cout << "Could not find attribute in shader program: " << attribute << std::endl;
				exit(-1);
			}
		}
 
 
		// Method to returns the bound location of a named uniform
		GLuint uniform(const string &uniform)
		{
			// Note: You could do this method with the single line:
			//
			// 		return uniformLocList[uniform];
			//
			// But we're not doing that. Explanation in the attribute() method above.
 
			// Create an iterator to look through our uniform map and try to find the named uniform
			std::map<string, int>::iterator it = uniformLocList.find(uniform);
 
			// Found it? Great - pass it back! Didn't find it? Alert user and halt.
			if ( it != uniformLocList.end() )
			{
				return uniformLocList[uniform];
			}
			else
			{
				std::cout << "Could not find uniform in shader program: " << uniform << std::endl;
				exit(-1);
			}
		}
 
 
		// Method to add an attrbute to the shader and return the bound location
		int addAttribute(const string &attributeName)
		{
			attributeLocList[attributeName] = glGetAttribLocation( programId, attributeName.c_str() );
 
			// Check to ensure that the shader contains an attribute with this name
			if (attributeLocList[attributeName] == -1)
			{
				cout << "Could not add attribute: " << attributeName << " - location returned -1!" << endl;
				exit(-1);
			}
			else
			{
				cout << "Attribute " << attributeName << " bound to location: " << attributeLocList[attributeName] << endl;
			}
 
			return attributeLocList[attributeName];
		}
 
 
		// Method to add a uniform to the shader and return the bound location
		int addUniform(const string &uniformName)
		{
			uniformLocList[uniformName] = glGetUniformLocation( programId, uniformName.c_str() );
 
			// Check to ensure that the shader contains a uniform with this name
			if (uniformLocList[uniformName] == -1)
			{
				cout << "Could not add uniform: " << uniformName << " - location returned -1!" << endl;
				exit(-1);
			}
			else
			{
				cout << "Uniform " << uniformName << " bound to location: " << uniformLocList[uniformName] << endl;
			}
 
			return uniformLocList[uniformName];
		}
 
};
 
#endif // SHADER_PROGRAM_HPP

6 thoughts on “A Simple C++ OpenGL Shader Loader”

  1. Thanks for the sample!

    I think you made a typo on line 74 of ShaderProgram.hpp

    if (GL_LINK_STATUS == GL_FALSE)

    should be

    if (linkStatus == GL_FALSE)

    Also, you should consider passing arguments by reference rather than by value in your member functions. :)

    1. You’re absolutely correct on both of your suggestions – I’ll modify the code accordingly!

      Glad you found the code of use & thanks for your feedback =D

      1. Oh, and while looking at my code – I passed arguments to the methods as references, rather than having the methods accept the arguments as references! I’ve no idea why I did that, and having the methods do the referencing is definitely the best way of going about things. Learning is fun =D

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.