Program Listing for File shadercompiler.cpp

Return to documentation for file (/home/runner/work/Legion-Engine/Legion-Engine/legion/engine/rendering/shadercompiler/shadercompiler.cpp)

#include <rendering/shadercompiler/shadercompiler.hpp>
#include <rendering/util/settings.hpp>
#include <lgnspre/gl_consts.hpp>
#include <application/application.hpp>

namespace legion::rendering
{
    delegate<void(const std::string&, log::severity)> ShaderCompiler::m_callback;


    std::string ShaderCompiler::get_view_path(const fs::view& view, bool mustBeFile)
    {
        OPTICK_EVENT();
        using severity = log::severity;

        fs::navigator navigator(view.get_virtual_path());
        auto solution = navigator.find_solution();
        if (solution.has_err())
        {
            m_callback(std::string("Shader processor error: ") + solution.get_error().what(), severity::error);
            return "";
        }

        auto s = solution.get();
        if (s.size() != 1)
        {
            m_callback("Shader processor error: invalid file, fs::view was not fully local", severity::error);
            return "";
        }

        fs::basic_resolver* resolver = dynamic_cast<fs::basic_resolver*>(s[0].first);
        if (!resolver)
        {
            m_callback("Shader processor error: invalid file, fs::view was not local", severity::error);
            return "";
        }

        resolver->set_target(s[0].second);

        if (!resolver->is_valid())
        {
            m_callback("Shader processor error: invalid path", severity::error);
            return "";
        }

        if (mustBeFile && !resolver->is_file())
        {
            m_callback("Shader processor error: not a file", severity::error);
            return "";
        }

        return resolver->get_absolute_path();
    }

    const std::string& ShaderCompiler::get_shaderlib_path()
    {
        OPTICK_EVENT();
        static std::string libPath;
        if (libPath.empty())
            libPath = get_view_path(fs::view("engine://shaderlib"), false);
        return libPath;
    }

    const std::string& ShaderCompiler::get_compiler_path()
    {
        OPTICK_EVENT();
        static std::string compPath;
        if (compPath.empty())
            compPath = get_view_path(fs::view("engine://tools"), false) + fs::strpath_manip::separator() + "lgnspre" + fs::strpath_manip::separator() + "lgnspre";
        return compPath;
    }

    const std::string& ShaderCompiler::get_cachecleaner_path()
    {
        OPTICK_EVENT();
        static std::string compPath;
        if (compPath.empty())
            compPath = get_view_path(fs::view("engine://tools"), false) + fs::strpath_manip::separator() + "lgnspre" + fs::strpath_manip::separator() + "lgncleancache";
        return compPath;
    }

