From c4097f1c9b32932a0eeb2419a18dd6f97d02631f Mon Sep 17 00:00:00 2001
From: Brett Laptop <tri11paragon@tpgc.me>
Date: Sun, 17 Dec 2023 20:39:01 -0500
Subject: [PATCH] basic shaders

---
 include/blt/gfx/shader.h | 144 +++++++++++++++++++++++++++++
 src/blt/gfx/shader.cpp   | 190 +++++++++++++++++++++++++++++++++++++++
 2 files changed, 334 insertions(+)
 create mode 100644 include/blt/gfx/shader.h
 create mode 100644 src/blt/gfx/shader.cpp

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 @@
+/*
+ *  <Short Description>
+ *  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 <https://www.gnu.org/licenses/>.
+ */
+
+#ifndef BLT_WITH_GRAPHICS_SHADER_H
+#define BLT_WITH_GRAPHICS_SHADER_H
+
+#include <blt/gfx/gl_includes.h>
+#include <vector>
+#include <unordered_map>
+#include <string>
+#include <blt/math/math.h>
+
+namespace blt::gfx
+{
+    class shader_base {
+        protected:
+            struct IntDefaultedToMinusOne {
+                GLint i = -1;
+            };
+            std::unordered_map<std::string, IntDefaultedToMinusOne> 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 @@
+/*
+ *  <Short Description>
+ *  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 <https://www.gnu.org/licenses/>.
+ */
+#include <blt/gfx/shader.h>
+#include <blt/std/loader.h>
+#include <blt/std/string.h>
+#include <blt/std/memory.h>
+
+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<GLchar> infoLog{static_cast<unsigned long>(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<GLchar> infoLog{static_cast<unsigned long>(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;
+    }
+}