Program Listing for File shader.hpp

Return to documentation for file (/home/runner/work/Legion-Engine/Legion-Engine/legion/engine/rendering/data/shader.hpp)

#pragma once
#include <vector>
#include <string>
#include <rendering/data/texture.hpp>
#include <rendering/util/bindings.hpp>
#include <rendering/util/settings.hpp>

namespace legion::rendering
{
    struct camera;
    struct shader;
    struct ShaderCache;
    struct shader_handle;

    using shader_ilo = std::unordered_map<std::string, std::vector<std::pair<GLuint, std::string>>>; // Shader intermediate language object.
    using shader_state = std::unordered_map<GLenum, GLenum>;

#pragma region shader parameters

    struct shader_parameter_base
    {
    protected:
        id_type m_shaderId;
        std::string m_name;
        GLenum m_type;
        GLint m_location;

        shader_parameter_base(std::nullptr_t t) : m_shaderId(invalid_id), m_name(""), m_type(0), m_location(-1) {};

        shader_parameter_base(id_type shaderId, std::string_view name, GLenum type, GLint location) : m_shaderId(shaderId), m_name(name), m_type(type), m_location(location) {};

    public:
        virtual bool is_valid() const
        {
            return m_location != -1;
        }

        virtual GLenum get_type() const { return m_type; }
        virtual std::string get_name() const { return m_name; }
        virtual GLint get_location() const { return m_location; }

        bool operator==(const shader_parameter_base& other)
        {
            return m_shaderId == other.m_shaderId && m_name == other.m_name && m_type == other.m_type && m_location == other.m_location;
        }

        bool operator!=(const shader_parameter_base& other)
        {
            return m_shaderId != other.m_shaderId || m_name != other.m_name || m_type != other.m_type || m_location != other.m_location;
        }
    };

    template<typename T>
    struct uniform : public shader_parameter_base
    {
    public:
        uniform(id_type shaderId, std::string_view name, GLenum type, GLint location) : shader_parameter_base(shaderId, name, type, location) {}
        uniform(std::nullptr_t t) : shader_parameter_base(t) {};
        void set_value(const T& value);
    };

    template<typename T>
    inline void uniform<T>::set_value(const T& value)
    {
    }

    template<>
    struct uniform<texture_handle> : public shader_parameter_base
    {
        uint m_textureUnit;
    public:
        uniform(id_type shaderId, std::string_view name, GLenum type, GLint location, uint textureUnit) : shader_parameter_base(shaderId, name, type, location), m_textureUnit(textureUnit) {}
        uniform(std::nullptr_t t) : shader_parameter_base(t) {};
        void set_value(const texture_handle& value)
        {
            texture tex;
            if (is_valid())
                tex = value.get_texture();
            else
                tex = invalid_texture_handle.get_texture();

            glActiveTexture(GL_TEXTURE0 + m_textureUnit);
            glBindTexture(GL_TEXTURE_2D, tex.textureId);
            glUniform1i(m_location, m_textureUnit);
            glActiveTexture(GL_TEXTURE0);
        }
    };

    template<>
    inline void uniform<uint>::set_value(const uint& value)
    {
        if (is_valid())
            glUniform1ui(m_location, value);
    }

    template<>
    inline void uniform<float>::set_value(const float& value)
    {
        if (is_valid())
            glUniform1f(m_location, value);
    }

    template<>
    inline void uniform<math::vec2>::set_value(const math::vec2& value)
    {
        if (is_valid())
            glUniform2fv(m_location, 1, math::value_ptr(value));
    }

    template<>
    inline void uniform<math::vec3>::set_value(const math::vec3& value)
    {
        if (is_valid())
            glUniform3fv(m_location, 1, math::value_ptr(value));
    }

    template<>
    inline void uniform<math::vec4>::set_value(const math::vec4& value)
    {
        if (is_valid())
            glUniform4fv(m_location, 1, math::value_ptr(value));
    }

    template<>
    inline void uniform<int>::set_value(const int& value)
    {
        if (is_valid())
            glUniform1i(m_location, value);
    }