    void ShaderCompiler::extract_state(std::string_view source, shader_state& state)
    {
        OPTICK_EVENT();
        std::string_view rest = source;
        std::vector<std::pair<std::string, std::string>> stateInput;
        while (!rest.empty())
        {
            auto seperator = rest.find_first_not_of('\n');
            seperator = rest.find_first_of('\n', seperator);

            if (seperator == std::string::npos)
                seperator = rest.size();

            auto line = rest.substr(0, seperator);
            auto space = line.find(' ');
            if (space != std::string::npos)
                stateInput.push_back(std::make_pair(std::string(line.substr(0, space)), std::string(line.substr(space + 1))));
            else
                stateInput.push_back(std::make_pair(std::string(line.substr(0, space)), std::string()));

            rest = rest.substr(seperator);
            seperator = rest.find_first_not_of('\n');
            if (seperator == std::string::npos)
                break;
            else
                rest = rest.substr(seperator);
        }

        // Create lookup table for the OpenGL function types that can be changed by the shader state.
        static std::unordered_map<std::string, GLenum> funcTypes;
        static bool funcTypesInitialized = false;
        if (!funcTypesInitialized)
        {
            funcTypesInitialized = true;
            funcTypes["DEPTH"] = GL_DEPTH_TEST;
            funcTypes["CULL"] = GL_CULL_FACE;
            funcTypes["ALPHA_SOURCE"] = GL_BLEND_SRC;
            funcTypes["ALPHA_DEST"] = GL_BLEND_DST;
            funcTypes["ALPHA"] = GL_BLEND;
            funcTypes["BLEND_SOURCE"] = GL_BLEND_SRC;
            funcTypes["BLEND_DEST"] = GL_BLEND_DST;
            funcTypes["BLEND"] = GL_BLEND;
            funcTypes["DITHER"] = GL_DITHER;
        }

        for (auto& [func, par] : stateInput)
        {
            if (!funcTypes.count(func)) // If the function type is unsupported or unknown we also want to fail compilation.
                continue;

            GLenum funcType = funcTypes.at(func); // Fetch the function type without risking editing the lookup table.
            GLenum param = GL_FALSE;

            switch (funcType)
            {
            case GL_DEPTH_TEST:
            {
                static std::unordered_map<std::string, GLenum> params; // Initialize parameter lookup table.
                static bool initialized = false;
                if (!initialized)
                {
                    initialized = true;
                    params["OFF"] = GL_FALSE;
                    params["NEVER"] = GL_NEVER;
                    params["LESS"] = GL_LESS;
                    params["EQUAL"] = GL_EQUAL;
                    params["LEQUAL"] = GL_LEQUAL;
                    params["GREATER"] = GL_GREATER;
                    params["NOTEQUAL"] = GL_NOTEQUAL;
                    params["GEQUAL"] = GL_GEQUAL;
                    params["ALWAYS"] = GL_ALWAYS;
                }

                if (!params.count(par))
                    continue;

                param = params.at(par);
            }
            break;
            case GL_CULL_FACE:
            {
                static std::unordered_map<std::string, GLenum> params; // Initialize parameter lookup table.
                static bool initialized = false;
                if (!initialized)
                {
                    initialized = true;
                    params["FRONT"] = GL_FRONT;
                    params["BACK"] = GL_BACK;
                    params["FRONT_AND_BACK"] = GL_FRONT_AND_BACK;
                    params["OFF"] = GL_FALSE;
                }

                if (!params.count(par))
                    continue;

                param = params.at(par);
            }
            break;
            case GL_BLEND:
            case GL_BLEND_SRC:
            case GL_BLEND_DST:
            {
                static std::unordered_map<std::string, GLenum> params; // Initialize parameter lookup table.
                static bool initialized = false;
                if (!initialized)
                {
                    initialized = true;
                    params["ZERO"] = GL_ZERO;
                    params["ONE"] = GL_ONE;
                    params["SRC_COLOR"] = GL_SRC_COLOR;
                    params["ONE_MINUS_SRC_COLOR"] = GL_ONE_MINUS_SRC_COLOR;
                    params["DST_COLOR"] = GL_DST_COLOR;
                    params["ONE_MINUS_DST_COLOR"] = GL_ONE_MINUS_DST_COLOR;
                    params["SRC_ALPHA"] = GL_SRC_ALPHA;
                    params["ONE_MINUS_SRC_ALPHA"] = GL_ONE_MINUS_SRC_ALPHA;
                    params["DST_ALPHA"] = GL_DST_ALPHA;
                    params["ONE_MINUS_DST_ALPHA"] = GL_ONE_MINUS_DST_ALPHA;
                    params["CONSTANT_COLOR"] = GL_CONSTANT_COLOR;
                    params["ONE_MINUS_CONSTANT_COLOR"] = GL_ONE_MINUS_CONSTANT_COLOR;
                    params["CONSTANT_ALPHA"] = GL_CONSTANT_ALPHA;
                    params["ONE_MINUS_CONSTANT_ALPHA"] = GL_ONE_MINUS_CONSTANT_ALPHA;
                    params["SRC_ALPHA_SATURATE"] = GL_SRC_ALPHA_SATURATE;
                    params["OFF"] = GL_FALSE;
                }

                if (!params.count(par))
                    continue;

                param = params.at(par);
            }
            break;
            case GL_DITHER:
            {
                static std::unordered_map<std::string, GLenum> params; // Initialize parameter lookup table.
                static bool initialized = false;
                if (!initialized)
                {
                    initialized = true;
                    params["OFF"] = GL_FALSE;
                    params["ON"] = GL_TRUE;
                    params["FALSE"] = GL_FALSE;
                    params["TRUE"] = GL_TRUE;
                }

                if (!params.count(par))
                    continue;

                param = params.at(par);
            }
            break;
            default:
                param = GL_FALSE;
                break;
            }

            state[funcType] = param;
        }
    }

