I come for my shader class review for the third time now. But I've updated a lot ( which might or might not be visible). And I hope I'll get more reviews this time. I made my shader class in a way so that I can use it as a header and lib only for my future projects. I've added a lot of functions on it. I tried making it in a way that I can forget about needing to make another shader class in the future. I tried adding in everything I know of so far. Here's my code:
SHADER.h
#pragma once
#include <string>
#include <map>
#include <unordered_map>
#include <vector>
#include <GL/glew.h>
#include <glm/glm.hpp>
namespace SHADER {
/// @brief Enumerates the different types of shaders that can be part of a program.
enum class Shader_Type {
NONE = -1,
VERTEX,
FRAGMENT,
GEOMETRY,
TESS_EVAL,
TESS_CONTROL,
};
/**
* @class Shader
* @brief A C++ wrapper for an OpenGL shader program, managing its lifecycle and uniforms.
*
* This class handles the compilation, linking, and validation of GLSL shaders.
* It provides an RAII-style interface, ensuring the GPU program is deleted upon destruction.
* It also includes a uniform location cache for performance and detailed error reporting.
*/
class Shader {
private:
std::map<Shader_Type, std::string> m_Data;
GLuint m_ProgramID ;
std::unordered_map<std::string, GLint> m_UniformLocationCache;
std::string m_shaderName;
GLuint CompileShader(GLuint type, const std::string& source);
void CheckCompileErrors(GLuint shader, GLuint type);
void LinkAndValidateProgram();
public:
/// @brief Default constructor. Creates an empty, uninitialized shader object.
Shader();
/// @brief Destructor. Automatically deletes the linked OpenGL shader program from the GPU.
~Shader();
/// @brief Move constructor. Transfers ownership of the shader program from another object.
Shader(Shader&& other) noexcept;
/// @brief Move assignment operator. Transfers ownership of the shader program from another object.
Shader& operator=(Shader&& other) noexcept;
/// @brief Deleted copy constructor to prevent accidental copying of a unique GPU resource.
Shader(const Shader& other) = delete;
/// @brief Deleted copy assignment operator to prevent accidental copying.
Shader& operator=(const Shader& other) = delete;
/// @brief Binds this shader program, making it the active program for subsequent rendering calls.
void Bind() const;
/// @brief Statically unbinds any currently active shader program by binding program 0.
void Unbind() const;
/// @brief Deletes the current GPU program and clears all internal state, returning the object to its default-constructed state.
void Reset();
/**
* @brief Assigns a human-readable name to the shader for clearer error messages.
* @param name The debug name for the shader.
*/
void SetShaderName(const std::string& name) { m_shaderName = name; }
/**
* @brief Initializes the shader by compiling and linking the provided shader sources.
* @param vertexShader The source code for the vertex shader.
* @param fragmentShader The source code for the fragment shader.
* @param geometryShader Optional source code for the geometry shader.
* @param tess_eval_shader Optional source code for the tessellation evaluation shader.
* @param tess_control_shader Optional source code for the tessellation control shader.
* @throws std::runtime_error if compilation, linking, or validation fails.
*/
void Init(
const std::string& vertexShader = "", const std::string& fragmentShader = "",
const std::string& geometryShader = "", const std::string& tess_eval_shader = "",
const std::string& tess_control_shader = "");
/// @brief Gets the raw OpenGL program ID for this shader.
/// @return The GLuint handle for the shader program.
GLuint ProgramId() const { return m_ProgramID ; }
/**
* @brief Gets the location of a uniform variable within the shader program.
* @details This function caches the location after the first lookup for performance.
* @param name The name of the uniform variable in the GLSL code.
* @return The GLint location of the uniform.
* @throws std::runtime_error if the uniform is not found or is unused by the shader program.
*/
GLint GetUniformLocation(const std::string& name);
public: //uniform functions
/// @brief Sets a float uniform.
void SetUniform1f(const std::string& name, GLfloat v1);
void SetUniform2f(const std::string& name, GLfloat v1, GLfloat v2);
void SetUniform3f(const std::string& name, GLfloat v1, GLfloat v2, GLfloat v3);
void SetUniform4f(const std::string& name, GLfloat v1, GLfloat v2, GLfloat v3, GLfloat v4);
/// @brief Sets an integer uniform.
void SetUniform1i(const std::string& name, GLint v1);
void SetUniform2i(const std::string& name, GLint v1, GLint v2);
void SetUniform3i(const std::string& name, GLint v1, GLint v2, GLint v3);
void SetUniform4i(const std::string& name, GLint v1, GLint v2, GLint v3, GLint v4);
/// @brief Sets a double uniform.
void SetUniform1d(const std::string& name, GLdouble v1);
void SetUniform2d(const std::string& name, GLdouble v1, GLdouble v2);
void SetUniform3d(const std::string& name, GLdouble v1, GLdouble v2, GLdouble v3);
void SetUniform4d(const std::string& name, GLdouble v1, GLdouble v2, GLdouble v3, GLdouble v4);
/// @brief Sets a float vector uniform from a glm::fvec.
void SetUniform1fv(const std::string& name, const glm::fvec1& v);
void SetUniform2fv(const std::string& name, const glm::fvec2& v);
void SetUniform3fv(const std::string& name, const glm::fvec3& v);
void SetUniform4fv(const std::string& name, const glm::fvec4& v);
/// @brief Sets an integer vector uniform from a glm::ivec.
void SetUniform1iv(const std::string& name, const glm::ivec1& v);
void SetUniform2iv(const std::string& name, const glm::ivec2& v);
void SetUniform3iv(const std::string& name, const glm::ivec3& v);
void SetUniform4iv(const std::string& name, const glm::ivec4& v);
/// @brief Sets a double vector uniform from a glm::dvec.
void SetUniform1dv(const std::string& name, const glm::dvec1& v);
void SetUniform2dv(const std::string& name, const glm::dvec2& v);
void SetUniform3dv(const std::string& name, const glm::dvec3& v);
void SetUniform4dv(const std::string& name, const glm::dvec4& v);
/// @brief Sets a matrix uniform from a glm::mat.
void SetUniformMat2fv(const std::string& name, const glm::mat2& value, GLboolean transpose = GL_FALSE);
void SetUniformMat3fv(const std::string& name, const glm::mat3& value, GLboolean transpose = GL_FALSE);
void SetUniformMat4fv(const std::string& name, const glm::mat4& value, GLboolean transpose = GL_FALSE);
/// @brief Sets a non-square matrix uniform from a glm::mat.
void SetUniformMatrix2x3fv(const std::string& name, GLboolean transpose, const glm::mat2x3& value);
void SetUniformMatrix2x4fv(const std::string& name, GLboolean transpose, const glm::mat2x4& value);
void SetUniformMatrix3x2fv(const std::string& name, GLboolean transpose, const glm::mat3x2& value);
void SetUniformMatrix3x4fv(const std::string& name, GLboolean transpose, const glm::mat3x4& value);
void SetUniformMatrix4x2fv(const std::string& name, GLboolean transpose, const glm::mat4x2& value);
void SetUniformMatrix4x3fv(const std::string& name, GLboolean transpose, const glm::mat4x3& value);
};
}
SHADER.cpp
#include "pch.h"
#include "SHADER.h"
#include <stdexcept>
namespace SHADER {
Shader::Shader() : m_ProgramID (0), m_shaderName("Unnamed Shader") {}
Shader::~Shader() {
Reset();
}
Shader::Shader(Shader&& other) noexcept
: m_ProgramID (other.m_ProgramID ),
m_UniformLocationCache(std::move(other.m_UniformLocationCache)),
m_Data(std::move(other.m_Data)), m_shaderName(other.m_shaderName) {
other.m_shaderName = "";
other.m_ProgramID = 0;
}
Shader& Shader::operator=(Shader&& other) noexcept {
if (this != &other) {
if (glIsProgram(m_ProgramID ))glDeleteProgram(m_ProgramID );
m_ProgramID = other.m_ProgramID ;
m_UniformLocationCache = std::move(other.m_UniformLocationCache);
m_Data = std::move(other.m_Data);
m_shaderName = other.m_shaderName;
other.m_ProgramID = 0;
other.m_shaderName = "";
}
return *this;
}
void Shader::Bind() const {
glUseProgram(m_ProgramID );
}
void Shader::Unbind() const {
glUseProgram(0);
}
void Shader::Reset() {
if (glIsProgram(m_ProgramID ))glDeleteProgram(m_ProgramID );
m_ProgramID = 0;
m_Data.clear();
m_UniformLocationCache.clear();
}
void Shader::Init(const std::string& vertexShader, const std::string& fragmentShader, const std::string& geometryShader, const std::string& tess_eval_shader, const std::string& tess_control_shader)
{
if (m_ProgramID != 0) {
throw std::runtime_error("Shader [" + m_shaderName + "]:[ERROR]: Program already initialized. Call Reset() first.");
}
if (vertexShader.empty() && fragmentShader.empty() && geometryShader.empty() &&
tess_eval_shader.empty() && tess_control_shader.empty()) {
throw std::runtime_error("Shader [" + m_shaderName + "]:[ERROR]: No shaders provided to Init().");
}
m_Data.clear();
if (!vertexShader.empty())m_Data[Shader_Type::VERTEX] = vertexShader;
if (!fragmentShader.empty())m_Data[Shader_Type::FRAGMENT] = fragmentShader;
if (!geometryShader.empty())m_Data[Shader_Type::GEOMETRY] = geometryShader;
if (!tess_eval_shader.empty())m_Data[Shader_Type::TESS_EVAL] = tess_eval_shader;
if (!tess_control_shader.empty())m_Data[Shader_Type::TESS_CONTROL] = tess_control_shader;
LinkAndValidateProgram();
Unbind();
}
GLint Shader::GetUniformLocation(const std::string& name)
{
std::string errorMsg;
if (m_UniformLocationCache.find(name) != m_UniformLocationCache.end())
{
return m_UniformLocationCache[name];
}
GLint location = glGetUniformLocation(m_ProgramID , name.c_str());
if (location == -1)
{
errorMsg = "Shader [" + m_shaderName + "]:[ERROR]: Uniform " + name + " is unused or not found or does not exist!";
throw std::runtime_error(errorMsg);
}
else
m_UniformLocationCache[name] = location;
return location;
}
void Shader::LinkAndValidateProgram() {
m_ProgramID = glCreateProgram();
if (m_ProgramID == 0) {
throw std::runtime_error("Shader [" + m_shaderName + "]:[ERROR]: Failed to create shader program.");
}
std::string errorMsg;
if (!m_Data[Shader_Type::VERTEX].empty()) {
GLuint vs = CompileShader(GL_VERTEX_SHADER, m_Data[Shader_Type::VERTEX]);
glAttachShader(m_ProgramID , vs);
glDeleteShader(vs);
}
if (!m_Data[Shader_Type::FRAGMENT].empty()) {
GLuint fs = CompileShader(GL_FRAGMENT_SHADER, m_Data[Shader_Type::FRAGMENT]);
glAttachShader(m_ProgramID , fs);
glDeleteShader(fs);
}
if (!m_Data[Shader_Type::GEOMETRY].empty()) {
GLuint gs = CompileShader(GL_GEOMETRY_SHADER, m_Data[Shader_Type::GEOMETRY]);
glAttachShader(m_ProgramID , gs);
glDeleteShader(gs);
}
if (!m_Data[Shader_Type::TESS_EVAL].empty()) {
GLuint ts = CompileShader(GL_TESS_EVALUATION_SHADER, m_Data[Shader_Type::TESS_EVAL]);
glAttachShader(m_ProgramID , ts);
glDeleteShader(ts);
}
if (!m_Data[Shader_Type::TESS_CONTROL].empty()) {
GLuint ts = CompileShader(GL_TESS_CONTROL_SHADER, m_Data[Shader_Type::TESS_CONTROL]);
glAttachShader(m_ProgramID , ts);
glDeleteShader(ts);
}
glLinkProgram(m_ProgramID );
Bind();
glValidateProgram(m_ProgramID );
GLint validateStatus = 0;
glGetProgramiv(m_ProgramID , GL_VALIDATE_STATUS, &validateStatus);
if (validateStatus == GL_FALSE) {
GLint logLength = 0;
glGetProgramiv(m_ProgramID , GL_INFO_LOG_LENGTH, &logLength);
std::vector<GLchar> log(logLength);
glGetProgramInfoLog(m_ProgramID , logLength, nullptr, log.data());
std::string validateMsg(log.begin(), log.end());
errorMsg = "Shader [" + m_shaderName + "]:[ERROR]: Shader Program Validation Failed:\n" + validateMsg;
throw std::runtime_error(errorMsg);
}
GLint success;
glGetProgramiv(m_ProgramID , GL_LINK_STATUS, &success);
if (!success) {
GLint infoLogLength = 0;
glGetProgramiv(m_ProgramID , GL_INFO_LOG_LENGTH, &infoLogLength);
if (infoLogLength > 0) {
std::vector<GLchar> infoLog(infoLogLength);
glGetProgramInfoLog(m_ProgramID , infoLogLength, nullptr, infoLog.data());
std::string errorMessage(infoLog.begin(), infoLog.end());
errorMsg = "Shader [" + m_shaderName + "]:[ERROR]: Shader Program Linking Error: " + errorMessage;
}
else {
errorMsg = "Shader [" + m_shaderName + "]:[ERROR]: Shader Program Linking Error: Unknown error (no details)";
}
throw std::runtime_error(errorMsg);
}
}
GLuint Shader::CompileShader(GLuint type, const std::string& source) {
GLuint id = glCreateShader(type);
if (id == 0) {
throw std::runtime_error("Shader [" + m_shaderName + "]:[ERROR]: Failed to create shader.");
}
const GLchar* src = source.c_str();
glShaderSource(id, 1, &src, nullptr);
glCompileShader(id);
CheckCompileErrors(id, type);
return id;
}
void Shader::CheckCompileErrors(GLuint shader, GLuint type) {
GLint success;
glGetShaderiv(shader, GL_COMPILE_STATUS, &success);
if (!success) {
GLchar infoLog[2048];
glGetShaderInfoLog(shader, sizeof(infoLog), nullptr, infoLog);
std::string shaderType;
switch (type) {
case GL_VERTEX_SHADER: shaderType = "VERTEX"; break;
case GL_FRAGMENT_SHADER: shaderType = "FRAGMENT"; break;
case GL_GEOMETRY_SHADER: shaderType = "GEOMETRY"; break;
case GL_TESS_EVALUATION_SHADER: shaderType = "TESSELLATION-EVALUATION"; break;
case GL_TESS_CONTROL_SHADER: shaderType = "TESSELLATION-CONTROL"; break;
default: shaderType = "UNKNOWN"; break;
}
std::string errorMsg = "Shader [" + m_shaderName + "]:[ERROR]: Shader Compilation Error On (" + shaderType + ") Shader:\n " + std::string(infoLog);
glDeleteShader(shader);
throw std::runtime_error(errorMsg);
}
}
void Shader::SetUniform1f(const std::string& name, GLfloat v1) { glUniform1f(GetUniformLocation(name), v1); }
void Shader::SetUniform2f(const std::string& name, GLfloat v1, GLfloat v2) { glUniform2f(GetUniformLocation(name), v1, v2); }
void Shader::SetUniform3f(const std::string& name, GLfloat v1, GLfloat v2, GLfloat v3) { glUniform3f(GetUniformLocation(name), v1, v2, v3); }
void Shader::SetUniform4f(const std::string& name, GLfloat v1, GLfloat v2, GLfloat v3, GLfloat v4) { glUniform4f(GetUniformLocation(name), v1, v2, v3, v4); }
void Shader::SetUniform1i(const std::string& name, GLint v1) { glUniform1i(GetUniformLocation(name), v1); }
void Shader::SetUniform2i(const std::string& name, GLint v1, GLint v2) { glUniform2i(GetUniformLocation(name), v1, v2); }
void Shader::SetUniform3i(const std::string& name, GLint v1, GLint v2, GLint v3) { glUniform3i(GetUniformLocation(name), v1, v2, v3); }
void Shader::SetUniform4i(const std::string& name, GLint v1, GLint v2, GLint v3, GLint v4) { glUniform4i(GetUniformLocation(name), v1, v2, v3, v4); }
void Shader::SetUniform1d(const std::string& name, GLdouble v1) { glUniform1d(GetUniformLocation(name), v1); }
void Shader::SetUniform2d(const std::string& name, GLdouble v1, GLdouble v2) { glUniform2d(GetUniformLocation(name), v1, v2); }
void Shader::SetUniform3d(const std::string& name, GLdouble v1, GLdouble v2, GLdouble v3) { glUniform3d(GetUniformLocation(name), v1, v2, v3); }
void Shader::SetUniform4d(const std::string& name, GLdouble v1, GLdouble v2, GLdouble v3, GLdouble v4) { glUniform4d(GetUniformLocation(name), v1, v2, v3, v4); }
void Shader::SetUniform1fv(const std::string& name, const glm::fvec1& v) { glUniform1fv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform2fv(const std::string& name, const glm::fvec2& v) { glUniform2fv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform3fv(const std::string& name, const glm::fvec3& v) { glUniform3fv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform4fv(const std::string& name, const glm::fvec4& v) { glUniform4fv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform1iv(const std::string& name, const glm::ivec1& v) { glUniform1iv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform2iv(const std::string& name, const glm::ivec2& v) { glUniform2iv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform3iv(const std::string& name, const glm::ivec3& v) { glUniform3iv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform4iv(const std::string& name, const glm::ivec4& v) { glUniform4iv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform1dv(const std::string& name, const glm::dvec1& v) { glUniform1dv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform2dv(const std::string& name, const glm::dvec2& v) { glUniform2dv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform3dv(const std::string& name, const glm::dvec3& v) { glUniform3dv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniform4dv(const std::string& name, const glm::dvec4& v) { glUniform4dv(GetUniformLocation(name), 1, &v[0]); }
void Shader::SetUniformMat2fv(const std::string& name, const glm::mat2& value, GLboolean transpose) { glUniformMatrix2fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMat3fv(const std::string& name, const glm::mat3& value, GLboolean transpose) { glUniformMatrix3fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMat4fv(const std::string& name, const glm::mat4& value, GLboolean transpose) { glUniformMatrix4fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMatrix2x3fv(const std::string& name, GLboolean transpose, const glm::mat2x3& value) { glUniformMatrix2x3fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMatrix2x4fv(const std::string& name, GLboolean transpose, const glm::mat2x4& value) { glUniformMatrix2x4fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMatrix3x2fv(const std::string& name, GLboolean transpose, const glm::mat3x2& value) { glUniformMatrix3x2fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMatrix3x4fv(const std::string& name, GLboolean transpose, const glm::mat3x4& value) { glUniformMatrix3x4fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMatrix4x2fv(const std::string& name, GLboolean transpose, const glm::mat4x2& value) { glUniformMatrix4x2fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
void Shader::SetUniformMatrix4x3fv(const std::string& name, GLboolean transpose, const glm::mat4x3& value) { glUniformMatrix4x3fv(GetUniformLocation(name), 1, transpose, &value[0][0]); }
}
I tried putting some documentation on my function declarations, because I thought that'd help me in later usage, and not need me to open the shader.cpp again in the future...
Any help, advice or suggestion is appreciated.