    template<>
    inline void uniform<math::ivec2>::set_value(const math::ivec2& value)
    {
        if (is_valid())
            glUniform2iv(m_location, 1, math::value_ptr(value));
    }

    template<>
    inline void uniform<math::ivec3>::set_value(const math::ivec3& value)
    {
        if (is_valid())
            glUniform3iv(m_location, 1, math::value_ptr(value));
    }

    template<>
    inline void uniform<math::ivec4>::set_value(const math::ivec4& value)
    {
        if (is_valid())
            glUniform4iv(m_location, 1, math::value_ptr(value));
    }

    template<>
    inline void uniform<bool>::set_value(const bool& value)
    {
        if (is_valid())
            glUniform1i(m_location, value);
    }

    template<>
    inline void uniform<math::bvec2>::set_value(const math::bvec2& value)
    {
        if (is_valid())
            glUniform2iv(m_location, 1, math::value_ptr(math::ivec2(value)));
    }

    template<>
    inline void uniform<math::bvec3>::set_value(const math::bvec3& value)
    {
        if (is_valid())
            glUniform3iv(m_location, 1, math::value_ptr(math::ivec3(value)));
    }

    template<>
    inline void uniform<math::bvec4>::set_value(const math::bvec4& value)
    {
        if (is_valid())
            glUniform4iv(m_location, 1, math::value_ptr(math::ivec4(value)));
    }

    template<>
    inline void uniform<math::mat2>::set_value(const math::mat2& value)
    {
        if (is_valid())
            glUniformMatrix2fv(m_location, 1, false, math::value_ptr(value));
    }

    template<>
    inline void uniform<math::mat3>::set_value(const math::mat3& value)
    {
        if (is_valid())
            glUniformMatrix3fv(m_location, 1, false, math::value_ptr(value));
    }

    template<>
    inline void uniform<math::mat4>::set_value(const math::mat4& value)
    {
        if (is_valid())
            glUniformMatrix4fv(m_location, 1, false, math::value_ptr(value));
    }

    struct attribute : public shader_parameter_base
    {
    public:
        attribute(id_type shaderId, std::string_view name, GLenum type, GLint location) : shader_parameter_base(shaderId, name, type, location) {}

        void set_attribute_pointer(GLint size, GLenum type, GLboolean normalized, GLsizei stride, GLsizei pointer)
        {
            glEnableVertexAttribArray(m_location);
            glVertexAttribPointer(m_location, size, type, normalized, stride, reinterpret_cast<const GLvoid*>(pointer));
        }

        void disable_attribute_pointer()
        {
            glDisableVertexAttribArray(m_location);
        }

        void set_divisor(uint offset, uint divisor)
        {
            glVertexAttribDivisor(m_location + offset, divisor);
        }
    };

    const attribute invalid_attribute(0, std::string_view(), 0, 0);

#pragma endregion

    struct shader_variant
    {
        GLint programId;
        std::unordered_map<id_type, std::unique_ptr<shader_parameter_base>> uniforms;
        std::unordered_map<id_type, std::unique_ptr<attribute>> attributes;
        std::unordered_map<GLint, id_type> idOfLocation;
        std::string name;
        std::string path;
        id_type nameHash;

        shader_state state;

        std::vector<std::tuple<std::string, GLint, GLenum>> get_uniform_info();
    };

    struct shader
    {
        friend class ShaderCache;
        friend struct shader_handle;
    private:
        mutable shader_variant* m_currentShaderVariant;
        mutable std::unordered_map<id_type, shader_variant> m_variants;
    public:
        std::string name;
        std::string path;

        // Since copying would mean that the in-vram version of the actual shader would also need to be copied, we don't allow copying.
        shader(const shader&) = delete;
        // Moving should be fine though.
        shader(shader&&) = default;
        shader() = default;

        shader& operator=(shader&&) = default;
        shader& operator=(const shader&) = delete;

        bool has_variant(id_type variantId) const;
        bool has_variant(const std::string& variant) const;
        void configure_variant(id_type variantId) const;
        void configure_variant(const std::string& variant) const;