    bool ShaderCompiler::extract_ilo(const std::string& variant, std::string_view source, uint64 shaderType, shader_ilo& ilo)
    {
        OPTICK_EVENT();
        using severity = log::severity;

        GLuint glShaderType = detail::get_gl_type(shaderType);
        auto lgnShaderType = detail::get_lgn_type(shaderType);

        if (shaderType != GL_LGN_VERTEX_SHADER &&
            shaderType != GL_LGN_FRAGMENT_SHADER &&
            shaderType != GL_LGN_GEOMETRY_SHADER &&
            shaderType != GL_LGN_TESS_EVALUATION_SHADER &&
            shaderType != GL_LGN_TESS_CONTROL_SHADER)
        {
            switch (lgnShaderType)
            {
            case LGN_VERTEX_SHADER:
                glShaderType = GL_VERTEX_SHADER;
                break;
            case LGN_FRAGMENT_SHADER:
                glShaderType = GL_FRAGMENT_SHADER;
                break;
            case LGN_GEOMETRY_SHADER:
                glShaderType = GL_GEOMETRY_SHADER;
                break;
            case LGN_TESS_EVALUATION_SHADER:
                glShaderType = GL_TESS_EVALUATION_SHADER;
                break;
            case LGN_TESS_CONTROL_SHADER:
                glShaderType = GL_TESS_CONTROL_SHADER;
                break;
            default:
                m_callback("Shader processor error: unkown shader type", severity::error);
                return false;
                break;
            }
        }

        ilo[variant].emplace_back(glShaderType, std::string(source));
        return true;
    }

    std::string ShaderCompiler::invoke_compiler(const fs::view& file, bitfield8 compilerSettings, const std::vector<std::string>& defines, const std::vector<std::string>& additionalIncludes)
    {
        OPTICK_EVENT();
        using severity = log::severity;

        std::string filepath = get_view_path(file, true);
        if (filepath.empty())
            return "";

        auto folderEnd = filepath.find_last_of("\\/");
        std::string folderPath(filepath.c_str(), folderEnd);

        std::string definesString = " -D LEGION";
        if (compilerSettings & shader_compiler_options::debug)
            definesString += " -D DEBUG";
        else
            definesString += " -D RELEASE";

        if (compilerSettings & shader_compiler_options::low_power)
            definesString += " -D LOW_POWER";
        else
            definesString += " -D HIGH_PERFORMANCE";

        bitfield8 apiSet = 0;
        if (compilerSettings & shader_compiler_options::api_opengl)
        {
            apiSet |= rendering_api::opengl;
            definesString += " -D API_OPENGL";
        }

        if (compilerSettings & shader_compiler_options::api_vulkan)
        {
            if (apiSet)
            {
                if (apiSet & rendering_api::opengl)
                    m_callback("Shader processor warning: both OpenGL and Vulkan were set as graphics api, OpenGL is assumed", severity::warn);
            }
            else
            {
                apiSet |= rendering_api::vulkan;
                definesString += " -D API_VULKAN";
            }
        }

        if (!apiSet)
        {
            m_callback("Shader processor warning: no api was set, OpenGL is assumed", severity::warn);
            definesString += " -D API_UNKNOWN -D API_OPENGL";
        }

        for (auto def : defines)
        {
            definesString += " -D " + def;
        }

        std::string includeString = " -I \"" + folderPath + "\" -I \"" + get_shaderlib_path() + "\"";

        for (auto incl : additionalIncludes)
        {
            includeString += " -I \"" + incl + "\"";
        }

        std::string command = "\"" + get_compiler_path() + "\" \"" + filepath + "\"" + definesString + includeString + " -f 1file -o stdout";

        std::string out, err;

        if (!ShellInvoke(command, out, err))
        {
            m_callback("Shader processor error: " + err, severity::error);
            return "";
        }

        if (!err.empty())
            m_callback("Shader processor warning: " + err, severity::warn);

        out.erase(std::remove(out.begin(), out.end(), '\r'), out.end());

        return out;
    }

