diff --git a/CMakeLists.txt b/CMakeLists.txt index 0a36135..e600a85 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -20,8 +20,8 @@ include_directories(${X11_INCLUDE_DIR} ${IRRLICHT_INCLUDE_DIR} ${LZ4_INCLUDE_DIR add_executable( wallengine main.cpp - WallpaperEngine/shaders/compiler.h - WallpaperEngine/shaders/compiler.cpp + WallpaperEngine/Render/Shaders/Compiler.h + WallpaperEngine/Render/Shaders/Compiler.cpp WallpaperEngine/project.cpp WallpaperEngine/project.h WallpaperEngine/scene.cpp diff --git a/WallpaperEngine/Render/Shaders/Compiler.cpp b/WallpaperEngine/Render/Shaders/Compiler.cpp new file mode 100644 index 0000000..5fd4f89 --- /dev/null +++ b/WallpaperEngine/Render/Shaders/Compiler.cpp @@ -0,0 +1,635 @@ +#include +#include +#include +#include + +// filesystem +#include + +// video engine +#include + +// shader compiler +#include +#include + +namespace WallpaperEngine::Render::Shaders +{ + Compiler::Compiler (irr::io::path& file, Type type, std::map* combos, bool recursive) + { + this->m_recursive = recursive; + this->m_combos = combos; + + // begin with an space so it gets ignored properly on parse + if (recursive == false) + { + // compatibility layer for OpenGL shaders + this->m_content = "#version 120\n" + "#define highp\n" + "#define mediump\n" + "#define lowp\n" + "#define mul(x, y) (y * x)\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 texture2D\n" + "#define texSample2DLod texture2DLod\n" + "#define texture2DLod texture2D\n" + "#define atan2 atan\n" + "#define ddx dFdx\n" + "#define ddy(x) dFdy(-(x))\n" + "#define GLSL 1\n\n"; + + std::map::const_iterator cur = this->m_combos->begin (); + std::map::const_iterator end = this->m_combos->end (); + + for (; cur != end; cur ++) + { + this->m_content += "#define " + (*cur).first + " " + std::to_string ((*cur).second) + "\n"; + } + } + else + { + this->m_content = ""; + } + + this->m_content.append (WallpaperEngine::FileSystem::loadFullFile (file)); + + // append file content + this->m_type = type; + + this->m_file = file; + } + + 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 = "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) == false) it ++; + } + + std::string Compiler::extractType (std::string::const_iterator& it) + { + std::vector::const_iterator cur = sTypes.begin (); + std::vector::const_iterator end = sTypes.end (); + + while (cur != end) + { + if (this->peekString (*cur, it) == true) + { + 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) == false && *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) == true || *cur == '_' || this->isNumeric (cur) == true)) cur ++; + + it = cur; + + return std::string (begin, cur); + } + + bool Compiler::isChar (std::string::const_iterator& it) + { + return ((*it) >= 'A' && (*it) <= 'Z') || ((*it) >= 'a' && (*it) <= 'z'); + } + + bool Compiler::isNumeric (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 = "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) + { + // get file information + irr::io::path shader = ("shaders/" + filename).c_str (); + + if (shader == "") + { + this->m_error = true; + this->m_errorInfo = "Cannot find file " + filename + " to include"; + return ""; + } + + // now compile the new shader + // do not include the default header (as it's already included in the parent) + Compiler loader (shader, this->m_type, this->m_combos, true); + + return loader.precompile (); + } + + std::string Compiler::lookupReplaceSymbol (std::string symbol) + { + std::map::const_iterator cur = sVariableReplacement.begin (); + std::map::const_iterator end = sVariableReplacement.end (); + + while (cur != end) + { + if (cur->first == symbol) + { + return cur->second; + } + + cur ++; + } + + // if there is no replacement, return the original + return symbol; + } + + std::string Compiler::precompile() + { + #define BREAK_IF_ERROR if (this->m_error == true) { WallpaperEngine::Irrlicht::device->getLogger ()->log ("ERROR PRE-COMPILING SHADER", irr::ELL_ERROR); WallpaperEngine::Irrlicht::device->getLogger ()->log (this->m_errorInfo.c_str (), irr::ELL_ERROR); return ""; } + // 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 = ""; + + // search preprocessor macros and parse them + while (it != this->m_content.end () && this->m_error == false) + { + 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) == true) + { + std::string filename = ""; + + // ignore whitespaces + this->ignoreSpaces (it); BREAK_IF_ERROR + // extract value between quotes + filename = this->extractQuotedValue (it); BREAK_IF_ERROR + + // try to find the file first + this->m_compiledContent += "// begin of included 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 + { + this->m_compiledContent += '#'; + it ++; + } + } + else if (*it == 'u') + { + // uniforms might have extra information for their values + if (this->peekString ("uniform", it) == true) + { + 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); + this->expectSemicolon (it); BREAK_IF_ERROR + this->ignoreSpaces (it); + + // check if there is any actual extra information and parse it + if (this->peekString ("//", it) == true) + { + 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 " + type + " " + name + "; // " + configuration; + } + else + { + this->m_compiledContent += "uniform " + type + " " + name + ";"; + } + } + } + else if (*it == 'a') + { + // find attribute definitions + if (this->peekString ("attribute", it) == true) + { + 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); + this->expectSemicolon (it); BREAK_IF_ERROR + + this->m_compiledContent += "// attribute"; + this->m_compiledContent += " " + type + " "; + this->m_compiledContent += name; + this->m_compiledContent += "; /* replaced by " + this->lookupReplaceSymbol (name) + " */"; + } + else + { + // check for types first + std::string type = this->extractType (it); + + // types not found, try names + if (this->m_error == false) + { + this->m_compiledContent += type; + } + else + { + this->m_error = false; + std::string name = this->extractName (it); + + if (this->m_error == false) + { + // check if the name is a translated one or not + this->m_compiledContent += this->lookupReplaceSymbol (name); + } + else + { + this->m_error = false; + this->m_compiledContent += *it; + it ++; + } + } + } + } + else if (*it == '/') + { + if (this->peekString ("//", it) == true) + { + std::string::const_iterator begin = it - 2; + // is there a COMBO mark to take care of? + this->ignoreSpaces (it); + + if (this->peekString ("[COMBO]", it) == true) + { + // 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); BREAK_IF_ERROR; + } + else if (this->peekString ("[COMBO_OFF]", it) == true) + { + // 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); BREAK_IF_ERROR; + } + else + { + this->ignoreUpToNextLineFeed (it); + this->m_compiledContent.append (begin, it); + } + } + else if (this->peekString ("/*", it) == true) + { + 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); + + // types not found, try names + if (this->m_error == false) + { + this->m_compiledContent += type; + } + else + { + this->m_error = false; + std::string name = this->extractName (it); + + if (this->m_error == false) + { + // check if the name is a translated one or not + this->m_compiledContent += this->lookupReplaceSymbol (name); + } + else + { + this->m_error = false; + this->m_compiledContent += *it; + it ++; + } + } + } + } + + if (this->m_recursive == false) + { + WallpaperEngine::Irrlicht::device->getLogger ()->log ("Compiled shader output for", this->m_file.c_str ()); + WallpaperEngine::Irrlicht::device->getLogger ()->log (this->m_compiledContent.c_str ()); + } + + return this->m_compiledContent; + #undef BREAK_IF_ERROR + } + + void Compiler::parseComboConfiguration (const std::string& content) + { + json data = json::parse (content); + json::const_iterator combo = data.find ("combo"); + json::const_iterator defvalue = data.find ("default"); + + // add line feed just in case + this->m_compiledContent += "\n"; + + if (combo == data.end () || defvalue == data.end ()) + { + WallpaperEngine::Irrlicht::device->getLogger ()->log ("Cannot parse combo information", irr::ELL_ERROR); + return; + } + + // check the combos + std::map::const_iterator entry = this->m_combos->find ((*combo).get ()); + + // 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 no combo is defined just load the default settings + if ((*defvalue).is_number_float ()) + { + this->m_compiledContent += "#define " + (*combo).get () + " " + std::to_string ((*defvalue).get ()) + "\n"; + } + else if ((*defvalue).is_number_integer ()) + { + this->m_compiledContent += "#define " + (*combo).get () + " " + std::to_string ((*defvalue).get ()) + "\n"; + } + else if ((*defvalue).is_string ()) + { + this->m_compiledContent += "#define " + (*combo).get () + " " + (*defvalue).get () + "\n"; + } + else + { + WallpaperEngine::Irrlicht::device->getLogger ()->log ("Cannot parse combo information, unknown type", irr::ELL_ERROR); + } + } + } + + void Compiler::parseParameterConfiguration (const std::string& type, const std::string& name, const std::string& content) + { + json data = json::parse (content); + json::const_iterator material = data.find ("material"); + json::const_iterator defvalue = data.find ("default"); + json::const_iterator range = data.find ("range"); + + // this is not a real parameter + if (material == data.end () || defvalue == data.end ()) + { + if (type != "sampler2D") + WallpaperEngine::Irrlicht::device->getLogger ()->log ("Cannot parse parameter info for ", name.c_str (), irr::ELL_ERROR); + + return; + } + + ShaderParameter* param = new ShaderParameter; + + param->identifierName = (*material).get (); + param->variableName = name; + param->type = type; + + if (type == "vec4" || type == "vec3") + { + if ((*defvalue).is_string () == false) + { + irr::core::vector3df* vector = new irr::core::vector3df; + + vector->X = 0.0f; + vector->Y = 0.0f; + vector->Z = 0.0f; + + param->defaultValue = vector; + } + else + { + irr::core::vector3df tmp = WallpaperEngine::Core::ato3vf ((*defvalue).get ().c_str ()); + irr::core::vector3df* vector = new irr::core::vector3df; + + vector->X = tmp.X; + vector->Y = tmp.Y; + vector->Z = tmp.Z; + + param->defaultValue = vector; + } + } + else if (type == "vec2") + { + if ((*defvalue).is_string () == false) + { + irr::core::vector2df* vector = new irr::core::vector2df; + + vector->X = 0.0f; + vector->Y = 0.0f; + + param->defaultValue = vector; + } + else + { + irr::core::vector2df* vector = new irr::core::vector2df; + irr::core::vector2df tmp = WallpaperEngine::Core::ato2vf ((*defvalue).get ().c_str ()); + + vector->X = tmp.X; + vector->Y = tmp.Y; + + param->defaultValue = vector; + } + } + else if (type == "float") + { + if ((*defvalue).is_number () == false) + { + irr::f32* val = new irr::f32; + + *val = 0.0f; + + param->defaultValue = val; + + } + else + { + irr::f32* val = new irr::f32; + + *val = (*defvalue).get (); + + param->defaultValue = val; + } + } + else if (type == "sampler2D") + { + // samplers are not saved, we can ignore them for now + delete param; + return; + } + else + { + this->m_error = true; + this->m_errorInfo = "Unknown parameter type: " + type + " for " + param->identifierName + " (" + param->variableName + ")"; + return; + } + + this->m_parameters.push_back (param); + } + + Compiler::ShaderParameter* Compiler::findParameter (std::string identifier) + { + std::vector::const_iterator cur = this->m_parameters.begin (); + std::vector::const_iterator end = this->m_parameters.end (); + + for (; cur != end; cur ++) + { + if ((*cur)->identifierName == identifier) + { + return (*cur); + } + } + + return nullptr; + } + + std::vector & Compiler::getParameters () + { + return this->m_parameters; + } + + std::map Compiler::sVariableReplacement = + { + // attribute vec3 a_position + {"a_Position", "gl_Vertex.xyz"}, + // attribute vec2 a_TexCoord + {"a_TexCoord", "gl_MultiTexCoord0.xy"}, + // attribute vec3 a_Normal + {"a_Normal", "gl_Normal.xyz"} + }; + + std::vector Compiler::sTypes = + { + "vec4", "vec3", "vec2", "float", "sampler2D", "mat4" + }; +} diff --git a/WallpaperEngine/Render/Shaders/Compiler.h b/WallpaperEngine/Render/Shaders/Compiler.h new file mode 100644 index 0000000..892cfd2 --- /dev/null +++ b/WallpaperEngine/Render/Shaders/Compiler.h @@ -0,0 +1,240 @@ +#pragma once + +#include +#include +#include +#include +#include + +#include + +namespace WallpaperEngine::Render::Shaders +{ + using json = nlohmann::json; + + /** + * A basic shader loader that adds basic function definitions to every loaded shader + */ + class Compiler + { + public: + /** + * Basic struct used to define all the shader variables + * the compiler will replace in pre-processing time + * to make sure the shaders compile under OpenGL + */ + struct VariableReplacement + { + const char* original; + const char* replacement; + }; + + struct TypeName + { + const char* name; + int size; + }; + + struct ShaderParameter + { + std::string type; + std::string variableName; + std::string identifierName; + void* defaultValue; + void* range [2]; + }; + + /** + * Types of shaders + */ + enum Type + { + Type_Vertex = 0, + Type_Pixel = 1, + }; + + /** + * List of variables to replace when pre-process is performed + */ + static std::map sVariableReplacement; + /** + * 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 + * required definitions if needed + * + * @param file The file to load + * @param type The type of shader + * @param recursive Whether the compiler should add base definitions or not + */ + Compiler (irr::io::path& file, Type type, std::map* combos, bool recursive = false); + /** + * 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 + * + * @return The shader contents ready to be used by OpenGL + */ + std::string precompile (); + /** + * Searches the list of parameters available for the parameter with the given value + * + * @param identifier The identifier to search for + * @return The shader information + */ + ShaderParameter* findParameter (std::string identifier); + + /** + * @return The list of parameters available for this shader with their default values + */ + std::vector & getParameters (); + + 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 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); + /** + * Searches for the given symbol in the replace table + * + * @param symbol The symbol to look for + * + * @return The symbol it should be replaced with + */ + std::string lookupReplaceSymbol (std::string symbol); + + /** + * @return Whether the character in the current position is a character or not + */ + bool isChar (std::string::const_iterator& it); + /** + * @return Whether the character in the current position is a number or not + */ + bool isNumeric (std::string::const_iterator& it); + /** + * Parses a COMBO value to add the proper define to the code + * + * @param content The parameter configuration + */ + void parseComboConfiguration (const std::string& content); + /** + * Parses a parameter extra metadata created by wallpaper engine + * + * @param type The type of variable to parse + * @param name The name of the variable in the shader (for actual variable declaration) + * @param content The parameter configuration + */ + void parseParameterConfiguration (const std::string& type, const std::string& name, const std::string& content); + /** + * The shader file this instance is loading + */ + irr::io::path m_file; + /** + * The original file content + */ + std::string m_content; + /** + * 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; + /** + * The parameters the shader needs + */ + std::vector m_parameters; + /** + * The combos the shader should be generated with + */ + std::map * m_combos; + /** + * Whether this compilation is a recursive one or not + */ + bool m_recursive; + }; +} diff --git a/WallpaperEngine/effect.cpp b/WallpaperEngine/effect.cpp index 8aaa7a8..309db7b 100644 --- a/WallpaperEngine/effect.cpp +++ b/WallpaperEngine/effect.cpp @@ -1,6 +1,6 @@ #include -#include "shaders/compiler.h" +#include "WallpaperEngine/Render/Shaders/Compiler.h" #include "effect.h" #include "WallpaperEngine/Irrlicht/Irrlicht.h" #include "WallpaperEngine/Core/Core.h" @@ -94,8 +94,8 @@ namespace WallpaperEngine irr::io::path fragpath = shaderpath + ".frag"; irr::io::path vertpath = shaderpath + ".vert"; - this->m_fragShader = new WallpaperEngine::shaders::compiler (fragpath, WallpaperEngine::shaders::compiler::Type::Type_Pixel, &this->m_combos, false); - this->m_vertShader = new WallpaperEngine::shaders::compiler (vertpath, WallpaperEngine::shaders::compiler::Type::Type_Vertex, &this->m_combos, false); + this->m_fragShader = new WallpaperEngine::Render::Shaders::Compiler (fragpath, WallpaperEngine::Render::Shaders::Compiler::Type::Type_Pixel, &this->m_combos, false); + this->m_vertShader = new WallpaperEngine::Render::Shaders::Compiler (vertpath, WallpaperEngine::Render::Shaders::Compiler::Type::Type_Vertex, &this->m_combos, false); this->m_materialType = WallpaperEngine::Irrlicht::driver->getGPUProgrammingServices () ->addHighLevelShaderMaterial ( @@ -111,8 +111,8 @@ namespace WallpaperEngine this->parseConstantValues ((*constantvalues)); // last step is creating the actual variables for the shaders - std::vector ::const_iterator cur; - std::vector ::const_iterator end; + std::vector ::const_iterator cur; + std::vector ::const_iterator end; cur = this->m_fragShader->getParameters ().begin (); end = this->m_fragShader->getParameters ().end (); @@ -120,7 +120,7 @@ namespace WallpaperEngine // first do fragment shaders for (; cur != end; cur ++) { - WallpaperEngine::shaders::compiler::ShaderParameter* param = (*cur); + WallpaperEngine::Render::Shaders::Compiler::ShaderParameter* param = (*cur); ShaderParameter* parameter = new ShaderParameter; void* defaultValue = param->defaultValue; @@ -203,7 +203,7 @@ namespace WallpaperEngine // second do vertex shaders for (;cur != end; cur ++) { - WallpaperEngine::shaders::compiler::ShaderParameter* param = (*cur); + WallpaperEngine::Render::Shaders::Compiler::ShaderParameter* param = (*cur); if (param == nullptr) continue; diff --git a/WallpaperEngine/effect.h b/WallpaperEngine/effect.h index bb87589..bd2c6d8 100644 --- a/WallpaperEngine/effect.h +++ b/WallpaperEngine/effect.h @@ -6,7 +6,7 @@ #include "texture.h" #include "object.h" -#include "shaders/compiler.h" +#include "WallpaperEngine/Render/Shaders/Compiler.h" namespace WallpaperEngine { @@ -42,8 +42,8 @@ namespace WallpaperEngine void parseConstantValues (json data); void parseCombos (json data); - WallpaperEngine::shaders::compiler* m_fragShader; - WallpaperEngine::shaders::compiler* m_vertShader; + WallpaperEngine::Render::Shaders::Compiler* m_fragShader; + WallpaperEngine::Render::Shaders::Compiler* m_vertShader; std::map m_vertexVariables; std::map m_pixelVariables; diff --git a/WallpaperEngine/shaders/compiler.cpp b/WallpaperEngine/shaders/compiler.cpp deleted file mode 100644 index c9bce31..0000000 --- a/WallpaperEngine/shaders/compiler.cpp +++ /dev/null @@ -1,638 +0,0 @@ -#include -#include -#include -#include - -// filesystem -#include - -// video engine -#include - -// shader compiler -#include -#include - -namespace WallpaperEngine -{ - namespace shaders - { - compiler::compiler (irr::io::path& file, Type type, std::map* combos, bool recursive) - { - this->m_recursive = recursive; - this->m_combos = combos; - - // begin with an space so it gets ignored properly on parse - if (recursive == false) - { - // compatibility layer for OpenGL shaders - this->m_content = "#version 120\n" - "#define highp\n" - "#define mediump\n" - "#define lowp\n" - "#define mul(x, y) (y * x)\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 texture2D\n" - "#define texSample2DLod texture2DLod\n" - "#define texture2DLod texture2D\n" - "#define atan2 atan\n" - "#define ddx dFdx\n" - "#define ddy(x) dFdy(-(x))\n" - "#define GLSL 1\n\n"; - - std::map::const_iterator cur = this->m_combos->begin (); - std::map::const_iterator end = this->m_combos->end (); - - for (; cur != end; cur ++) - { - this->m_content += "#define " + (*cur).first + " " + std::to_string ((*cur).second) + "\n"; - } - } - else - { - this->m_content = ""; - } - - this->m_content.append (WallpaperEngine::FileSystem::loadFullFile (file)); - - // append file content - this->m_type = type; - - this->m_file = file; - } - - 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 = "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) == false) it ++; - } - - std::string compiler::extractType (std::string::const_iterator& it) - { - std::vector::const_iterator cur = sTypes.begin (); - std::vector::const_iterator end = sTypes.end (); - - while (cur != end) - { - if (this->peekString (*cur, it) == true) - { - 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) == false && *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) == true || *cur == '_' || this->isNumeric (cur) == true)) cur ++; - - it = cur; - - return std::string (begin, cur); - } - - bool compiler::isChar (std::string::const_iterator& it) - { - return ((*it) >= 'A' && (*it) <= 'Z') || ((*it) >= 'a' && (*it) <= 'z'); - } - - bool compiler::isNumeric (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 = "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) - { - // get file information - irr::io::path shader = ("shaders/" + filename).c_str (); - - if (shader == "") - { - this->m_error = true; - this->m_errorInfo = "Cannot find file " + filename + " to include"; - return ""; - } - - // now compile the new shader - // do not include the default header (as it's already included in the parent) - compiler loader (shader, this->m_type, this->m_combos, true); - - return loader.precompile (); - } - - std::string compiler::lookupReplaceSymbol (std::string symbol) - { - std::map::const_iterator cur = sVariableReplacement.begin (); - std::map::const_iterator end = sVariableReplacement.end (); - - while (cur != end) - { - if (cur->first == symbol) - { - return cur->second; - } - - cur ++; - } - - // if there is no replacement, return the original - return symbol; - } - - std::string compiler::precompile() - { - #define BREAK_IF_ERROR if (this->m_error == true) { WallpaperEngine::Irrlicht::device->getLogger ()->log ("ERROR PRE-COMPILING SHADER", irr::ELL_ERROR); WallpaperEngine::Irrlicht::device->getLogger ()->log (this->m_errorInfo.c_str (), irr::ELL_ERROR); return ""; } - // 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 = ""; - - // search preprocessor macros and parse them - while (it != this->m_content.end () && this->m_error == false) - { - 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) == true) - { - std::string filename = ""; - - // ignore whitespaces - this->ignoreSpaces (it); BREAK_IF_ERROR - // extract value between quotes - filename = this->extractQuotedValue (it); BREAK_IF_ERROR - - // try to find the file first - this->m_compiledContent += "// begin of included 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 - { - this->m_compiledContent += '#'; - it ++; - } - } - else if (*it == 'u') - { - // uniforms might have extra information for their values - if (this->peekString ("uniform", it) == true) - { - 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); - this->expectSemicolon (it); BREAK_IF_ERROR - this->ignoreSpaces (it); - - // check if there is any actual extra information and parse it - if (this->peekString ("//", it) == true) - { - 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 " + type + " " + name + "; // " + configuration; - } - else - { - this->m_compiledContent += "uniform " + type + " " + name + ";"; - } - } - } - else if (*it == 'a') - { - // find attribute definitions - if (this->peekString ("attribute", it) == true) - { - 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); - this->expectSemicolon (it); BREAK_IF_ERROR - - this->m_compiledContent += "// attribute"; - this->m_compiledContent += " " + type + " "; - this->m_compiledContent += name; - this->m_compiledContent += "; /* replaced by " + this->lookupReplaceSymbol (name) + " */"; - } - else - { - // check for types first - std::string type = this->extractType (it); - - // types not found, try names - if (this->m_error == false) - { - this->m_compiledContent += type; - } - else - { - this->m_error = false; - std::string name = this->extractName (it); - - if (this->m_error == false) - { - // check if the name is a translated one or not - this->m_compiledContent += this->lookupReplaceSymbol (name); - } - else - { - this->m_error = false; - this->m_compiledContent += *it; - it ++; - } - } - } - } - else if (*it == '/') - { - if (this->peekString ("//", it) == true) - { - std::string::const_iterator begin = it - 2; - // is there a COMBO mark to take care of? - this->ignoreSpaces (it); - - if (this->peekString ("[COMBO]", it) == true) - { - // 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); BREAK_IF_ERROR; - } - else if (this->peekString ("[COMBO_OFF]", it) == true) - { - // 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); BREAK_IF_ERROR; - } - else - { - this->ignoreUpToNextLineFeed (it); - this->m_compiledContent.append (begin, it); - } - } - else if (this->peekString ("/*", it) == true) - { - 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); - - // types not found, try names - if (this->m_error == false) - { - this->m_compiledContent += type; - } - else - { - this->m_error = false; - std::string name = this->extractName (it); - - if (this->m_error == false) - { - // check if the name is a translated one or not - this->m_compiledContent += this->lookupReplaceSymbol (name); - } - else - { - this->m_error = false; - this->m_compiledContent += *it; - it ++; - } - } - } - } - - if (this->m_recursive == false) - { - WallpaperEngine::Irrlicht::device->getLogger ()->log ("Compiled shader output for", this->m_file.c_str ()); - WallpaperEngine::Irrlicht::device->getLogger ()->log (this->m_compiledContent.c_str ()); - } - - return this->m_compiledContent; - #undef BREAK_IF_ERROR - } - - void compiler::parseComboConfiguration (const std::string& content) - { - json data = json::parse (content); - json::const_iterator combo = data.find ("combo"); - json::const_iterator defvalue = data.find ("default"); - - // add line feed just in case - this->m_compiledContent += "\n"; - - if (combo == data.end () || defvalue == data.end ()) - { - WallpaperEngine::Irrlicht::device->getLogger ()->log ("Cannot parse combo information", irr::ELL_ERROR); - return; - } - - // check the combos - std::map::const_iterator entry = this->m_combos->find ((*combo).get ()); - - // 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 no combo is defined just load the default settings - if ((*defvalue).is_number_float ()) - { - this->m_compiledContent += "#define " + (*combo).get () + " " + std::to_string ((*defvalue).get ()) + "\n"; - } - else if ((*defvalue).is_number_integer ()) - { - this->m_compiledContent += "#define " + (*combo).get () + " " + std::to_string ((*defvalue).get ()) + "\n"; - } - else if ((*defvalue).is_string ()) - { - this->m_compiledContent += "#define " + (*combo).get () + " " + (*defvalue).get () + "\n"; - } - else - { - WallpaperEngine::Irrlicht::device->getLogger ()->log ("Cannot parse combo information, unknown type", irr::ELL_ERROR); - } - } - } - - void compiler::parseParameterConfiguration (const std::string& type, const std::string& name, const std::string& content) - { - json data = json::parse (content); - json::const_iterator material = data.find ("material"); - json::const_iterator defvalue = data.find ("default"); - json::const_iterator range = data.find ("range"); - - // this is not a real parameter - if (material == data.end () || defvalue == data.end ()) - { - if (type != "sampler2D") - WallpaperEngine::Irrlicht::device->getLogger ()->log ("Cannot parse parameter info for ", name.c_str (), irr::ELL_ERROR); - - return; - } - - ShaderParameter* param = new ShaderParameter; - - param->identifierName = (*material).get (); - param->variableName = name; - param->type = type; - - if (type == "vec4" || type == "vec3") - { - if ((*defvalue).is_string () == false) - { - irr::core::vector3df* vector = new irr::core::vector3df; - - vector->X = 0.0f; - vector->Y = 0.0f; - vector->Z = 0.0f; - - param->defaultValue = vector; - } - else - { - irr::core::vector3df tmp = WallpaperEngine::Core::ato3vf ((*defvalue).get ().c_str ()); - irr::core::vector3df* vector = new irr::core::vector3df; - - vector->X = tmp.X; - vector->Y = tmp.Y; - vector->Z = tmp.Z; - - param->defaultValue = vector; - } - } - else if (type == "vec2") - { - if ((*defvalue).is_string () == false) - { - irr::core::vector2df* vector = new irr::core::vector2df; - - vector->X = 0.0f; - vector->Y = 0.0f; - - param->defaultValue = vector; - } - else - { - irr::core::vector2df* vector = new irr::core::vector2df; - irr::core::vector2df tmp = WallpaperEngine::Core::ato2vf ((*defvalue).get ().c_str ()); - - vector->X = tmp.X; - vector->Y = tmp.Y; - - param->defaultValue = vector; - } - } - else if (type == "float") - { - if ((*defvalue).is_number () == false) - { - irr::f32* val = new irr::f32; - - *val = 0.0f; - - param->defaultValue = val; - - } - else - { - irr::f32* val = new irr::f32; - - *val = (*defvalue).get (); - - param->defaultValue = val; - } - } - else if (type == "sampler2D") - { - // samplers are not saved, we can ignore them for now - delete param; - return; - } - else - { - this->m_error = true; - this->m_errorInfo = "Unknown parameter type: " + type + " for " + param->identifierName + " (" + param->variableName + ")"; - return; - } - - this->m_parameters.push_back (param); - } - - compiler::ShaderParameter* compiler::findParameter (std::string identifier) - { - std::vector::const_iterator cur = this->m_parameters.begin (); - std::vector::const_iterator end = this->m_parameters.end (); - - for (; cur != end; cur ++) - { - if ((*cur)->identifierName == identifier) - { - return (*cur); - } - } - - return nullptr; - } - - std::vector & compiler::getParameters () - { - return this->m_parameters; - } - - std::map compiler::sVariableReplacement = - { - // attribute vec3 a_position - {"a_Position", "gl_Vertex.xyz"}, - // attribute vec2 a_TexCoord - {"a_TexCoord", "gl_MultiTexCoord0.xy"}, - // attribute vec3 a_Normal - {"a_Normal", "gl_Normal.xyz"} - }; - - std::vector compiler::sTypes = - { - "vec4", "vec3", "vec2", "float", "sampler2D", "mat4" - }; - } -} diff --git a/WallpaperEngine/shaders/compiler.h b/WallpaperEngine/shaders/compiler.h deleted file mode 100644 index 24577b4..0000000 --- a/WallpaperEngine/shaders/compiler.h +++ /dev/null @@ -1,246 +0,0 @@ -#ifndef __BASIC_SHADER_LOADER_H__ -#define __BASIC_SHADER_LOADER_H__ - -#include -#include -#include -#include -#include - -#include - -namespace WallpaperEngine -{ - namespace shaders - { - using json = nlohmann::json; - - /** - * A basic shader loader that adds basic function definitions to every loaded shader - */ - class compiler - { - public: - /** - * Basic struct used to define all the shader variables - * the compiler will replace in pre-processing time - * to make sure the shaders compile under OpenGL - */ - struct VariableReplacement - { - const char* original; - const char* replacement; - }; - - struct TypeName - { - const char* name; - int size; - }; - - struct ShaderParameter - { - std::string type; - std::string variableName; - std::string identifierName; - void* defaultValue; - void* range [2]; - }; - - /** - * Types of shaders - */ - enum Type - { - Type_Vertex = 0, - Type_Pixel = 1, - }; - - /** - * List of variables to replace when pre-process is performed - */ - static std::map sVariableReplacement; - /** - * 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 - * required definitions if needed - * - * @param file The file to load - * @param type The type of shader - * @param recursive Whether the compiler should add base definitions or not - */ - compiler (irr::io::path& file, Type type, std::map* combos, bool recursive = false); - /** - * 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 - * - * @return The shader contents ready to be used by OpenGL - */ - std::string precompile (); - /** - * Searches the list of parameters available for the parameter with the given value - * - * @param identifier The identifier to search for - * @return The shader information - */ - ShaderParameter* findParameter (std::string identifier); - - /** - * @return The list of parameters available for this shader with their default values - */ - std::vector & getParameters (); - - 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 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); - /** - * Searches for the given symbol in the replace table - * - * @param symbol The symbol to look for - * - * @return The symbol it should be replaced with - */ - std::string lookupReplaceSymbol (std::string symbol); - - /** - * @return Whether the character in the current position is a character or not - */ - bool isChar (std::string::const_iterator& it); - /** - * @return Whether the character in the current position is a number or not - */ - bool isNumeric (std::string::const_iterator& it); - /** - * Parses a COMBO value to add the proper define to the code - * - * @param content The parameter configuration - */ - void parseComboConfiguration (const std::string& content); - /** - * Parses a parameter extra metadata created by wallpaper engine - * - * @param type The type of variable to parse - * @param name The name of the variable in the shader (for actual variable declaration) - * @param content The parameter configuration - */ - void parseParameterConfiguration (const std::string& type, const std::string& name, const std::string& content); - /** - * The shader file this instance is loading - */ - irr::io::path m_file; - /** - * The original file content - */ - std::string m_content; - /** - * 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; - /** - * The parameters the shader needs - */ - std::vector m_parameters; - /** - * The combos the shader should be generated with - */ - std::map * m_combos; - /** - * Whether this compilation is a recursive one or not - */ - bool m_recursive; - }; - } -} - -#endif /* !__BASIC_SHADER_LOADER_H__ */ \ No newline at end of file diff --git a/main.cpp b/main.cpp index 0c14985..acc34ca 100644 --- a/main.cpp +++ b/main.cpp @@ -8,7 +8,7 @@ #include #include -#include "WallpaperEngine/shaders/compiler.h" +#include "WallpaperEngine/Render/Shaders/Compiler.h" #include "WallpaperEngine/project.h" #include "WallpaperEngine/Irrlicht/Irrlicht.h" #include "WallpaperEngine/Irrlicht/CImageLoaderTEX.h"