// ===================================================================================================================== // 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