diff --git a/include/blt/gfx/shader.h b/include/blt/gfx/shader.h new file mode 100644 index 0000000..a84591d --- /dev/null +++ b/include/blt/gfx/shader.h @@ -0,0 +1,144 @@ +/* + * + * Copyright (C) 2023 Brett Terpstra + * + * 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 BLT_WITH_GRAPHICS_SHADER_H +#define BLT_WITH_GRAPHICS_SHADER_H + +#include +#include +#include +#include +#include + +namespace blt::gfx +{ + class shader_base { + protected: + struct IntDefaultedToMinusOne { + GLint i = -1; + }; + std::unordered_map uniformVars; + GLuint programID = 0; + + inline GLint getUniformLocation(const std::string &name) { + if (uniformVars[name].i != -1) + return uniformVars[name].i; + int loc = glGetUniformLocation(programID, name.c_str()); + uniformVars[name].i = loc; + return loc; + } + public: + inline void bind() const { + glUseProgram(programID); + } + + inline void setBool(const std::string &name, bool value) { + glUniform1i(getUniformLocation(name), (int) value); + } + + inline void setInt(const std::string &name, int value) { + glUniform1i(getUniformLocation(name), value); + } + + inline void setFloat(const std::string &name, float value) { + glUniform1f(getUniformLocation(name), value); + } + + inline void setMatrix(const std::string &name, blt::mat4x4 &matrix) { + glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, matrix.ptr()); + } + + inline void setVec3(const std::string &name, const blt::vec3 &vec) { + glUniform3f(getUniformLocation(name), vec.x(), vec.y(), vec.z()); + } + + inline void setVec4(const std::string &name, const blt::vec4 &vec) { + glUniform4f(getUniformLocation(name), vec.x(), vec.y(), vec.z(), vec.w()); + } + + inline void setVec2(const std::string &name, float x, float y) { + glUniform2f(getUniformLocation(name), x, y); + } + + inline void setVec3(const std::string &name, float x, float y, float z) { + glUniform3f(getUniformLocation(name), x, y, z); + } + + inline void setVec4(const std::string &name, float x, float y, float z, float w) { + glUniform4f(getUniformLocation(name), x, y, z, w); + } + }; + + /** + * a basic computer shader class, contains the functions and resources required to use compute shaders! + */ + class compute_shader : public shader_base { + private: + GLuint shaderID = 0; + public: + explicit compute_shader(const std::string& shader_source, bool loadAsString = true); + + inline void execute(int x, int y, int z) const { + bind(); + glDispatchCompute(x, y, z); + } + + ~compute_shader(); + }; + + class shader : public shader_base { + private: + GLuint vertexShaderID = 0; + GLuint fragmentShaderID = 0; + // while these will remain unused. (Webgl2 apparently doesn't support them despite being based on GL4.3? that's a TODO!) + GLuint geometryShaderID = 0; + // this would be very useful however it is highly unlikely webgl will support it + // im leaving some of this stuff in here because I might expand the native application to use some of it. + // im trying to keep the web and native versions the same though + GLuint tessellationShaderID = 0; + + static unsigned int createShader(const std::string& source, int type); + public: + /** + * Creates a shader + * @param vertex vertex shader source or file + * @param fragment fragment shader source or file + * @param geometry geometry shader source or file (optional) + * @param load_as_string load the shader as a string (true) or use the string to load the shader as a file (false) + */ + shader(const std::string &vertex, const std::string &fragment, const std::string &geometry = "", bool load_as_string = true); + + shader(shader&& move) noexcept; + + // used to set the location of VAOs to the in variables in opengl shaders. + void bindAttribute(int attribute, const std::string &name) const; + + // used to set location of shared UBOs like the perspective and view matrix + void setUniformBlockLocation(const std::string &name, int location) const; + + ~shader(); + + static void updateProjectionMatrix(const blt::mat4x4& projectionMatrix); + static void updateOrthographicMatrix(const blt::mat4x4& orthoMatrix); + static void updateViewMatrix(const blt::mat4x4& viewMatrix); + // returns the perspective view matrix which is calculated per frame. (This is for optimization) + static const blt::mat4x4& getPVM(); + }; +} + +#endif //BLT_WITH_GRAPHICS_SHADER_H diff --git a/src/blt/gfx/shader.cpp b/src/blt/gfx/shader.cpp new file mode 100644 index 0000000..b7b6c98 --- /dev/null +++ b/src/blt/gfx/shader.cpp @@ -0,0 +1,190 @@ +/* + * + * Copyright (C) 2023 Brett Terpstra + * + * 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 . + */ +#include +#include +#include +#include + +namespace blt::gfx +{ + static std::string removeEmptyFirstLines(const std::string& string){ + auto lines = blt::string::split(string, "\n"); + std::string new_source_string; + for (const auto& line : lines) { + if (!line.empty() && !blt::string::contains(line, "\"")) { + new_source_string += line; + new_source_string += "\n"; + } + } + return new_source_string; + } + + unsigned int shader::createShader(const std::string& source, int type) { + const char* shader_code = source.c_str(); + // creates a Shader + unsigned int shaderID = glCreateShader(type); + // loads the shader code for later complication and uploading into the graphics card + // TODO: defines can be added here by sending them as additional strings. No need to edit the source string + glShaderSource(shaderID, 1, &shader_code, nullptr); + // Compile it + glCompileShader(shaderID); + + // make sure there are no errors in the compilation. If there is then print out information pertaining to the error. + // the actual log is highly dependent on the platform this is being run from, so we cannot make any assumptions about the issue. + // the TODO: maybe find a way of lexing the output to give suggestions about fixing the error? default error messages can be unhelpful at times. + GLint success; + glGetShaderiv(shaderID, GL_COMPILE_STATUS, &success); + if (!success) { + int log_length = 0; + glGetShaderiv(shaderID, GL_INFO_LOG_LENGTH, &log_length); + + // scoped buffers will delete their memory when they go out of scope. A benefit of using BLT + blt::scoped_buffer infoLog{static_cast(log_length + 1)}; + + glGetShaderInfoLog(shaderID, log_length + 1, nullptr, infoLog.data()); + auto shader_type_str = (type == GL_VERTEX_SHADER ? "Vertex Shader" : type == GL_FRAGMENT_SHADER ? "Fragment Shader" : "Other Shader"); + BLT_ERROR("--- --- --- --- --- --- --- --- ---"); + BLT_ERROR("Unable to compile shader of type %s\nShader source:", shader_type_str); + BLT_ERROR(source); + BLT_ERROR("I have an log of %d length", log_length); + BLT_ERROR(infoLog.data()); + BLT_ERROR("--- --- --- --- --- --- --- --- ---"); + } + return shaderID; + } + + shader::shader(const std::string& vertex, const std::string& fragment, const std::string& geometry, bool load_as_string) { +// load shader sources + bool load_geometry = !geometry.empty(); + std::string vertex_source = vertex; + std::string fragment_source = fragment; + std::string geometry_source = geometry; + if (!load_as_string){ + // BLT provides a recursive file loader for glsl shaders. It's pretty much just a recursive function looking for include statements. + vertex_source = blt::fs::loadShaderFile(vertex); + fragment_source = blt::fs::loadShaderFile(fragment); + if (load_geometry) + geometry_source = blt::fs::loadShaderFile(geometry); + } else { + vertex_source = removeEmptyFirstLines(vertex_source); + fragment_source = removeEmptyFirstLines(fragment_source); + geometry_source = removeEmptyFirstLines(geometry_source); + } + + // create the shaders + vertexShaderID = createShader(vertex_source, GL_VERTEX_SHADER); + fragmentShaderID = createShader(fragment_source, GL_FRAGMENT_SHADER); + if (load_geometry) + geometryShaderID = createShader(geometry_source, GL_GEOMETRY_SHADER); + + // bind them to a program + programID = glCreateProgram(); + // attach the loaded shaders to the Shader program + glAttachShader(programID, vertexShaderID); + glAttachShader(programID, fragmentShaderID); + if (load_geometry) + glAttachShader(programID, geometryShaderID); + // link and make sure that our program is valid. + glLinkProgram(programID); + + GLint success; + glGetProgramiv(programID, GL_LINK_STATUS, &success); + if (!success) { + int log_length = 0; + glGetProgramiv(programID, GL_INFO_LOG_LENGTH, &log_length); + + // scoped buffers will delete their memory when they go out of scope. + blt::scoped_buffer infoLog{static_cast(log_length + 1)}; + + glGetProgramInfoLog(programID, log_length + 1, nullptr, infoLog.data()); + BLT_ERROR("--- --- --- --- --- --- --- --- ---"); + BLT_ERROR("Unable to link program of ID: %d", programID); + BLT_ERROR(vertex_source); + BLT_ERROR(fragment_source); + BLT_ERROR(geometry_source); + BLT_ERROR("I have an log of %d length", log_length); + BLT_ERROR(infoLog.data()); + BLT_ERROR("--- --- --- --- --- --- --- --- ---"); + } + + glValidateProgram(programID); + bind(); + setUniformBlockLocation("StandardMatrices", 0); + glUseProgram(0); + } + + void shader::bindAttribute(int attribute, const std::string &name) const { + bind(); + glBindAttribLocation(programID, attribute, name.c_str()); + } + + void shader::setUniformBlockLocation(const std::string &name, int location) const { + bind(); + glUniformBlockBinding(programID, glGetUniformBlockIndex(programID, name.c_str()), location); + } + + shader::~shader() { + glUseProgram(0); + // shader was moved + if (programID <= 0) + return; + // remove all the shaders from the program + glDetachShader(programID, vertexShaderID); + if (geometryShaderID) + glDetachShader(programID, geometryShaderID); + if (tessellationShaderID) + glDetachShader(programID, tessellationShaderID); + glDetachShader(programID, fragmentShaderID); + + // delete the shaders + glDeleteShader(vertexShaderID); + if (geometryShaderID) + glDeleteShader(geometryShaderID); + if (tessellationShaderID) + glDeleteShader(tessellationShaderID); + glDeleteShader(fragmentShaderID); + + // delete the Shader program + glDeleteProgram(programID); + } + + void shader::updateProjectionMatrix(const blt::mat4x4& projectionMatrix) { + + } + + void shader::updateViewMatrix(const blt::mat4x4& viewMatrix) { + + } + + void shader::updateOrthographicMatrix(const blt::mat4x4& orthoMatrix) { + + } + + shader::shader(shader&& move) noexcept { + // the move constructor doesn't need to construct a new shader but it does need to ensure all old variables are moved over + programID = move.programID; + vertexShaderID = move.vertexShaderID; + fragmentShaderID = move.fragmentShaderID; + geometryShaderID = move.geometryShaderID; + tessellationShaderID = move.tessellationShaderID; + for (const auto& pair : move.uniformVars) + uniformVars.insert(pair); + // by setting the program ID to -1 we tell the shader it has been moved. + move.programID = -1; + } +}