413 lines
13 KiB
C++

/***
author : r3dux
version : 0.3 - 15/01/2014
description: Gets GLSL source code either provided as strings or can load from filenames,
compiles the shaders, creates a shader program which the shaders are linked
to, then the program is validated and is ready for use via myProgram.use(),
<draw-stuff-here> then calling myProgram.disable();
Attributes and uniforms are stored in <string, int> maps and can be added
via calls to addAttribute(<name-of-attribute>) and then the attribute
index can be obtained via myProgram.attribute(<name-of-attribute>) - Uniforms
work in the exact same way.
***/
#ifndef SHADER_PROGRAM_HPP
#define SHADER_PROGRAM_HPP
#include <iostream>
#include <fstream>
#include <sstream>
#include <map>
class ShaderProgram
{
private:
// static DEBUG flag - if set to false then, errors aside, we'll run completely silent
static const bool DEBUG = true;
// We'll use an enum to differentiate between shaders and shader programs when querying the info log
enum class ObjectType
{
SHADER, PROGRAM
};
// Shader program and individual shader Ids
GLuint programId;
GLuint vertexShaderId;
GLuint fragmentShaderId;
// How many shaders are attached to the shader program
GLuint shaderCount;
// Map of attributes and their binding locations
std::map<std::string, int> attributeMap;
// Map of uniforms and their binding locations
std::map<std::string, int> uniformMap;
// Has this shader program been initialised?
bool initialised;
// ---------- PRIVATE METHODS ----------
// Private method to compile a shader of a given type
GLuint compileShader(std::string shaderSource, GLenum shaderType)
{
std::string shaderTypeString;
switch (shaderType)
{
case GL_VERTEX_SHADER:
shaderTypeString = "GL_VERTEX_SHADER";
break;
case GL_FRAGMENT_SHADER:
shaderTypeString = "GL_FRAGMENT_SHADER";
break;
case GL_GEOMETRY_SHADER:
throw std::runtime_error("Geometry shaders are unsupported at this time.");
break;
default:
throw std::runtime_error("Bad shader type enum in compileShader.");
break;
}
// Generate a shader id
// Note: Shader id will be non-zero if successfully created.
GLuint shaderId = glCreateShader(shaderType);
if (shaderId == 0)
{
// Display the shader log via a runtime_error
throw std::runtime_error("Could not create shader of type " + shaderTypeString + ": " + getInfoLog(ObjectType::SHADER, shaderId));
}
// Get the source string as a pointer to an array of characters
const char *shaderSourceChars = shaderSource.c_str();
// Attach the GLSL source code to the shader
// Params: GLuint shader, GLsizei count, const GLchar **string, const GLint *length
// Note: The pointer to an array of source chars will be null terminated, so we don't need to specify the length and can instead use NULL.
glShaderSource(shaderId, 1, &shaderSourceChars, NULL);
// Compile the shader
glCompileShader(shaderId);
// Check the compilation status and throw a runtime_error if shader compilation failed
GLint shaderStatus;
glGetShaderiv(shaderId, GL_COMPILE_STATUS, &shaderStatus);
if (shaderStatus == GL_FALSE)
{
std::string err = getInfoLog(ObjectType::SHADER, shaderId);
std::cout << err << std::endl;
throw std::runtime_error(shaderTypeString + " compilation failed: " + err);
}
else
{
if (DEBUG)
{
std::cout << shaderTypeString << " shader compilation successful." << std::endl;
}
}
// If everything went well, return the shader id
return shaderId;
}
// Private method to compile/attach/link/verify the shaders.
// Note: Rather than returning a boolean as a success/fail status we'll just consider
// a failure here to be an unrecoverable error and throw a runtime_error.
void initialise(std::string vertexShaderSource, std::string fragmentShaderSource)
{
// Compile the shaders and return their id values
vertexShaderId = compileShader(vertexShaderSource, GL_VERTEX_SHADER);
fragmentShaderId = compileShader(fragmentShaderSource, GL_FRAGMENT_SHADER);
// Attach the compiled shaders to the shader program
glAttachShader(programId, vertexShaderId);
glAttachShader(programId, fragmentShaderId);
// Link the shader program - details are placed in the program info log
glLinkProgram(programId);
// Once the shader program has the shaders attached and linked, the shaders are no longer required.
// If the linking failed, then we're going to abort anyway so we still detach the shaders.
glDetachShader(programId, vertexShaderId);
glDetachShader(programId, fragmentShaderId);
// Check the program link status and throw a runtime_error if program linkage failed.
GLint programLinkSuccess = GL_FALSE;
glGetProgramiv(programId, GL_LINK_STATUS, &programLinkSuccess);
if (programLinkSuccess == GL_TRUE)
{
if (DEBUG)
{
std::cout << "Shader program link successful." << std::endl;
}
}
else
{
throw std::runtime_error("Shader program link failed: " + getInfoLog(ObjectType::PROGRAM, programId));
}
// Validate the shader program
glValidateProgram(programId);
// Check the validation status and throw a runtime_error if program validation failed
GLint programValidatationStatus;
glGetProgramiv(programId, GL_VALIDATE_STATUS, &programValidatationStatus);
if (programValidatationStatus == GL_TRUE)
{
if (DEBUG)
{
std::cout << "Shader program validation successful." << std::endl;
}
}
else
{
throw std::runtime_error("Shader program validation failed: " + getInfoLog(ObjectType::PROGRAM, programId));
}
// Finally, the shader program is initialised
initialised = true;
}
// Private method to load the shader source code from a file
std::string loadShaderFromFile(const std::string filename)
{
// Create an input filestream and attempt to open the specified file
std::ifstream file(filename.c_str());
// If we couldn't open the file we'll bail out
if (!file.good())
{
throw std::runtime_error("Failed to open file: " + filename);
}
// Otherwise, create a string stream...
std::stringstream stream;
// ...and dump the contents of the file into it.
stream << file.rdbuf();
// Now that we've read the file we can close it
file.close();
// Finally, convert the stringstream into a string and return it
return stream.str();
}
// Private method to return the current shader program info log as a string
std::string getInfoLog(ObjectType type, int id)
{
GLint infoLogLength;
if (type == ObjectType::SHADER)
{
glGetShaderiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
}
else // type must be ObjectType::PROGRAM
{
glGetProgramiv(id, GL_INFO_LOG_LENGTH, &infoLogLength);
}
GLchar *infoLog = new GLchar[infoLogLength + 1];
if (type == ObjectType::SHADER)
{
glGetShaderInfoLog(id, infoLogLength, NULL, infoLog);
}
else // type must be ObjectType::PROGRAM
{
glGetProgramInfoLog(id, infoLogLength, NULL, infoLog);
}
// Convert the info log to a string
std::string infoLogString(infoLog);
// Delete the char array version of the log
delete[] infoLog;
// Finally, return the string version of the info log
return infoLogString;
}
public:
// Constructor
ShaderProgram()
{
// We start in a non-initialised state - calling initFromFiles() or initFromStrings() will
// initialise us.
initialised = false;
// Generate a unique Id / handle for the shader program
// Note: We MUST have a valid rendering context before generating the programId or we'll segfault!
programId = glCreateProgram();
glUseProgram(programId);
// 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 initialise a shader program from shaders provided as files
void initFromFiles(std::string vertexShaderFilename, std::string fragmentShaderFilename)
{
// Get the shader file contents as strings
std::string vertexShaderSource = loadShaderFromFile(vertexShaderFilename);
std::string fragmentShaderSource = loadShaderFromFile(fragmentShaderFilename);
initialise(vertexShaderSource, fragmentShaderSource);
}
// Method to initialise a shader program from shaders provided as strings
void initFromStrings(std::string vertexShaderSource, std::string fragmentShaderSource)
{
initialise(vertexShaderSource, fragmentShaderSource);
}
// Method to enable the shader program - we'll suggest this for inlining
inline void use()
{
// Santity check that we're initialised and ready to go...
if (initialised)
{
glUseProgram(programId);
}
else
{
std::string msg = "Shader program " + programId;
msg += " not initialised - aborting.";
throw std::runtime_error(msg);
}
}
// Method to disable the shader - we'll also suggest this for inlining
inline void disable()
{
glUseProgram(0);
}
// Method to return the bound location of a named attribute, or -1 if the attribute was not found
GLuint attribute(const std::string attributeName)
{
// You could do this method with the single line:
//
// return attributeMap[attribute];
//
// BUT, if you did, and you asked it for a named attribute which didn't exist
// like: attributeMap["FakeAttrib"] 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 then we alert the user & bail.
// Create an iterator to look through our attribute map (only create iterator on first run -
// reuse it for all further calls).
static std::map<std::string, int>::const_iterator attributeIter;
// Try to find the named attribute
attributeIter = attributeMap.find(attributeName);
// Not found? Bail.
if (attributeIter == attributeMap.end())
{
throw std::runtime_error("Could not find attribute in shader program: " + attributeName);
}
// Otherwise return the attribute location from the attribute map
return attributeMap[attributeName];
}
// Method to returns the bound location of a named uniform
GLuint uniform(const std::string uniformName)
{
// 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 (only create iterator on first run -
// reuse it for all further calls).
static std::map<std::string, int>::const_iterator uniformIter;
// Try to find the named uniform
uniformIter = uniformMap.find(uniformName);
// Found it? Great - pass it back! Didn't find it? Alert user and halt.
if (uniformIter == uniformMap.end())
{
throw std::runtime_error("Could not find uniform in shader program: " + uniformName);
}
// Otherwise return the attribute location from the uniform map
return uniformMap[uniformName];
}
// Method to add an attribute to the shader and return the bound location
int addAttribute(const std::string attributeName)
{
// Add the attribute location value for the attributeName key
attributeMap[attributeName] = glGetAttribLocation(programId, attributeName.c_str());
// Check to ensure that the shader contains an attribute with this name
if (attributeMap[attributeName] == -1)
{
throw std::runtime_error("Could not add attribute: " + attributeName + " - location returned -1.");
}
else // Valid attribute location? Inform user if we're in debug mode.
{
if (DEBUG)
{
std::cout << "Attribute " << attributeName << " bound to location: " << attributeMap[attributeName] << std::endl;
}
}
// Return the attribute location
return attributeMap[attributeName];
}
// Method to add a uniform to the shader and return the bound location
int addUniform(const std::string uniformName)
{
// Add the uniform location value for the uniformName key
uniformMap[uniformName] = glGetUniformLocation(programId, uniformName.c_str());
// Check to ensure that the shader contains a uniform with this name
if (uniformMap[uniformName] == -1)
{
int error = glGetError();
throw std::runtime_error("Could not add uniform: " + uniformName + " - location returned -1.");
}
else // Valid uniform location? Inform user if we're in debug mode.
{
if (DEBUG)
{
//std::cout << "Uniform " << uniformName << " bound to location: " << uniformMap[uniformName] << std::endl;
}
}
// Return the uniform location
return uniformMap[uniformName];
}
std::map<std::string, GLuint> _subroutine_map;
int addSubroutine(GLenum shadertype, const std::string uniformName)
{
_subroutine_map[uniformName] = glGetSubroutineIndex(programId, shadertype, uniformName.c_str());
return _subroutine_map[uniformName];
}
void enableSubroutine(const std::string uniformName)
{
use();
glUniformSubroutinesuiv(GL_FRAGMENT_SHADER, 1, &_subroutine_map[uniformName]);
disable();
}
}; // End of class
#endif // SHADER_PROGRAM_HPP