        shader_variant& get_variant(id_type variantId);
        shader_variant& get_variant(const std::string& variant);
        const shader_variant& get_variant(id_type variantId) const;
        const shader_variant& get_variant(const std::string& variant) const;

        void bind();
        static void release();

        GLuint get_uniform_block_index(const std::string& name) const;
        void bind_uniform_block(GLuint uniformBlockIndex, GLuint uniformBlockBinding) const;

        template<typename T>
        uniform<T> get_uniform(const std::string& name)
        {
            OPTICK_EVENT();
            if (!m_currentShaderVariant)
            {
                log::error("No current shader variant configured for shader {}", name);
                return uniform<T>(nullptr);
            }

            auto* ptr = dynamic_cast<uniform<T>*>(m_currentShaderVariant->uniforms[nameHash(name)].get());
            if (ptr)
                return *ptr;
            log::error("Uniform of type {} does not exist with name {}.", nameOfType<T>(), name);
            return uniform<T>(nullptr);
        }

        template<typename T>
        bool has_uniform(const std::string& name)
        {
            OPTICK_EVENT();
            if (!m_currentShaderVariant)
            {
                log::error("No current shader variant configured for shader {}", name);
                return false;
            }

            auto id = nameHash(name);
            return m_currentShaderVariant->uniforms.count(id) && dynamic_cast<uniform<T>*>(m_currentShaderVariant->uniforms[id].get()) != nullptr;
        }

        template<typename T>
        uniform<T> get_uniform(id_type id)
        {
            OPTICK_EVENT();
            if (!m_currentShaderVariant)
            {
                log::error("No current shader variant configured for shader {}", name);
                return uniform<T>(nullptr);
            }

            auto* ptr = dynamic_cast<uniform<T>*>(m_currentShaderVariant->uniforms[id].get());
            if (ptr)
                return *ptr;
            log::error("Uniform of type {} does not exist with id {}.", nameOfType<T>(), id);
            return uniform<T>(nullptr);
        }

        template<typename T>
        bool has_uniform(id_type id)
        {
            OPTICK_EVENT();
            if (!m_currentShaderVariant)
            {
                log::error("No current shader variant configured for shader {}", name);
                return false;
            }

            return m_currentShaderVariant->uniforms.count(id) && dynamic_cast<uniform<T>*>(m_currentShaderVariant->uniforms[id].get()) != nullptr;
        }

        template<typename T>
        uniform<T> get_uniform_with_location(GLint location)
        {
            OPTICK_EVENT();
            if (!m_currentShaderVariant)
            {
                log::error("No current shader variant configured for shader {}", name);
                return uniform<T>(nullptr);
            }

            auto* ptr = dynamic_cast<uniform<T>*>(m_currentShaderVariant->uniforms[m_currentShaderVariant->idOfLocation[location]].get());
            if (ptr)
                return *ptr;
            log::error("Uniform of type {} does not exist with location {}.", nameOfType<T>(), location);
            return uniform<T>(nullptr);
        }

        template<typename T>
        bool has_uniform_with_location(GLint location)
        {
            OPTICK_EVENT();
            if (!m_currentShaderVariant)
            {
                log::error("No current shader variant configured for shader {}", name);
                return false;
            }

            return m_currentShaderVariant->uniforms.count(m_currentShaderVariant->idOfLocation[location]) && dynamic_cast<uniform<T>*>(m_currentShaderVariant->uniforms[m_currentShaderVariant->idOfLocation[location]].get()) != nullptr;
        }

        attribute get_attribute(const std::string& name);

        attribute get_attribute(id_type id);

    };

    struct shader_handle
    {
        using cache = ShaderCache;
        id_type id;

        bool has_variant(id_type variantId) const;
        bool has_variant(const std::string& variant) const;
        void configure_variant(id_type variantId);
        void configure_variant(const std::string& variant);

        shader_variant& get_variant(id_type variantId);
        shader_variant& get_variant(const std::string& variant);
        const shader_variant& get_variant(id_type variantId) const;
        const shader_variant& get_variant(const std::string& variant) const;