    void ShaderCompiler::cleanCache()
    {
        OPTICK_EVENT();
        using severity = log::severity;
        std::string out, err;

        std::string command = "\"" + get_cachecleaner_path() + "\" -I \"" + get_shaderlib_path() + "\" ./ --filter=shil";

        if (!ShellInvoke(command, out, err))
        {
            m_callback("Shader processor error: " + err, severity::error);
        }
    }

    bool ShaderCompiler::process(const fs::view& file, bitfield8 compilerSettings, shader_ilo& ilo, std::unordered_map<std::string, shader_state>& state)
    {
        std::vector<std::string> temp;
        return process(file, compilerSettings, ilo, state, temp, temp);
    }

    bool ShaderCompiler::process(const fs::view& file, bitfield8 compilerSettings, shader_ilo& ilo, std::unordered_map<std::string, shader_state>& state, const std::vector<std::string>& defines)
    {
        std::vector<std::string> temp;
        return process(file, compilerSettings, ilo, state, defines, temp);
    }

    bool ShaderCompiler::process(const fs::view& file, bitfield8 compilerSettings, shader_ilo& ilo, std::unordered_map<std::string, shader_state>& state, const std::vector<std::string>& defines, const std::vector<std::string>& additionalIncludes)
    {
        OPTICK_EVENT();
        using severity = log::severity;

        log::info("Compiling shader: {}", file.get_virtual_path());

        auto result = invoke_compiler(file, compilerSettings, defines, additionalIncludes);

        if (result.empty())
            return false;

        auto start = result.find("=========== BEGIN SHADER CODE ===========\n") + 42;
        start = result.find_first_not_of('\n', start);
        auto end = result.find("============ END SHADER CODE ============");

        auto rest = std::string_view(result.data() + start, end - start);

        while (!rest.empty())
        {
            auto seperator = rest.find_first_not_of('\n');
            if (seperator == std::string::npos)
                break;

            seperator = rest.find_first_of('\n', seperator) + 1;
            if (rest.empty())
                break;
            auto shaderType = std::stoull(std::string(rest.substr(0, seperator)));
            rest = rest.substr(seperator);
            if (rest.empty())
                break;

            seperator = rest.find_first_of('\n') + 1;
            auto sourceLength = std::stoull(std::string(rest.substr(0, seperator)));
            if (sourceLength == 0)
                continue;

            rest = rest.substr(seperator);
            if (rest.empty())
                break;

            seperator = rest.substr(0, sourceLength).find_last_of('\n');
            auto source = rest.substr(0, seperator);
            rest = rest.substr(seperator);

            seperator = source.find_first_of('\n');
            std::string variant(source.substr(0, seperator));
            source = source.substr(seperator+1);

            if (shaderType == 0)
            {
                // Default shader state in case nothing was specified by the shader.
                shader_state& currentVariantState = state[variant];
                currentVariantState[GL_DEPTH_TEST] = GL_GREATER;
                currentVariantState[GL_CULL_FACE] = GL_BACK;
                currentVariantState[GL_BLEND_SRC] = GL_SRC_ALPHA;
                currentVariantState[GL_BLEND_DST] = GL_ONE_MINUS_SRC_ALPHA;
                currentVariantState[GL_DITHER] = GL_FALSE;
                extract_state(source, currentVariantState);
            }
            else if (!extract_ilo(variant, source, shaderType, ilo))
            {
                return false;
            }
            if (rest.empty())
                break;

            seperator = rest.find_first_not_of('\n');
            if (seperator == std::string::npos)
                break;
        }

        if (ilo.empty())
        {
            m_callback("Shader processor error: shader was empty", severity::error);
            return false;
        }
        return true;
    }
}