// =====================================================================================================================
// glw, an open-source library that wraps OpenGL structures into classes.
// Copyright (C) 2024 Medusa Slockbower
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program. If not, see .
// =====================================================================================================================
#ifndef GLW_SHADER_H
#define GLW_SHADER_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "common.h"
namespace ocu = open_cpp_utils;
namespace glw
{
// =====================================================================================================================
// Definitions
// =====================================================================================================================
// Typedefs ------------------------------------------------------------------------------------------------------------
// Enums ---------------------------------------------------------------------------------------------------------------
enum source_type
{
vertex = GL_VERTEX_SHADER
, tess_ctrl = GL_TESS_CONTROL_SHADER
, tess_eval = GL_TESS_EVALUATION_SHADER
, geometry = GL_GEOMETRY_SHADER
, fragment = GL_FRAGMENT_SHADER
, compute = GL_COMPUTE_SHADER
};
enum uniform_type
{
// bool ----------------------------------------------------------------------------------------------------------------
b = GL_BOOL
, bvec2 = GL_BOOL_VEC2
, bvec3 = GL_BOOL_VEC3
, bvec4 = GL_BOOL_VEC4
// int -----------------------------------------------------------------------------------------------------------------
, i = GL_INT
, ivec2 = GL_INT_VEC2
, ivec3 = GL_INT_VEC3
, ivec4 = GL_INT_VEC4
// unsigned int --------------------------------------------------------------------------------------------------------
, u = GL_UNSIGNED_INT
, uvec2 = GL_UNSIGNED_INT_VEC2
, uvec3 = GL_UNSIGNED_INT_VEC3
, uvec4 = GL_UNSIGNED_INT_VEC4
// float ---------------------------------------------------------------------------------------------------------------
, f = GL_FLOAT
, vec2 = GL_FLOAT_VEC2
, vec3 = GL_FLOAT_VEC3
, vec4 = GL_FLOAT_VEC4
, mat2 = GL_FLOAT_MAT2
, mat3 = GL_FLOAT_MAT3
, mat4 = GL_FLOAT_MAT4
, mat2x3 = GL_FLOAT_MAT2x3
, mat2x4 = GL_FLOAT_MAT2x4
, mat3x2 = GL_FLOAT_MAT3x2
, mat3x4 = GL_FLOAT_MAT3x4
, mat4x2 = GL_FLOAT_MAT4x2
, mat4x3 = GL_FLOAT_MAT4x3
// double --------------------------------------------------------------------------------------------------------------
, d = GL_DOUBLE
, dvec2 = GL_DOUBLE_VEC2
, dvec3 = GL_DOUBLE_VEC3
, dvec4 = GL_DOUBLE_VEC4
, dmat2 = GL_DOUBLE_MAT2
, dmat3 = GL_DOUBLE_MAT3
, dmat4 = GL_DOUBLE_MAT4
, dmat2x3 = GL_DOUBLE_MAT2x3
, dmat2x4 = GL_DOUBLE_MAT2x4
, dmat3x2 = GL_DOUBLE_MAT3x2
, dmat3x4 = GL_DOUBLE_MAT3x4
, dmat4x2 = GL_DOUBLE_MAT4x2
, dmat4x3 = GL_DOUBLE_MAT4x3
// sampler -------------------------------------------------------------------------------------------------------------
, sampler1D = GL_SAMPLER_1D
, sampler1DShadow = GL_SAMPLER_1D_SHADOW
, sampler1DArray = GL_SAMPLER_1D_ARRAY
, sampler1DArrayShadow = GL_SAMPLER_1D_ARRAY_SHADOW
, sampler2D = GL_SAMPLER_2D
, sampler2DShadow = GL_SAMPLER_2D_SHADOW
, sampler2DRect = GL_SAMPLER_2D_RECT
, sampler2DRectShadow = GL_SAMPLER_2D_RECT_SHADOW
, sampler2DArray = GL_SAMPLER_2D_ARRAY
, sampler2DArrayShadow = GL_SAMPLER_2D_ARRAY_SHADOW
, sampler2DMS = GL_SAMPLER_2D_MULTISAMPLE
, sampler2DMSArray = GL_SAMPLER_2D_MULTISAMPLE_ARRAY
, samplerCube = GL_SAMPLER_CUBE
, samplerCubeShadow = GL_SAMPLER_CUBE_SHADOW
, samplerCubeArray = GL_SAMPLER_CUBE_MAP_ARRAY
, sampler3D = GL_SAMPLER_3D
// isampler ------------------------------------------------------------------------------------------------------------
, isampler1D = GL_INT_SAMPLER_1D
, isampler1DArray = GL_INT_SAMPLER_1D_ARRAY
, isampler2D = GL_INT_SAMPLER_2D
, isampler2DRect = GL_INT_SAMPLER_2D_RECT
, isampler2DArray = GL_INT_SAMPLER_2D_ARRAY
, isampler2DMS = GL_INT_SAMPLER_2D_MULTISAMPLE
, isampler2DMSArray = GL_INT_SAMPLER_2D_MULTISAMPLE_ARRAY
, isamplerCube = GL_INT_SAMPLER_CUBE
, isamplerCubeArray = GL_INT_SAMPLER_CUBE_MAP_ARRAY
, isampler3D = GL_INT_SAMPLER_3D
// usampler ------------------------------------------------------------------------------------------------------------
, usampler1D = GL_UNSIGNED_INT_SAMPLER_1D
, usampler1DArray = GL_UNSIGNED_INT_SAMPLER_1D_ARRAY
, usampler2D = GL_UNSIGNED_INT_SAMPLER_2D
, usampler2DRect = GL_UNSIGNED_INT_SAMPLER_2D_RECT
, usampler2DArray = GL_UNSIGNED_INT_SAMPLER_2D_ARRAY
, usampler2DMS = GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE
, usampler2DMSArray = GL_UNSIGNED_INT_SAMPLER_2D_MULTISAMPLE_ARRAY
, usamplerCube = GL_UNSIGNED_INT_SAMPLER_CUBE
, usamplerCubeArray = GL_UNSIGNED_INT_SAMPLER_CUBE_MAP_ARRAY
, usampler3D = GL_UNSIGNED_INT_SAMPLER_3D
// image ---------------------------------------------------------------------------------------------------------------
, image1D = GL_IMAGE_1D
, image1DArray = GL_IMAGE_1D_ARRAY
, image2D = GL_IMAGE_2D
, image2DRect = GL_IMAGE_2D_RECT
, image2DArray = GL_IMAGE_2D_ARRAY
, image2DMS = GL_IMAGE_2D_MULTISAMPLE
, image2DMSArray = GL_IMAGE_2D_MULTISAMPLE_ARRAY
, imageCube = GL_IMAGE_CUBE
, imageCubeArray = GL_IMAGE_CUBE_MAP_ARRAY
, image3D = GL_IMAGE_3D
// iimage --------------------------------------------------------------------------------------------------------------
, iimage1D = GL_INT_IMAGE_1D
, iimage1DArray = GL_INT_IMAGE_1D_ARRAY
, iimage2D = GL_INT_IMAGE_2D
, iimage2DRect = GL_INT_IMAGE_2D_RECT
, iimage2DArray = GL_INT_IMAGE_2D_ARRAY
, iimage2DMS = GL_INT_IMAGE_2D_MULTISAMPLE
, iimage2DMSArray = GL_INT_IMAGE_2D_MULTISAMPLE_ARRAY
, iimageCube = GL_INT_IMAGE_CUBE
, iimageCubeArray = GL_INT_IMAGE_CUBE_MAP_ARRAY
, iimage3D = GL_INT_IMAGE_3D
// uimage --------------------------------------------------------------------------------------------------------------
, uimage1D = GL_UNSIGNED_INT_IMAGE_1D
, uimage1DArray = GL_UNSIGNED_INT_IMAGE_1D_ARRAY
, uimage2D = GL_UNSIGNED_INT_IMAGE_2D
, uimage2DRect = GL_UNSIGNED_INT_IMAGE_2D_RECT
, uimage2DArray = GL_UNSIGNED_INT_IMAGE_2D_ARRAY
, uimage2DMS = GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE
, uimage2DMSArray = GL_UNSIGNED_INT_IMAGE_2D_MULTISAMPLE_ARRAY
, uimageCube = GL_UNSIGNED_INT_IMAGE_CUBE
, uimageCubeArray = GL_UNSIGNED_INT_IMAGE_CUBE_MAP_ARRAY
, uimage3D = GL_UNSIGNED_INT_IMAGE_3D
// atomic --------------------------------------------------------------------------------------------------------------
, atomic_uint = GL_UNSIGNED_INT_ATOMIC_COUNTER
};
// Buffer Definition ---------------------------------------------------------------------------------------------------
class shader
{
// Typedefs ============================================================================================================
public:
struct uniform
{
public:
uniform() : shader_(0), name_(""), type_(int8), location_(-1) { }
uniform(const std::string& str) : shader_(0), name_(str), type_(int8), location_(-1) { }
uniform(shader& shader, const std::string& name, enum_t type);
uniform(const uniform& u) : shader_(u.shader_), name_(u.name_), type_(u.type_), location_(u.location_) { }
~uniform() = default;
template
inline uniform& operator=(T v) { assert(false); return *this; }
const std::string& name() const { return name_; }
enum_t type() const { return type_; }
index_t location() const { return location_; }
private:
handle_t shader_;
std::string name_;
enum_t type_;
index_t location_;
};
// OpenGL Built Ins
static constexpr char gl_NumWorkGroups[] = "gl_NumWorkGroups";
static constexpr char gl_WorkGroupID[] = "gl_WorkGroupID";
static constexpr char gl_LocalInvocationID[] = "gl_LocalInvocationID";
static constexpr char gl_GlobalInvocationID[] = "gl_GlobalInvocationID";
static constexpr char gl_LocalInvocationIndex[] = "gl_LocalInvocationIndex";
// GLW Built Ins
static constexpr char glw_RequestedInvocations[] = "glw_RequestedInvocations";
static inline const std::string built_ins_compute = std::format("uniform ivec3 {};", glw_RequestedInvocations);
private:
using uniform_map = std::unordered_map;
// Functions ===========================================================================================================
public:
shader();
~shader();
bool attach_source(enum_t type, size_t count, const char** source, const size_t* lengths);
bool attach_source(enum_t type, const std::vector& source);
bool link();
void bind() const;
void dispatch(int x = 1, int y = 1, int z = 1);
uniform operator[](const std::string& str);
const std::string& get_error_string() const { return error_; }
inline static std::string group_size(int x, int y, int z)
{ return std::format("layout (local_size_x = {}, local_size_y = {}, local_size_z = {}) in;", x, y, z); }
private:
handle_t handle_;
std::string error_;
int work_group_size_[3];
bool pass_requested_invocations_;
uniform_map uniforms_;
};
// =====================================================================================================================
// Implementation
// =====================================================================================================================
// uniform -------------------------------------------------------------------------------------------------------------
inline shader::uniform::uniform(shader& shader, const std::string& name, enum_t type)
: shader_(shader.handle_)
, name_(name)
, type_(type)
, location_(0)
{
location_ = glGetUniformLocation(shader_, name_.c_str());
}
#define validate(handle, type) if(handle == 0) return *this; assert(type_ == type);
template<> inline shader::uniform& shader::uniform::operator=(bool v) { validate(shader_, uniform_type::b); glProgramUniform1i(shader_, location_, v); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::bvec2 v) { validate(shader_, uniform_type::bvec2); glProgramUniform2i(shader_, location_, v.x, v.y); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::bvec3 v) { validate(shader_, uniform_type::bvec3); glProgramUniform3i(shader_, location_, v.x, v.y, v.z); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::bvec4 v) { validate(shader_, uniform_type::bvec4); glProgramUniform4i(shader_, location_, v.x, v.y, v.z, v.w); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(int v) { validate(shader_, uniform_type::i); glProgramUniform1i(shader_, location_, v); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::ivec2 v) { validate(shader_, uniform_type::ivec2); glProgramUniform2i(shader_, location_, v.x, v.y); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::ivec3 v) { validate(shader_, uniform_type::ivec3); glProgramUniform3i(shader_, location_, v.x, v.y, v.z); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::ivec4 v) { validate(shader_, uniform_type::ivec4); glProgramUniform4i(shader_, location_, v.x, v.y, v.z, v.w); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::uint v) { validate(shader_, uniform_type::u); glProgramUniform1ui(shader_, location_, v); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::uvec2 v) { validate(shader_, uniform_type::uvec2); glProgramUniform2ui(shader_, location_, v.x, v.y); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::uvec3 v) { validate(shader_, uniform_type::uvec3); glProgramUniform3ui(shader_, location_, v.x, v.y, v.z); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::uvec4 v) { validate(shader_, uniform_type::uvec4); glProgramUniform4ui(shader_, location_, v.x, v.y, v.z, v.w); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(float v) { validate(shader_, uniform_type::f); glProgramUniform1f(shader_, location_, v); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::vec2 v) { validate(shader_, uniform_type::vec2); glProgramUniform2f(shader_, location_, v.x, v.y); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::vec3 v) { validate(shader_, uniform_type::vec3); glProgramUniform3f(shader_, location_, v.x, v.y, v.z); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::vec4 v) { validate(shader_, uniform_type::vec4); glProgramUniform4f(shader_, location_, v.x, v.y, v.z, v.w); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(double v) { validate(shader_, uniform_type::d); glProgramUniform1d(shader_, location_, v); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dvec2 v) { validate(shader_, uniform_type::dvec2); glProgramUniform2d(shader_, location_, v.x, v.y); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dvec3 v) { validate(shader_, uniform_type::dvec3); glProgramUniform3d(shader_, location_, v.x, v.y, v.z); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dvec4 v) { validate(shader_, uniform_type::dvec4); glProgramUniform4d(shader_, location_, v.x, v.y, v.z, v.w); return *this; }
template<> inline shader::uniform& shader::uniform::operator= (glm::mat2 v) { validate(shader_, uniform_type::mat2); glProgramUniformMatrix2fv (shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::mat2x3 v) { validate(shader_, uniform_type::mat2x3); glProgramUniformMatrix2x3fv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::mat2x4 v) { validate(shader_, uniform_type::mat2x4); glProgramUniformMatrix2x4fv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::mat3x2 v) { validate(shader_, uniform_type::mat3x2); glProgramUniformMatrix3x2fv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator= (glm::mat3 v) { validate(shader_, uniform_type::mat3); glProgramUniformMatrix3fv (shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::mat3x4 v) { validate(shader_, uniform_type::mat3x4); glProgramUniformMatrix3x4fv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::mat4x2 v) { validate(shader_, uniform_type::mat4x2); glProgramUniformMatrix4x2fv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::mat4x3 v) { validate(shader_, uniform_type::mat4x3); glProgramUniformMatrix4x3fv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator= (glm::mat4 v) { validate(shader_, uniform_type::mat4); glProgramUniformMatrix4fv (shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator= (glm::dmat2 v) { validate(shader_, uniform_type::dmat2); glProgramUniformMatrix2dv (shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dmat2x3 v) { validate(shader_, uniform_type::dmat2x3); glProgramUniformMatrix2x3dv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dmat2x4 v) { validate(shader_, uniform_type::dmat2x4); glProgramUniformMatrix2x4dv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dmat3x2 v) { validate(shader_, uniform_type::dmat3x2); glProgramUniformMatrix3x2dv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator= (glm::dmat3 v) { validate(shader_, uniform_type::dmat3); glProgramUniformMatrix3dv (shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dmat3x4 v) { validate(shader_, uniform_type::dmat3x4); glProgramUniformMatrix3x4dv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dmat4x2 v) { validate(shader_, uniform_type::dmat4x2); glProgramUniformMatrix4x2dv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator=(glm::dmat4x3 v) { validate(shader_, uniform_type::dmat4x3); glProgramUniformMatrix4x3dv(shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
template<> inline shader::uniform& shader::uniform::operator= (glm::dmat4 v) { validate(shader_, uniform_type::dmat4); glProgramUniformMatrix4dv (shader_, location_, 1, GL_FALSE, &v[0][0]); return *this; }
// shader --------------------------------------------------------------------------------------------------------------
inline shader::shader()
: handle_(glCreateProgram())
, work_group_size_{ 1 }
, pass_requested_invocations_(false)
{
}
inline shader::~shader()
{
glDeleteProgram(handle_);
}
inline bool shader::attach_source(enum_t type, size_t count, const char** source, const size_t* lengths)
{
handle_t shader = glCreateShader(type);
glShaderSource(shader, count, source, lengths);
glCompileShader(shader);
int result; glGetShaderiv(shader, GL_COMPILE_STATUS, &result);
if(result) glAttachShader(handle_, shader);
else
{
int res; glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &res);
error_.resize(res);
glGetShaderInfoLog(shader, res, &res, error_.data());
}
glDeleteShader(shader);
return result;
}
inline bool shader::attach_source(enum_t type, const std::vector& source)
{
std::vector src; src.reserve(source.size());
std::vector len; len.reserve(source.size());
for(const std::string& str : source)
{
src.push_back(str.c_str());
len.push_back(static_cast(str.length()));
}
return attach_source(type, static_cast(source.size()), src.data(), len.data());
}
inline bool shader::link()
{
glLinkProgram(handle_);
int result; glGetProgramiv(handle_, GL_LINK_STATUS, &result);
if(!result)
{
int res; glGetProgramiv(handle_, GL_INFO_LOG_LENGTH, &res);
error_.resize(res);
glGetProgramInfoLog(handle_, res, &res, error_.data());
return false;
}
glGetProgramiv(handle_, GL_ACTIVE_UNIFORM_MAX_LENGTH, &result);
std::string buffer(result + 1, '\0');
size_t max_length = static_cast(buffer.capacity());
char* str = buffer.data();
glGetProgramiv(handle_, GL_ACTIVE_UNIFORMS, &result);
for(int i = 0; i < result; ++i)
{
enum_t type; size_t length, size;
glGetActiveUniform(handle_, i, max_length, &length, &size, &type, str);
std::string res = buffer.substr(0, length).c_str();
uniforms_[res] = uniform(*this, res, type);
const bool len = length == sizeof(glw_RequestedInvocations) - 1;
const bool cmp = (strncmp(buffer.c_str(), glw_RequestedInvocations, length) == 0);
pass_requested_invocations_ |= (len && cmp);
}
glGetProgramiv(handle_, GL_COMPUTE_WORK_GROUP_SIZE, work_group_size_);
return true;
}
inline void shader::bind() const
{
glUseProgram(handle_);
}
inline void shader::dispatch(int x, int y, int z)
{
int num_groups_x = ceil_div(x, work_group_size_[0]);
int num_groups_y = ceil_div(y, work_group_size_[1]);
int num_groups_z = ceil_div(z, work_group_size_[2]);
bind();
if(pass_requested_invocations_)
this->operator[](glw_RequestedInvocations) = glm::ivec3(x, y, z);
glDispatchCompute(num_groups_x, num_groups_y, num_groups_z);
}
inline shader::uniform shader::operator[](const std::string &str)
{
auto it = uniforms_.find(str);
if(it != uniforms_.end())
return it->second;
return uniform();
}
}
#endif //SHADER_H