diff --git a/.github/workflows/arch.yml b/.github/workflows/arch.yml index d57ed7e..4987965 100644 --- a/.github/workflows/arch.yml +++ b/.github/workflows/arch.yml @@ -15,7 +15,7 @@ jobs: - name: Checkout uses: actions/checkout@v2 - - name: Publish parallel-disk-usage to the AUR + - name: Publish linux-wallpaperengine-git to the AUR uses: KSXGitHub/github-actions-deploy-aur@v2.7.2 with: pkgname: linux-wallpaperengine-git diff --git a/CMakeLists.txt b/CMakeLists.txt index 79dbaf2..e47ad63 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -34,6 +34,10 @@ find_package(MPV REQUIRED) find_package(LZ4 REQUIRED) find_package(FFMPEG REQUIRED) find_package(PulseAudio REQUIRED) +find_package(glslang REQUIRED) +find_package(spirv_cross_core CONFIG REQUIRED) +find_package(spirv_cross_glsl CONFIG REQUIRED) + # Download CEF of specified version for current platform # Specify the CEF distribution version. @@ -118,6 +122,9 @@ include_directories( src ${CEF_INCLUDE_PATH} ${CMAKE_SOURCE_DIR} + ${glslang_INCLUDE_DIRS} + ${spirv_cross_core_INCLUDE_DIRS} + ${spirv_cross_glsl_INCLUDE_DIRS} include) @@ -281,8 +288,10 @@ add_executable( src/WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector4.h src/WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector4.cpp - src/WallpaperEngine/Render/Shaders/Compiler.h - src/WallpaperEngine/Render/Shaders/Compiler.cpp + src/WallpaperEngine/Render/Shaders/CCompiler.h + src/WallpaperEngine/Render/Shaders/CCompiler.cpp + src/WallpaperEngine/Render/Shaders/CGLSLContext.cpp + src/WallpaperEngine/Render/Shaders/CGLSLContext.h src/WallpaperEngine/Render/Helpers/CContextAware.cpp src/WallpaperEngine/Render/Helpers/CContextAware.h @@ -463,6 +472,9 @@ target_link_libraries (linux-wallpaperengine PUBLIC ${FFMPEG_LIBRARIES} ${MPV_LIBRARY} ${PULSEAUDIO_LIBRARY} + glslang + spirv-cross-core + spirv-cross-glsl glfw libcef_lib libcef_dll_wrapper) diff --git a/src/WallpaperEngine/Logging/CLog.cpp b/src/WallpaperEngine/Logging/CLog.cpp index 45f35b6..c194047 100644 --- a/src/WallpaperEngine/Logging/CLog.cpp +++ b/src/WallpaperEngine/Logging/CLog.cpp @@ -1,6 +1,7 @@ #include "CLog.h" #include +#include using namespace WallpaperEngine::Logging; @@ -10,7 +11,7 @@ CLog::CLog () { CLog& CLog::get () { if (sInstance == nullptr) - sInstance.reset (new CLog ()); + sInstance = std::make_shared (); return *sInstance; } diff --git a/src/WallpaperEngine/Render/Objects/CImage.h b/src/WallpaperEngine/Render/Objects/CImage.h index 649be5b..8ca5ae7 100644 --- a/src/WallpaperEngine/Render/Objects/CImage.h +++ b/src/WallpaperEngine/Render/Objects/CImage.h @@ -8,7 +8,7 @@ #include "WallpaperEngine/Render/Objects/Effects/CPass.h" #include "WallpaperEngine/Render/Wallpapers/CScene.h" -#include "WallpaperEngine/Render/Shaders/Compiler.h" +#include "WallpaperEngine/Render/Shaders/CCompiler.h" #include "WallpaperEngine/Assets/ITexture.h" @@ -34,17 +34,17 @@ class CImage final : public CObject { void setup (); void render () override; - const Core::Objects::CImage* getImage () const; - const std::vector& getEffects () const; - glm::vec2 getSize () const; + [[nodiscard]] const Core::Objects::CImage* getImage () const; + [[nodiscard]] const std::vector& getEffects () const; + [[nodiscard]] glm::vec2 getSize () const; - GLuint getSceneSpacePosition () const; - GLuint getCopySpacePosition () const; - GLuint getPassSpacePosition () const; - GLuint getTexCoordCopy () const; - GLuint getTexCoordPass () const; - const ITexture* getTexture () const; - double getAnimationTime () const; + [[nodiscard]] GLuint getSceneSpacePosition () const; + [[nodiscard]] GLuint getCopySpacePosition () const; + [[nodiscard]] GLuint getPassSpacePosition () const; + [[nodiscard]] GLuint getTexCoordCopy () const; + [[nodiscard]] GLuint getTexCoordPass () const; + [[nodiscard]] const ITexture* getTexture () const; + [[nodiscard]] double getAnimationTime () const; /** * Performs a ping-pong on the available framebuffers to be able to continue rendering things to them diff --git a/src/WallpaperEngine/Render/Objects/Effects/CPass.cpp b/src/WallpaperEngine/Render/Objects/Effects/CPass.cpp index ea0a6ae..b285c4d 100644 --- a/src/WallpaperEngine/Render/Objects/Effects/CPass.cpp +++ b/src/WallpaperEngine/Render/Objects/Effects/CPass.cpp @@ -282,7 +282,7 @@ Core::Objects::Images::Materials::CPass* CPass::getPass () { return this->m_pass; } -GLuint CPass::compileShader (Render::Shaders::Compiler* shader, GLuint type) { +GLuint CPass::compileShader (Render::Shaders::CCompiler* shader, GLuint type) { // reserve shaders in OpenGL const GLuint shaderID = glCreateShader (type); @@ -332,16 +332,18 @@ void CPass::setupShaders () { } // prepare the shaders - this->m_fragShader = new Render::Shaders::Compiler ( - this->m_material->getImage ()->getContainer (), this->m_pass->getShader (), Shaders::Compiler::Type_Pixel, + this->m_fragShader = new Render::Shaders::CCompiler ( + this->m_material->getImage ()->getContainer (), this->m_pass->getShader (), Shaders::CGLSLContext::ShaderType_Pixel, this->m_pass->getCombos (), &m_foundCombos, this->m_pass->getTextures (), this->m_pass->getConstants ()); - this->m_fragShader->precompile (); - this->m_vertShader = new Render::Shaders::Compiler ( - this->m_material->getImage ()->getContainer (), this->m_pass->getShader (), Shaders::Compiler::Type_Vertex, + this->m_fragShader->compile (); + this->m_vertShader = new Render::Shaders::CCompiler ( + this->m_material->getImage ()->getContainer (), this->m_pass->getShader (), Shaders::CGLSLContext::ShaderType_Vertex, this->m_pass->getCombos (), &m_foundCombos, this->m_pass->getTextures (), this->m_pass->getConstants ()); - this->m_vertShader->precompile (); - this->m_fragShader->precompile (); - this->m_vertShader->precompile (); + this->m_vertShader->compile (); + + // they're re-compiled to ensure they are using the latest constants available + this->m_fragShader->compile (); + this->m_vertShader->compile (); // compile the shaders const GLuint vertexShaderID = compileShader (this->m_vertShader, GL_VERTEX_SHADER); diff --git a/src/WallpaperEngine/Render/Objects/Effects/CPass.h b/src/WallpaperEngine/Render/Objects/Effects/CPass.h index a5dea99..7bb502e 100644 --- a/src/WallpaperEngine/Render/Objects/Effects/CPass.h +++ b/src/WallpaperEngine/Render/Objects/Effects/CPass.h @@ -7,7 +7,7 @@ #include "WallpaperEngine/Core/Objects/Effects/Constants/CShaderConstant.h" #include "WallpaperEngine/Render/CFBO.h" #include "WallpaperEngine/Render/Objects/Effects/CMaterial.h" -#include "WallpaperEngine/Render/Shaders/Compiler.h" +#include "WallpaperEngine/Render/Shaders/CCompiler.h" #include "WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h" #include "WallpaperEngine/Render/Helpers/CContextAware.h" @@ -34,7 +34,7 @@ class CPass final : public Helpers::CContextAware { void setModelMatrix (const glm::mat4* model); void setViewProjectionMatrix (const glm::mat4* viewProjection); - const CMaterial* getMaterial () const; + [[nodiscard]] const CMaterial* getMaterial () const; Core::Objects::Images::Materials::CPass* getPass (); private: @@ -95,7 +95,7 @@ class CPass final : public Helpers::CContextAware { const GLuint* value; }; - static GLuint compileShader (Render::Shaders::Compiler* shader, GLuint type); + static GLuint compileShader (Render::Shaders::CCompiler* shader, GLuint type); void setupTextures (); void setupShaders (); void setupShaderVariables (); @@ -152,8 +152,8 @@ class CPass final : public Helpers::CContextAware { */ std::map m_finalTextures; - Render::Shaders::Compiler* m_fragShader; - Render::Shaders::Compiler* m_vertShader; + Render::Shaders::CCompiler* m_fragShader; + Render::Shaders::CCompiler* m_vertShader; const CFBO* m_drawTo; const ITexture* m_input; diff --git a/src/WallpaperEngine/Render/Shaders/CCompiler.cpp b/src/WallpaperEngine/Render/Shaders/CCompiler.cpp new file mode 100644 index 0000000..899c69b --- /dev/null +++ b/src/WallpaperEngine/Render/Shaders/CCompiler.cpp @@ -0,0 +1,340 @@ +#include "common.h" +#include +#include +#include +#include + +// filesystem +#include + +// shader compiler +#include +#include +#include +#include +#include + +#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h" +#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableFloat.h" +#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableInteger.h" +#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector2.h" +#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector3.h" +#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector4.h" + +#include "CGLSLContext.h" +#include "WallpaperEngine/Assets/CAssetLoadException.h" + +using namespace WallpaperEngine::Core; +using namespace WallpaperEngine::Assets; + +namespace WallpaperEngine::Render::Shaders { +CCompiler::CCompiler (CContainer* container, std::string filename, CGLSLContext::ShaderType type, std::map* combos, + std::map* foundCombos, const std::vector& textures, + const std::map& constants) : + m_combos (combos), + m_foundCombos (foundCombos), + m_passTextures (textures), + m_type (type), + m_file (std::move (filename)), + m_constants (constants), + m_container (container) { + if (type == CGLSLContext::ShaderType_Vertex) + this->m_content = this->m_container->readVertexShader (this->m_file); + else if (type == CGLSLContext::ShaderType_Pixel) + this->m_content = this->m_container->readFragmentShader (this->m_file); + else if (type == CGLSLContext::ShaderType_Include) + sLog.exception ("Include shaders should never be compiled, they're part of a bigger shader: ", this->m_file); + + // clone the combos into the baseCombos to keep track of values that must be embedded no matter what + for (const auto& [name, value] : *this->m_combos) + this->m_baseCombos.insert (std::make_pair (name, value)); +} + +std::string CCompiler::lookupShaderFile (const std::string& filename) { + // TODO: + // includes are not compiled, lookup the file and return the contents + // there might be situations where an #include is part of a comment + // instead of trying to figure out if an #include is part of that + // just try to load it + // if nothing is found, nothing is really lost + // but if something is found, depending on the type of comment and the contents + // the included file could break the shader, we'll need to perform some more + // checks here at some point, but for now should be enough + try { + return this->m_container->readIncludeShader (filename); + } catch (CAssetLoadException&) { + sLog.error ("Cannot find include file ", filename, " in shader ", this->m_file, ". Using empty content."); + return ""; + } +} + +std::string& CCompiler::getCompiled () { + return this->m_compiledContent; +} + +void CCompiler::compile () { + std::string precompile = "#version 330\n" + "// ======================================================\n" + "// Processed shader " + + this->m_file + + "\n" + "// ======================================================\n" + "precision highp float;\n" + "#define mul(x, y) ((y) * (x))\n" + "#define max(x, y) max (y, x)\n" + "#define lerp mix\n" + "#define frac fract\n" + "#define CAST2(x) (vec2(x))\n" + "#define CAST3(x) (vec3(x))\n" + "#define CAST4(x) (vec4(x))\n" + "#define CAST3X3(x) (mat3(x))\n" + "#define saturate(x) (clamp(x, 0.0, 1.0))\n" + "#define texSample2D texture\n" + "#define texSample2DLod textureLod\n" + "#define atan2 atan\n" + "#define fmod(x, y) ((x)-(y)*trunc((x)/(y)))\n" + "#define ddx dFdx\n" + "#define ddy(x) dFdy(-(x))\n" + "#define GLSL 1\n\n"; + + if (this->m_type == CGLSLContext::ShaderType_Vertex) { + precompile += "#define attribute in\n" + "#define varying out\n"; + } else { + precompile += "out vec4 out_FragColor;\n" + "#define varying in\n"; + } + // searches for the combos available and adds the defines required + + // go line by line in the shader content + size_t start = 0, end = 0; + while ((end = this->m_content.find ('\n', start)) != std::string::npos) { + // Extract a line from the string + std::string line = this->m_content.substr (start, end - start); + size_t combo = line.find("// [COMBO] "); + size_t uniform = line.find("uniform "); + size_t comment = line.find("// "); + size_t semicolon = line.find(';'); + + if (combo != std::string::npos) { + this->parseComboConfiguration (line.substr(combo + strlen("// [COMBO] ")), 0); + } else if (uniform != std::string::npos && comment != std::string::npos && semicolon != std::string::npos) { + // uniforms with comments should never have a value assigned, use this fact to detect the required parts + size_t last_space = line.find_last_of (' ', semicolon); + + if (last_space != std::string::npos) { + size_t previous_space = line.find_last_of (' ', last_space - 1); + + if (previous_space != std::string::npos) { + // extract type and name + std::string type = line.substr (previous_space + 1, last_space - previous_space - 1); + std::string name = line.substr (last_space + 1, semicolon - last_space - 1); + std::string json = line.substr (comment + 2); + + this->parseParameterConfiguration (type, name, json); + } + } + } + + // Move to the next line + start = end + 1; + } + + // add all the defines we have for now + for (const auto& [name, value] : *this->m_foundCombos) { + // find the right value for the combo in the combos map + auto combo = this->m_combos->find (name); + + if (combo == this->m_combos->end ()) + continue; + + precompile += "#define " + name + " " + std::to_string (combo->second) + "\n"; + } + + // add base combos that come from the pass change that MUST be added + for (const auto& [name, value] : this->m_baseCombos) { + auto alreadyFound = this->m_foundCombos->find (name); + + if (alreadyFound != this->m_foundCombos->end ()) + continue; + + precompile += "#define " + name + " " + std::to_string (value) + "\n"; + } + + precompile += this->m_content; + + // reset end so we start from the beginning + end = 0; + + // then apply includes in-place + while((start = precompile.find("#include", end)) != std::string::npos) { + size_t lineEnd = precompile.find_first_of ('\n', start); + // TODO: CHECK FOR ERRORS HERE, MALFORMED INCLUDES WILL NOT BE PROPERLY HANDLED + size_t quoteStart = precompile.find_first_of ('"', start) + 1; + size_t quoteEnd = precompile.find_first_of('"', quoteStart); + std::string filename = precompile.substr(quoteStart, quoteEnd - quoteStart); + std::string content = this->lookupShaderFile (filename); + + // file contents ready, replace things + precompile = precompile.replace (start, lineEnd - start, + "// begin of include from file " + filename + "\n" + + content + + "\n// end of included from file " + filename + "\n" + ); + + // go back to the beginning of the line to properly continue detecting things + end = start; + } + + // content should be ready, finally ask glslang to compile the shader + this->m_compiledContent = CGLSLContext::get().toGlsl (precompile, this->m_type); +} + +void CCompiler::parseComboConfiguration (const std::string& content, int defaultValue) { + json data = json::parse (content); + const auto combo = jsonFindRequired (data, "combo", "cannot parse combo information"); + const auto type = data.find ("type"); + const auto defvalue = data.find ("default"); + + // check the combos + const auto entry = this->m_combos->find (combo->get ()); + + // add the combo to the found list + this->m_foundCombos->insert (std::make_pair (*combo, true)); + + // if the combo was not found in the predefined values this means that the default value in the JSON data can be + // used so only define the ones that are not already defined + if (entry == this->m_combos->end ()) { + if (type != data.end ()) + sLog.error ("Resorting to default value as type ", *type, " is unknown"); + + // if no combo is defined just load the default settings + if (defvalue == data.end ()) { + // TODO: PROPERLY SUPPORT EMPTY COMBOS + this->m_combos->insert (std::make_pair (*combo, (int) defaultValue)); + } else if (defvalue->is_number_float ()) { + sLog.exception ("float combos are not supported in shader ", this->m_file, ". ", *combo); + } else if (defvalue->is_number_integer ()) { + this->m_combos->insert (std::make_pair (*combo, defvalue->get ())); + } else if (defvalue->is_string ()) { + sLog.exception ("string combos are not supported in shader ", this->m_file, ". ", *combo); + } else { + sLog.exception ("cannot parse combo information ", *combo, ". unknown type for ", defvalue->dump ()); + } + } +} + +void CCompiler::parseParameterConfiguration (const std::string& type, const std::string& name, + const std::string& content) { + json data = json::parse (content); + const auto material = data.find ("material"); + const auto defvalue = data.find ("default"); + // auto range = data.find ("range"); + const auto combo = data.find ("combo"); + + // this is not a real parameter + auto constant = this->m_constants.end (); + + if (material != data.end ()) + constant = this->m_constants.find (*material); + + if (constant == this->m_constants.end () && defvalue == data.end ()) { + if (type != "sampler2D") + sLog.exception ("Cannot parse parameter data for ", name, " in shader ", this->m_file); + } + + Variables::CShaderVariable* parameter = nullptr; + + // TODO: SUPPORT VALUES FOR ALL THESE TYPES + if (type == "vec4") { + parameter = new Variables::CShaderVariableVector4 ( + constant == this->m_constants.end () ? WallpaperEngine::Core::aToVector4 (*defvalue) + : *constant->second->as ()->getValue ()); + } else if (type == "vec3") { + parameter = new Variables::CShaderVariableVector3 ( + constant == this->m_constants.end () ? WallpaperEngine::Core::aToVector3 (*defvalue) + : *constant->second->as ()->getValue ()); + } else if (type == "vec2") { + parameter = new Variables::CShaderVariableVector2 (WallpaperEngine::Core::aToVector2 (*defvalue)); + } else if (type == "float") { + float value = 0; + + if (constant == this->m_constants.end ()) + value = defvalue->get (); + else if (constant->second->is ()) + value = *constant->second->as ()->getValue (); + else if (constant->second->is ()) + value = *constant->second->as ()->getValue (); + + parameter = new Variables::CShaderVariableFloat (value); + } else if (type == "int") { + int value = 0; + + if (constant == this->m_constants.end ()) + value = defvalue->get (); + else if (constant->second->is ()) + value = *constant->second->as ()->getValue (); + else if (constant->second->is ()) + value = *constant->second->as ()->getValue (); + + parameter = new Variables::CShaderVariableInteger (value); + } else if (type == "sampler2D" || type == "sampler2DComparison") { + // samplers can have special requirements, check what sampler we're working with and create definitions + // if needed + const auto textureName = data.find ("default"); + // extract the texture number from the name + const char value = name.at (std::string ("g_Texture").length ()); + // now convert it to integer + size_t index = value - '0'; + + if (combo != data.end ()) { + // if the texture exists (and is not null), add to the combo + if (this->m_passTextures.size () > index && + (!this->m_passTextures.at (index).empty () || textureName != data.end ())) { + // add the new combo to the list + this->m_combos->insert (std::make_pair (*combo, 1)); + + // textures linked to combos need to be tracked too + if (this->m_foundCombos->find (*combo) == this->m_foundCombos->end ()) + this->m_foundCombos->insert (std::make_pair (*combo, true)); + } + } + + if (textureName != data.end ()) + this->m_textures.insert (std::make_pair (index, *textureName)); + + // samplers are not saved, we can ignore them for now + return; + } else { + sLog.error ("Unknown parameter type: ", type, " for ", name, " in shader ", this->m_file); + return; + } + + if (material != data.end ()) { + parameter->setIdentifierName (*material); + parameter->setName (name); + + this->m_parameters.push_back (parameter); + } +} + +Variables::CShaderVariable* CCompiler::findParameter (const std::string& identifier) { + for (const auto& cur : this->m_parameters) + if (cur->getIdentifierName () == identifier) + return cur; + + return nullptr; +} + +const std::vector& CCompiler::getParameters () const { + return this->m_parameters; +} + +std::map* CCompiler::getCombos () const { + return this->m_combos; +} + +const std::map& CCompiler::getTextures () const { + return this->m_textures; +} +} // namespace WallpaperEngine::Render::Shaders diff --git a/src/WallpaperEngine/Render/Shaders/Compiler.h b/src/WallpaperEngine/Render/Shaders/CCompiler.h similarity index 55% rename from src/WallpaperEngine/Render/Shaders/Compiler.h rename to src/WallpaperEngine/Render/Shaders/CCompiler.h index 79d69a5..c7882f3 100644 --- a/src/WallpaperEngine/Render/Shaders/Compiler.h +++ b/src/WallpaperEngine/Render/Shaders/CCompiler.h @@ -11,6 +11,8 @@ #include "WallpaperEngine/FileSystem/FileSystem.h" #include "WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h" +#include "CGLSLContext.h" + namespace WallpaperEngine::Render::Shaders { using json = nlohmann::json; using namespace WallpaperEngine::Assets; @@ -19,22 +21,8 @@ using namespace WallpaperEngine::Core::Objects::Effects::Constants; /** * A basic shader loader that adds basic function definitions to every loaded shader */ -class Compiler { +class CCompiler { public: - /** - * Types of shaders - */ - enum Type { - Type_Vertex = 0, - Type_Pixel = 1, - Type_Include = 2 - }; - - /** - * Types of variables the pre-processor understands - */ - static std::vector sTypes; - /** * Compiler constructor, loads the given shader file and prepares * the pre-processing and compilation of the shader, adding @@ -49,16 +37,16 @@ class Compiler { * @param constants Default values for shader variables * @param recursive Whether the compiler should add base definitions or not */ - Compiler (CContainer* container, std::string filename, Type type, std::map* combos, + CCompiler (CContainer* container, std::string filename, CGLSLContext::ShaderType type, std::map* combos, std::map* foundCombos, const std::vector& textures, - const std::map& constants, bool recursive = false); + const std::map& constants); /** * Performs the actual pre-compilation/pre-processing over the shader files * This step is kinda big, replaces variables names on sVariableReplacement, * ensures #include directives are correctly handled * and takes care of attribute comments for the wallpaper engine specifics */ - void precompile (); + void compile (); /** * @return The compiled shader's text (if available) */ @@ -86,97 +74,6 @@ class Compiler { [[nodiscard]] const std::map& getTextures () const; private: - /** - * Checks if there is "str" in the current position without advancing the - * iterator in use - * - * @param str The string to check for - * @param it The position to start checking at - * - * @return - */ - bool peekString (std::string str, std::string::const_iterator& it); - /** - * Checks for a semicolon as current character, advancing the iterator - * after finding it, otherwise returns an error - * - * @param it The position where to expect the semicolon - * - * @return - */ - bool expectSemicolon (std::string::const_iterator& it); - /** - * Ignores contiguous space characters in the string advancing the iterator - * until the first non-space character - * - * @param it The iterator to increase - */ - void ignoreSpaces (std::string::const_iterator& it); - /** - * Ignores all characters until next line-fee (\n) advancing the interator - * - * @param it The iterator to increase - */ - void ignoreUpToNextLineFeed (std::string::const_iterator& it); - /** - * Ignores all characters until a block comment end is found, advancing the iterator - * - * @param it The iterator to increase - */ - void ignoreUpToBlockCommentEnd (std::string::const_iterator& it); - /** - * Parses the current position as a variable type, extracts it and compares it - * to the registered types in the pre-processor, returning it's name if valid - * increasing the iterator at the same time - * - * @param it The position to extract it from - * - * @return The type name - */ - std::string extractType (std::string::const_iterator& it); - /** - * Parses the current position as a variable name, extractig it's name and - * increasing the iterator as the name is extracted - * - * @param it The position to start extracting the variable name from - * - * @return The variable name - */ - std::string extractName (std::string::const_iterator& it); - /** - * Parses the current position as an array indicator - * - * @param it The position to start extracting the array from - * @param mustExists Whether the array indicator must exists or not - * @return - */ - std::string extractArray (std::string::const_iterator& it, bool mustExists = false); - /** - * Parses the current position as a quoted value, extracting it's value - * and increasing the iterator at the same time - * - * @param it The position to start extracting the value from - * - * @return The value - */ - std::string extractQuotedValue (std::string::const_iterator& it); - /** - * Tries to find the given shader file and compile it - * - * @param filename The shader's filename - * - * @return The compiled contents - */ - std::string lookupShaderFile (std::string filename); - - /** - * @return Whether the character in the current position is a character or not - */ - static bool isChar (const std::string::const_iterator& it); - /** - * @return Whether the character in the current position is a number or not - */ - static bool isNumeric (const std::string::const_iterator& it); /** * Parses a COMBO value to add the proper define to the code * @@ -193,9 +90,13 @@ class Compiler { */ void parseParameterConfiguration (const std::string& type, const std::string& name, const std::string& content); /** - * Applies any available patches for this shader + * Tries to find the given shader file and compile it + * + * @param filename The shader's filename + * + * @return The compiled contents */ - void applyPatches (); + std::string lookupShaderFile (const std::string& filename); /** * The shader file this instance is loading @@ -205,24 +106,14 @@ class Compiler { * The original file content */ std::string m_content; - /** The content of all the included files */ - std::string m_includesContent; /** * The final, compiled content ready to be used by OpenGL */ std::string m_compiledContent; - /** - * Whether there was any kind of error in the compilation or not - */ - bool m_error; - /** - * Extra information about the error (if any) - */ - std::string m_errorInfo; /** * The type of shader */ - Type m_type; + CGLSLContext::ShaderType m_type; /** * The parameters the shader needs */ @@ -250,10 +141,6 @@ class Compiler { * The shader constants with values for variables inside the shader */ const std::map& m_constants; - /** - * Whether this compilation is a recursive one or not - */ - bool m_recursive; /** * The container to load files from */ @@ -262,6 +149,5 @@ class Compiler { * List of textures that the shader expects (inferred from sampler2D and it's JSON data) */ std::map m_textures; - bool m_includesProcessed = false; }; } // namespace WallpaperEngine::Render::Shaders diff --git a/src/WallpaperEngine/Render/Shaders/CGLSLContext.cpp b/src/WallpaperEngine/Render/Shaders/CGLSLContext.cpp new file mode 100644 index 0000000..852096a --- /dev/null +++ b/src/WallpaperEngine/Render/Shaders/CGLSLContext.cpp @@ -0,0 +1,181 @@ +#include "CGLSLContext.h" +#include "WallpaperEngine/Logging/CLog.h" + +#include +#include + +#include +#include +#include +#include + +using namespace WallpaperEngine::Render::Shaders; + +TBuiltInResource BuiltInResource = { + .maxLights = 32, + .maxClipPlanes = 6, + .maxTextureUnits = 32, + .maxTextureCoords = 32, + .maxVertexAttribs = 64, + .maxVertexUniformComponents = 4096, + .maxVaryingFloats = 64, + .maxVertexTextureImageUnits = 32, + .maxCombinedTextureImageUnits = 80, + .maxTextureImageUnits = 32, + .maxFragmentUniformComponents = 4096, + .maxDrawBuffers = 32, + .maxVertexUniformVectors = 128, + .maxVaryingVectors = 8, + .maxFragmentUniformVectors = 16, + .maxVertexOutputVectors = 16, + .maxFragmentInputVectors = 15, + .minProgramTexelOffset = -8, + .maxProgramTexelOffset = 7, + .maxClipDistances = 8, + .maxComputeWorkGroupCountX = 65535, + .maxComputeWorkGroupCountY = 65535, + .maxComputeWorkGroupCountZ = 65535, + .maxComputeWorkGroupSizeX = 1024, + .maxComputeWorkGroupSizeY = 1024, + .maxComputeWorkGroupSizeZ = 64, + .maxComputeUniformComponents = 1024, + .maxComputeTextureImageUnits = 16, + .maxComputeImageUniforms = 8, + .maxComputeAtomicCounters = 8, + .maxComputeAtomicCounterBuffers = 1, + .maxVaryingComponents = 60, + .maxVertexOutputComponents = 64, + .maxGeometryInputComponents = 64, + .maxGeometryOutputComponents = 128, + .maxFragmentInputComponents = 128, + .maxImageUnits = 8, + .maxCombinedImageUnitsAndFragmentOutputs = 8, + .maxCombinedShaderOutputResources = 8, + .maxImageSamples = 0, + .maxVertexImageUniforms = 0, + .maxTessControlImageUniforms = 0, + .maxTessEvaluationImageUniforms = 0, + .maxGeometryImageUniforms = 0, + .maxFragmentImageUniforms = 8, + .maxCombinedImageUniforms = 8, + .maxGeometryTextureImageUnits = 16, + .maxGeometryOutputVertices = 256, + .maxGeometryTotalOutputComponents = 1024, + .maxGeometryUniformComponents = 1024, + .maxGeometryVaryingComponents = 64, + .maxTessControlInputComponents = 128, + .maxTessControlOutputComponents = 128, + .maxTessControlTextureImageUnits = 16, + .maxTessControlUniformComponents = 1024, + .maxTessControlTotalOutputComponents = 4096, + .maxTessEvaluationInputComponents = 128, + .maxTessEvaluationOutputComponents = 128, + .maxTessEvaluationTextureImageUnits = 16, + .maxTessEvaluationUniformComponents = 1024, + .maxTessPatchComponents = 120, + .maxPatchVertices = 32, + .maxTessGenLevel = 64, + .maxViewports = 16, + .maxVertexAtomicCounters = 0, + .maxTessControlAtomicCounters = 0, + .maxTessEvaluationAtomicCounters = 0, + .maxGeometryAtomicCounters = 0, + .maxFragmentAtomicCounters = 8, + .maxCombinedAtomicCounters = 8, + .maxAtomicCounterBindings = 1, + .maxVertexAtomicCounterBuffers = 0, + .maxTessControlAtomicCounterBuffers = 0, + .maxTessEvaluationAtomicCounterBuffers = 0, + .maxGeometryAtomicCounterBuffers = 0, + .maxFragmentAtomicCounterBuffers = 1, + .maxCombinedAtomicCounterBuffers = 1, + .maxAtomicCounterBufferSize = 16384, + .maxTransformFeedbackBuffers = 4, + .maxTransformFeedbackInterleavedComponents = 64, + .maxCullDistances = 8, + .maxCombinedClipAndCullDistances = 8, + .maxSamples = 4, + .maxMeshOutputVerticesNV = 256, + .maxMeshOutputPrimitivesNV = 512, + .maxMeshWorkGroupSizeX_NV = 32, + .maxMeshWorkGroupSizeY_NV = 1, + .maxMeshWorkGroupSizeZ_NV = 1, + .maxTaskWorkGroupSizeX_NV = 32, + .maxTaskWorkGroupSizeY_NV = 1, + .maxTaskWorkGroupSizeZ_NV = 1, + .maxMeshViewCountNV = 4, + .limits = { + .nonInductiveForLoops = 1, + .whileLoops = 1, + .doWhileLoops = 1, + .generalUniformIndexing = 1, + .generalAttributeMatrixVectorIndexing = 1, + .generalVaryingIndexing = 1, + .generalSamplerIndexing = 1, + .generalVariableIndexing = 1, + .generalConstantMatrixVectorIndexing = 1, + } +}; + +CGLSLContext::CGLSLContext () { + assert (this->sInstance == nullptr); + + glslang::InitializeProcess(); +} + +CGLSLContext::~CGLSLContext () { + glslang::FinalizeProcess(); +} + +CGLSLContext& CGLSLContext::get () { + if (sInstance == nullptr) + sInstance = std::make_shared (); + + return *sInstance; +} + +std::string CGLSLContext::toGlsl (const std::string& content, ShaderType type) { + if (type == ShaderType_Include) { + sLog.error ("Include shaders cannot be converted as they should be part of a bigger shader"); + return ""; + } + + EShLanguage shaderType = type == ShaderType_Vertex ? EShLangVertex : EShLangFragment; + glslang::TShader shader (shaderType); + + const char* shaderSource = content.c_str(); + shader.setStrings(&shaderSource, 1); + shader.setEntryPoint("main"); + // TODO: USE HLSL IF GLSL-TO-GLSL DOESN'T WORK + shader.setEnvInput(glslang::EShSourceGlsl, shaderType, glslang::EShClientOpenGL, 330); + shader.setEnvClient(glslang::EShClientOpenGL, glslang::EShTargetOpenGL_450); + shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_5); + shader.setAutoMapLocations (true); + shader.setAutoMapBindings (true); + + if (!shader.parse (&BuiltInResource, 100, false, EShMsgDefault)) { + sLog.error ("GLSL Parsing Failed: %s", shader.getInfoLog()); + return ""; + } + + glslang::TProgram program; + program.addShader (&shader); + + if (!program.link (EShMsgDefault)) { + sLog.error ("Program Linking Failed: %s", program.getInfoLog()); + return ""; + } + + std::vector spirv; + glslang::GlslangToSpv (*program.getIntermediate (shaderType), spirv); + + spirv_cross::CompilerGLSL compiler(spirv); + spirv_cross::CompilerGLSL::Options options; + options.version = 450; // OpenGL 4.5 / Vulkan GLSL TODO: MAYBE THIS CAN BE CHANGED? + options.es = false; + compiler.set_common_options(options); + + return compiler.compile(); +} + +std::shared_ptr CGLSLContext::sInstance = nullptr; \ No newline at end of file diff --git a/src/WallpaperEngine/Render/Shaders/CGLSLContext.h b/src/WallpaperEngine/Render/Shaders/CGLSLContext.h new file mode 100644 index 0000000..b02c534 --- /dev/null +++ b/src/WallpaperEngine/Render/Shaders/CGLSLContext.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include +#include + +namespace WallpaperEngine::Render::Shaders { +class CGLSLContext { + public: + /** + * Types of shaders + */ + enum ShaderType { + ShaderType_Vertex = 0, + ShaderType_Pixel = 1, + ShaderType_Include = 2 + }; + + CGLSLContext (); + ~CGLSLContext (); + + [[nodiscard]] std::string toGlsl (const std::string& content, ShaderType type); + + [[nodiscard]] static CGLSLContext& get (); + + private: + static std::shared_ptr sInstance; +}; +} // namespace WallpaperEngine::Render::Shaders \ No newline at end of file diff --git a/src/WallpaperEngine/Render/Shaders/Compiler.cpp b/src/WallpaperEngine/Render/Shaders/Compiler.cpp deleted file mode 100644 index 1325237..0000000 --- a/src/WallpaperEngine/Render/Shaders/Compiler.cpp +++ /dev/null @@ -1,771 +0,0 @@ -#include "common.h" -#include -#include -#include -#include - -// filesystem -#include - -// shader compiler -#include -#include -#include -#include -#include -#include - -#include "WallpaperEngine/Assets/CAssetLoadException.h" -#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariable.h" -#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableFloat.h" -#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableInteger.h" -#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector2.h" -#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector3.h" -#include "WallpaperEngine/Render/Shaders/Variables/CShaderVariableVector4.h" - -using namespace WallpaperEngine::Core; -using namespace WallpaperEngine::Assets; - -namespace WallpaperEngine::Render::Shaders { -Compiler::Compiler (CContainer* container, std::string filename, Type type, std::map* combos, - std::map* foundCombos, const std::vector& textures, - const std::map& constants, bool recursive) : - m_combos (combos), - m_foundCombos (foundCombos), - m_passTextures (textures), - m_recursive (recursive), - m_type (type), - m_file (std::move (filename)), - m_error (), - m_constants (constants), - m_container (container) { - if (type == Type_Vertex) - this->m_content = this->m_container->readVertexShader (this->m_file); - else if (type == Type_Pixel) - this->m_content = this->m_container->readFragmentShader (this->m_file); - else if (type == Type_Include) - this->m_content = this->m_container->readIncludeShader (this->m_file); - - // clone the combos into the baseCombos to keep track of values that must be embedded no matter what - for (const auto& [name, value] : *this->m_combos) - this->m_baseCombos.insert (std::make_pair (name, value)); -} - -bool Compiler::peekString (std::string str, std::string::const_iterator& it) { - std::string::const_iterator check = str.begin (); - std::string::const_iterator cur = it; - - while (cur != this->m_content.end () && check != str.end ()) { - if (*cur != *check) - return false; - - ++cur; - ++check; - } - - if (cur == this->m_content.end ()) { - return false; - } - - if (check != str.end ()) { - return false; - } - - it = cur; - - return true; -} - -bool Compiler::expectSemicolon (std::string::const_iterator& it) { - if (*it != ';') { - this->m_error = true; - this->m_errorInfo = std::string ("Expected semicolon but got ") + *it; - return false; - } - - ++it; - - return true; -} - -void Compiler::ignoreSpaces (std::string::const_iterator& it) { - while (it != this->m_content.end () && (*it == ' ' || *it == '\t')) - ++it; -} - -void Compiler::ignoreUpToNextLineFeed (std::string::const_iterator& it) { - while (it != this->m_content.end () && *it != '\n') - ++it; -} - -void Compiler::ignoreUpToBlockCommentEnd (std::string::const_iterator& it) { - while (it != this->m_content.end () && !this->peekString ("*/", it)) - ++it; -} - -std::string Compiler::extractType (std::string::const_iterator& it) { - // first of all check for highp/mediump/lowp as these operators have to be ignored - this->peekString ("highp", it); - this->peekString ("mediump", it); - this->peekString ("lowp", it); - this->ignoreSpaces (it); - - auto cur = sTypes.begin (); - const auto end = sTypes.end (); - - while (cur != end) { - if (this->peekString (*cur + " ", it)) - return *cur; - - ++cur; - } - - this->m_error = true; - this->m_errorInfo = "Expected type"; - return ""; -} - -std::string Compiler::extractName (std::string::const_iterator& it) { - std::string::const_iterator cur = it; - std::string::const_iterator begin = cur; - - // first character has to be a valid alphabetic characer - if (!this->isChar (cur) && *cur != '_') { - this->m_error = true; - this->m_errorInfo = "Expected name doesn't start with a valid character"; - return ""; - } - - ++cur; - - while (cur != this->m_content.end () && (this->isChar (cur) || *cur == '_' || this->isNumeric (cur))) - ++cur; - - it = cur; - - return {begin, cur}; -} - -std::string Compiler::extractArray (std::string::const_iterator& it, bool mustExists) { - auto cur = it; - auto begin = cur; - - if (*cur != '[') { - if (!mustExists) { - return ""; - } - - this->m_error = true; - this->m_errorInfo = "Expected an array but found nothing"; - return ""; - } - - ++cur; - - while (cur != this->m_content.end () && *cur != ']') - ++cur; - - it = ++cur; - - return {begin, cur}; -} - -bool Compiler::isChar (const std::string::const_iterator& it) { - return ((*it) >= 'A' && (*it) <= 'Z') || ((*it) >= 'a' && (*it) <= 'z'); -} - -bool Compiler::isNumeric (const std::string::const_iterator& it) { - return (*it) >= '0' && (*it) <= '9'; -} - -std::string Compiler::extractQuotedValue (std::string::const_iterator& it) { - std::string::const_iterator cur = it; - - if (*cur != '"') { - m_error = true; - m_errorInfo = std::string ("Expected opening \" but got ") + (*cur); - return ""; - } - - ++cur; - - while (cur != this->m_content.end () && *cur != '\n' && *cur != '"') - ++cur; - - if (cur == this->m_content.end ()) { - m_error = true; - m_errorInfo = "Expected closing \" not found"; - it = cur; - return ""; - } - - std::string filename = std::string (++it, cur); - - it = ++cur; - - return filename; -} - -std::string Compiler::lookupShaderFile (std::string filename) { - // now compile the new shader - // do not include the default header (as it's already included in the parent) - Compiler loader (this->m_container, std::move (filename), Type_Include, this->m_combos, this->m_foundCombos, - this->m_passTextures, this->m_constants, true); - - loader.precompile (); - - return loader.getCompiled (); -} - -std::string& Compiler::getCompiled () { - return this->m_compiledContent; -} - -void Compiler::precompile () { - // TODO: SEPARATE THIS IN TWO SO THE COMBOS ARE DETECTED FIRST AND WE DO NOT REQUIRE DOUBLE COMPILATION OF THE - // SHADER'S SOURCE -#define BREAK_IF_ERROR \ - if (this->m_error) { \ - sLog.exception ("ERROR PRE-COMPILING SHADER.", this->m_errorInfo); \ - } - // parse the shader and find #includes and such things and translate them to the correct name - // also remove any #version definition to prevent errors - std::string::const_iterator it = this->m_content.begin (); - - // reset error indicator - this->m_error = false; - this->m_errorInfo = ""; - this->m_compiledContent = ""; - this->m_includesContent = ""; - this->m_includesProcessed = false; - - // search preprocessor macros and parse them - while (it != this->m_content.end () && !this->m_error) { - if (*it == ' ' || *it == '\t' || *it == '\n' || *it == '\r' || *it == '\0' || *it == '{' || *it == '}' || - *it == '[' || *it == ']' || *it == '.') { - this->m_compiledContent += *it; - ++it; - } else if (*it == '#') { - if (this->peekString ("#include ", it)) { - // ignore whitespaces - this->ignoreSpaces (it); - BREAK_IF_ERROR - // extract value between quotes - std::string filename = this->extractQuotedValue (it); - BREAK_IF_ERROR - - if (this->m_recursive) { - this->m_compiledContent += "// begin of include from file " + filename + "\r\n"; - this->m_compiledContent += this->lookupShaderFile (filename); - this->m_compiledContent += "\r\n// end of included from file " + filename + "\r\n"; - } else { - // load the content to the includes contents and continue with the next one - // try to find the file first - this->m_includesContent += "// begin of included from file " + filename + "\r\n"; - this->m_includesContent += this->lookupShaderFile (filename); - this->m_includesContent += "\r\n// end of included from file " + filename + "\r\n"; - } - } else if (this->peekString ("#require ", it)) { - // TODO: PROPERLY IMPLEMENT #require - this->m_compiledContent += "// #require "; - // ignore whitespaces - this->ignoreSpaces (it); - BREAK_IF_ERROR - std::string name = this->extractName (it); - BREAK_IF_ERROR - this->m_compiledContent += name; - } else { - this->m_compiledContent += '#'; - ++it; - } - } else if (*it == 'u') { - // uniforms might have extra information for their values - if (this->peekString ("uniform ", it)) { - this->ignoreSpaces (it); - std::string type = this->extractType (it); - BREAK_IF_ERROR - this->ignoreSpaces (it); - std::string name = this->extractName (it); - BREAK_IF_ERROR - this->ignoreSpaces (it); - std::string array = this->extractArray (it, false); - BREAK_IF_ERROR - this->ignoreSpaces (it); - this->expectSemicolon (it); - BREAK_IF_ERROR - this->ignoreSpaces (it); - - // check if there is any actual extra information and parse it - if (this->peekString ("//", it)) { - this->ignoreSpaces (it); - std::string::const_iterator begin = it; - this->ignoreUpToNextLineFeed (it); - - std::string configuration; - configuration.append (begin, it); - - // parse the parameter information - this->parseParameterConfiguration (type, name, configuration); - BREAK_IF_ERROR - this->m_compiledContent += "uniform "; - this->m_compiledContent += type; - this->m_compiledContent += " "; - this->m_compiledContent += name; - this->m_compiledContent += array; - this->m_compiledContent += "; // "; - this->m_compiledContent += configuration; - } else { - this->m_compiledContent += "uniform "; - this->m_compiledContent += type; - this->m_compiledContent += " "; - this->m_compiledContent += name; - this->m_compiledContent += array; - this->m_compiledContent += ";"; - } - } else { - // continue reading the original shader as this most likely is a variable name or something - // that is not really interesting for the compiler - this->m_compiledContent += *it; - ++it; - } - } else if (*it == 'a') { - // find attribute definitions - if (this->peekString ("attribute ", it)) { - this->ignoreSpaces (it); - std::string type = this->extractType (it); - BREAK_IF_ERROR - this->ignoreSpaces (it); - std::string name = this->extractName (it); - BREAK_IF_ERROR - this->ignoreSpaces (it); - std::string array = this->extractArray (it, false); - BREAK_IF_ERROR - this->ignoreSpaces (it); - this->expectSemicolon (it); - BREAK_IF_ERROR - - this->m_compiledContent += "attribute " + type + " " + name + array + ";"; - } else { - // check for types first - std::string type = this->extractType (it); - - // types not found, try names - if (!this->m_error) { - this->ignoreSpaces (it); - this->m_compiledContent += type; - } else { - this->m_error = false; - std::string name = this->extractName (it); - - if (!this->m_error) { - // check if the name is a translated one or not - this->m_compiledContent += name; - } else { - this->m_error = false; - this->m_compiledContent += *it; - ++it; - } - } - } - } else if (*it == '/') { - if (this->peekString ("//", it)) { - std::string::const_iterator begin = it - 2; - // is there a COMBO mark to take care of? - this->ignoreSpaces (it); - - if (this->peekString ("[COMBO]", it)) { - // parse combo json data to define the proper variables - this->ignoreSpaces (it); - begin = it; - this->ignoreUpToNextLineFeed (it); - - std::string configuration; - configuration.append (begin, it); - - this->m_compiledContent += "// [COMBO] " + configuration; - - this->parseComboConfiguration (configuration, 0); - BREAK_IF_ERROR; - } else if (this->peekString ("[COMBO_OFF]", it)) { - // parse combo json data to define the proper variables - this->ignoreSpaces (it); - begin = it; - this->ignoreUpToNextLineFeed (it); - - std::string configuration; - configuration.append (begin, it); - - this->m_compiledContent += "// [COMBO_OFF] " + configuration; - - this->parseComboConfiguration (configuration, 0); - BREAK_IF_ERROR; - } else { - // the comment can be ignored and put back as is - this->ignoreUpToNextLineFeed (it); - this->m_compiledContent.append (begin, it); - } - } else if (this->peekString ("/*", it)) { - std::string::const_iterator begin = it - 2; - this->ignoreUpToBlockCommentEnd (it); - this->m_compiledContent.append (begin, it); - } else { - this->m_compiledContent += *it; - ++it; - } - } else { - // check for types first - std::string type = this->extractType (it); - - // type found - if (!this->m_error) { - this->ignoreSpaces (it); - // check for main, and take it into account, this also helps adding the includes - std::string name = this->extractName (it); - - this->ignoreSpaces (it); - - if (this->peekString ("(", it)) { - if (!this->m_includesProcessed) { - this->m_compiledContent += "\n\n" + this->m_includesContent + "\n\n"; - this->m_includesProcessed = true; - } - - this->m_compiledContent += type + " " + name + "("; - } else { - this->m_compiledContent += type + " " + name; - } - } else { - this->m_error = false; - std::string name = this->extractName (it); - - if (!this->m_error) { - // check if the name is a translated one or not - this->m_compiledContent += name; - } else { - this->m_error = false; - this->m_compiledContent += *it++; - } - } - } - } - - std::string finalCode; - - if (!this->m_recursive) { - // add the opengl compatibility at the top - finalCode = "#version 330\n" - "// ======================================================\n" - "// Processed shader " + - this->m_file + - "\n" - "// ======================================================\n" - "precision highp float;\n" - "#define mul(x, y) ((y) * (x))\n" - "#define max(x, y) max (y, x)\n" - "#define lerp mix\n" - "#define frac fract\n" - "#define CAST2(x) (vec2(x))\n" - "#define CAST3(x) (vec3(x))\n" - "#define CAST4(x) (vec4(x))\n" - "#define CAST3X3(x) (mat3(x))\n" - "#define saturate(x) (clamp(x, 0.0, 1.0))\n" - "#define texSample2D texture\n" - "#define texSample2DLod textureLod\n" - "#define atan2 atan\n" - "#define fmod(x, y) ((x)-(y)*trunc((x)/(y)))\n" - "#define ddx dFdx\n" - "#define ddy(x) dFdy(-(x))\n" - "#define GLSL 1\n\n"; - - if (this->m_type == Type_Vertex) { - finalCode += "#define attribute in\n" - "#define varying out\n"; - } else { - finalCode += "out vec4 out_FragColor;\n" - "#define varying in\n"; - } - - finalCode += "// ======================================================\n" - "// Shader combo parameter definitions\n" - "// ======================================================\n"; - - finalCode += "// found combos from current shader\n"; - - // add combo values - for (const auto& [name, value] : *this->m_foundCombos) { - // find the right value for the combo in the combos map - auto combo = this->m_combos->find (name); - - if (combo == this->m_combos->end ()) - continue; - - finalCode += "#define " + name + " " + std::to_string (combo->second) + "\n"; - } - - finalCode += "// combos from pass\n"; - - // add base combos that come from the pass change that MUST be added - for (const auto& [name, value] : this->m_baseCombos) { - auto alreadyFound = this->m_foundCombos->find (name); - - if (alreadyFound != this->m_foundCombos->end ()) - continue; - - finalCode += "#define " + name + " " + std::to_string (value) + "\n"; - } - } - - // replace gl_FragColor with the equivalent - std::string from = "gl_FragColor"; - std::string to = "out_FragColor"; - - size_t start_pos = 0; - while ((start_pos = this->m_compiledContent.find (from, start_pos)) != std::string::npos) { - this->m_compiledContent.replace (start_pos, from.length (), to); - start_pos += to.length (); // Handles case where 'to' is a substring of 'from' - } - - // replace sample occurrences - from = "sample"; - to = "_sample"; - - start_pos = 0; - while ((start_pos = this->m_compiledContent.find (from, start_pos)) != std::string::npos) { - // ensure that after it comes something like a space or a ; or a tab - std::string after = this->m_compiledContent.substr (start_pos + from.length (), 1); - - if (after != " " && after != ";" && after != "\t" && after != "=" && after != "+" && after != "-" && - after != "/" && after != "*" && after != "." && after != "," && after != ")") { - start_pos += to.length (); // Handles case where 'to' is a substring of 'from' - continue; - } - - this->m_compiledContent.replace (start_pos, from.length (), to); - start_pos += to.length (); // Handles case where 'to' is a substring of 'from' - } - - try { - this->applyPatches (); - } catch (CAssetLoadException&) { - // nothing important, no patch was found - } - - finalCode += this->m_compiledContent; - - if (!this->m_recursive) { - sLog.debug ("======================== COMPILED ", (this->m_type == Type_Vertex ? "VERTEX" : "FRAGMENT"), - " SHADER ", this->m_file, " ========================"); - sLog.debug (finalCode); - } - - // store the final final code here - this->m_compiledContent = finalCode; -#undef BREAK_IF_ERROR -} - -void Compiler::applyPatches () { - // small patches for things, looks like the official wpengine does the same thing - std::filesystem::path file = this->m_file; - file = "patches" / file.filename (); - - if (this->m_type == Type_Vertex) - file += ".vert"; - else if (this->m_type == Type_Pixel) - file += ".frag"; - - file += ".json"; - - std::string tmp = file; - const std::string patchContents = this->m_container->readFileAsString (file); - - json data = json::parse (patchContents); - const auto patches = data.find ("patches"); - - for (auto patch : *patches) { - auto matches = patch.find ("matches"); - bool canApply = true; - - // check for matches first, as these signal whether the patch can be applied or not - for (const auto& match : *matches) { - if (this->m_compiledContent.find (match) == std::string::npos) { - canApply = false; - break; - } - } - - if (canApply == false) - continue; - - const auto replacements = patch.find ("replacements"); - - for (const auto& replacement : replacements->items ()) { - // replace gl_FragColor with the equivalent - std::string from = replacement.key (); - std::string to = replacement.value (); - - size_t start_pos = 0; - while ((start_pos = this->m_compiledContent.find (from, start_pos)) != std::string::npos) { - this->m_compiledContent.replace (start_pos, from.length (), to); - start_pos += to.length (); // Handles case where 'to' is a substring of 'from' - } - } - } -} - -void Compiler::parseComboConfiguration (const std::string& content, int defaultValue) { - json data = json::parse (content); - const auto combo = jsonFindRequired (data, "combo", "cannot parse combo information"); - const auto type = data.find ("type"); - const auto defvalue = data.find ("default"); - - // add line feed just in case - this->m_compiledContent += "\n"; - - // check the combos - const auto entry = this->m_combos->find (combo->get ()); - - // add the combo to the found list - this->m_foundCombos->insert (std::make_pair (*combo, true)); - - // if the combo was not found in the predefined values this means that the default value in the JSON data can be - // used so only define the ones that are not already defined - if (entry == this->m_combos->end ()) { - if (type != data.end ()) - sLog.error ("Resorting to default value as type ", *type, " is unknown"); - - // if no combo is defined just load the default settings - if (defvalue == data.end ()) { - // TODO: PROPERLY SUPPORT EMPTY COMBOS - this->m_combos->insert (std::make_pair (*combo, (int) defaultValue)); - } else if (defvalue->is_number_float ()) { - sLog.exception ("float combos are not supported in shader ", this->m_file, ". ", *combo); - } else if (defvalue->is_number_integer ()) { - this->m_combos->insert (std::make_pair (*combo, defvalue->get ())); - } else if (defvalue->is_string ()) { - sLog.exception ("string combos are not supported in shader ", this->m_file, ". ", *combo); - } else { - sLog.exception ("cannot parse combo information ", *combo, ". unknown type for ", defvalue->dump ()); - } - } -} - -void Compiler::parseParameterConfiguration (const std::string& type, const std::string& name, - const std::string& content) { - json data = json::parse (content); - const auto material = data.find ("material"); - const auto defvalue = data.find ("default"); - // auto range = data.find ("range"); - const auto combo = data.find ("combo"); - - // this is not a real parameter - auto constant = this->m_constants.end (); - - if (material != data.end ()) - constant = this->m_constants.find (*material); - - if (constant == this->m_constants.end () && defvalue == data.end ()) { - if (type != "sampler2D") - sLog.exception ("Cannot parse parameter data for ", name, " in shader ", this->m_file); - } - - Variables::CShaderVariable* parameter; - - // TODO: SUPPORT VALUES FOR ALL THESE TYPES - if (type == "vec4") { - parameter = new Variables::CShaderVariableVector4 ( - constant == this->m_constants.end () ? WallpaperEngine::Core::aToVector4 (*defvalue) - : *constant->second->as ()->getValue ()); - } else if (type == "vec3") { - parameter = new Variables::CShaderVariableVector3 ( - constant == this->m_constants.end () ? WallpaperEngine::Core::aToVector3 (*defvalue) - : *constant->second->as ()->getValue ()); - } else if (type == "vec2") { - parameter = new Variables::CShaderVariableVector2 (WallpaperEngine::Core::aToVector2 (*defvalue)); - } else if (type == "float") { - float value = 0; - - if (constant == this->m_constants.end ()) - value = defvalue->get (); - else if (constant->second->is ()) - value = *constant->second->as ()->getValue (); - else if (constant->second->is ()) - value = *constant->second->as ()->getValue (); - - parameter = new Variables::CShaderVariableFloat (value); - } else if (type == "int") { - int value = 0; - - if (constant == this->m_constants.end ()) - value = defvalue->get (); - else if (constant->second->is ()) - value = *constant->second->as ()->getValue (); - else if (constant->second->is ()) - value = *constant->second->as ()->getValue (); - - parameter = new Variables::CShaderVariableInteger (value); - } else if (type == "sampler2D" || type == "sampler2DComparison") { - // samplers can have special requirements, check what sampler we're working with and create definitions - // if needed - const auto textureName = data.find ("default"); - // extract the texture number from the name - const char value = name.at (std::string ("g_Texture").length ()); - // now convert it to integer - size_t index = value - '0'; - - if (combo != data.end ()) { - // if the texture exists (and is not null), add to the combo - if (this->m_passTextures.size () > index && - (!this->m_passTextures.at (index).empty () || textureName != data.end ())) { - // add the new combo to the list - this->m_combos->insert (std::make_pair (*combo, 1)); - - // textures linked to combos need to be tracked too - if (this->m_foundCombos->find (*combo) == this->m_foundCombos->end ()) - this->m_foundCombos->insert (std::make_pair (*combo, true)); - } - } - - if (textureName != data.end ()) - this->m_textures.insert (std::make_pair (index, *textureName)); - - // samplers are not saved, we can ignore them for now - return; - } else { - this->m_error = true; - this->m_errorInfo = "Unknown parameter type: " + type + " for " + name; - return; - } - - if (material != data.end ()) { - parameter->setIdentifierName (*material); - parameter->setName (name); - - this->m_parameters.push_back (parameter); - } -} - -Variables::CShaderVariable* Compiler::findParameter (const std::string& identifier) { - for (const auto& cur : this->m_parameters) - if (cur->getIdentifierName () == identifier) - return cur; - - return nullptr; -} - -const std::vector& Compiler::getParameters () const { - return this->m_parameters; -} - -std::map* Compiler::getCombos () const { - return this->m_combos; -} - -const std::map& Compiler::getTextures () const { - return this->m_textures; -} - -std::vector Compiler::sTypes = {"vec4", "uvec4", "ivec4", "dvec4", "bvec4", "vec3", - "uvec3", "ivec3", "dvec3", "bvec3", "vec2", "uvec2", - "ivec2", "dvec2", "bvec2", "float", "sampler2D", "sampler2DComparison", - "mat4x3", "mat4", "mat3", "uint", "uint4", "void"}; -} // namespace WallpaperEngine::Render::Shaders