        GLuint get_uniform_block_index(const std::string& name) const;
        void bind_uniform_block(GLuint uniformBlockIndex, GLuint uniformBlockBinding) const;

        std::string get_name() const;
        std::string get_path() const;

        std::unordered_map<id_type, std::vector<std::tuple<std::string, GLint, GLenum>>> get_uniform_info() const;
        std::vector<std::tuple<std::string, GLint, GLenum>> get_uniform_info(id_type variantId) const;
        std::vector<std::tuple<std::string, GLint, GLenum>> get_uniform_info(const std::string& variant) const;

        template<typename T>
        uniform<T> get_uniform(const std::string& name);

        template<typename T>
        bool has_uniform(const std::string& name);

        template<typename T>
        uniform<T> get_uniform(id_type uniformId);

        template<typename T>
        bool has_uniform(id_type uniformId);

        template<typename T>
        uniform<T> get_uniform_with_location(GLint location);

        template<typename T>
        bool has_uniform_with_location(GLint location);

        attribute get_attribute(const std::string& name);

        attribute get_attribute(id_type attributeId);

        void bind();
        static void release();

        bool operator==(const shader_handle& other) const { return id == other.id; }
        bool operator!=(const shader_handle& other) const { return id != other.id; }
        operator bool() const noexcept { return id != invalid_id; }

        template<typename Archive>
        void serialize(Archive& archive);
    };
    template<class Archive>
    void shader_handle::serialize(Archive& archive)
    {
        archive(id);
    }

    constexpr shader_handle invalid_shader_handle{ invalid_id };

    class ShaderCache
    {
        friend class renderer;
        friend struct shader_handle;
    private:

        static sparse_map<id_type, shader> m_shaders;
        static async::rw_spinlock m_shaderLock;

        static shader* get_shader(id_type id);

        static void process_io(shader& shader, id_type id);
        static app::gl_id compile_shader(GLuint shaderType, cstring source, GLint sourceLength);

        static bool load_precompiled(const fs::view& file, shader_ilo& ilo, std::unordered_map<std::string, shader_state>& state);
        static void store_precompiled(const fs::view& file, const shader_ilo& ilo, const std::unordered_map<std::string, shader_state>& state);

        static shader_handle create_invalid_shader(const fs::view& file, shader_import_settings settings = default_shader_settings);

    public:
        static shader_handle create_shader(const std::string& name, const fs::view& file, shader_import_settings settings = default_shader_settings);
        static shader_handle create_shader(const fs::view& file, shader_import_settings settings = default_shader_settings);
        static shader_handle get_handle(const std::string& name);
        static shader_handle get_handle(id_type id);
    };

    template<typename T>
    uniform<T> shader_handle::get_uniform(const std::string& name)
    {
        OPTICK_EVENT();
        return ShaderCache::get_shader(id)->get_uniform<T>(name);
    }

    template<typename T>
    inline bool shader_handle::has_uniform(const std::string& name)
    {
        OPTICK_EVENT();
        return ShaderCache::get_shader(id)->has_uniform<T>(name);
    }

    template<typename T>
    uniform<T> shader_handle::get_uniform(id_type uniformId)
    {
        OPTICK_EVENT();
        return ShaderCache::get_shader(id)->get_uniform<T>(uniformId);
    }

    template<typename T>
    inline bool shader_handle::has_uniform(id_type uniformId)
    {
        OPTICK_EVENT();
        return ShaderCache::get_shader(id)->has_uniform<T>(uniformId);
    }

    template<typename T>
    inline uniform<T> shader_handle::get_uniform_with_location(GLint location)
    {
        OPTICK_EVENT();
        return ShaderCache::get_shader(id)->get_uniform_with_location<T>(location);
    }

    template<typename T>
    inline bool shader_handle::has_uniform_with_location(GLint location)
    {
        OPTICK_EVENT();
        return ShaderCache::get_shader(id)->has_uniform_with_location<T>(location);
    }

}