diff --git a/src/coders/BasicParser.hpp b/src/coders/BasicParser.hpp index ba1cf5a8..e0a4445a 100644 --- a/src/coders/BasicParser.hpp +++ b/src/coders/BasicParser.hpp @@ -8,7 +8,9 @@ class BasicParser { using StringT = std::basic_string; using StringViewT = std::basic_string_view; + void skipWhitespaceBasic(bool newline = true); void skipWhitespaceHashComment(bool newline = true); + void skipWhitespaceCLikeComment(bool newline = true); protected: std::string_view filename; StringViewT source; @@ -16,6 +18,7 @@ protected: uint line = 1; uint linestart = 0; bool hashComment = false; + bool clikeComment = false; void skipWhitespace(bool newline = true); void skip(size_t n); @@ -35,7 +38,7 @@ protected: StringT parseString(CharT chr, bool closeRequired = true); parsing_error error(const std::string& message); -public: + StringViewT readUntil(CharT c); StringViewT readUntil(StringViewT s, bool nothrow); StringViewT readUntilWhitespace(); diff --git a/src/coders/BasicParser.inl b/src/coders/BasicParser.inl index 3616b4a1..7ed9c10d 100644 --- a/src/coders/BasicParser.inl +++ b/src/coders/BasicParser.inl @@ -31,13 +31,9 @@ namespace { } template -void BasicParser::skipWhitespace(bool newline) { - if (hashComment) { - skipWhitespaceHashComment(newline); - return; - } +void BasicParser::skipWhitespaceBasic(bool newline) { while (hasNext()) { - char next = source[pos]; + CharT next = source[pos]; if (next == '\n') { if (!newline) { break; @@ -55,23 +51,20 @@ void BasicParser::skipWhitespace(bool newline) { } template -void BasicParser::skipWhitespaceHashComment(bool newline) { - while (hasNext()) { - char next = source[pos]; - if (next == '\n') { - if (!newline) { - break; - } - line++; - linestart = ++pos; - continue; - } - if (is_whitespace(next)) { - pos++; - } else { - break; - } +void BasicParser::skipWhitespace(bool newline) { + if (hashComment) { + skipWhitespaceHashComment(newline); + return; + } else if (clikeComment) { + skipWhitespaceCLikeComment(newline); + return; } + skipWhitespaceBasic(newline); +} + +template +void BasicParser::skipWhitespaceHashComment(bool newline) { + skipWhitespaceBasic(newline); if (hasNext() && source[pos] == '#') { if (!newline) { readUntilEOL(); @@ -84,6 +77,39 @@ void BasicParser::skipWhitespaceHashComment(bool newline) { } } +template +void BasicParser::skipWhitespaceCLikeComment(bool newline) { + skipWhitespaceBasic(newline); + if (hasNext() && source[pos] == '/' && pos + 1 < source.length()) { + pos++; + switch (source[pos]) { + case '*': + pos++; + while (hasNext()) { + if (source[pos] == '/' && source[pos-1] == '*') { + pos++; + skipWhitespace(); + return; + } + pos++; + } + break; + case '/': + if (!newline) { + readUntilEOL(); + return; + } + skipLine(); + if (hasNext() && (is_whitespace(source[pos]) || source[pos] == '/')) { + skipWhitespaceCLikeComment(newline); + } + default: + pos--; + break; + } + } +} + template void BasicParser::skip(size_t n) { n = std::min(n, source.length() - pos); diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index b36e7a07..fcacc372 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -1,23 +1,28 @@ #include "GLSLExtension.hpp" -#include #include #include #include +#include "debug/Logger.hpp" #include "io/engine_paths.hpp" #include "typedefs.hpp" #include "util/stringutil.hpp" +#include "coders/BasicParser.hpp" +#include "graphics/core/PostEffect.hpp" -void GLSLExtension::setVersion(std::string version) { - this->version = std::move(version); -} +static debug::Logger logger("glsl-extension"); + +using Type = PostEffect::Param::Type; void GLSLExtension::setPaths(const ResPaths* paths) { this->paths = paths; } void GLSLExtension::loadHeader(const std::string& name) { + if (paths == nullptr) { + return; + } io::path file = paths->find("shaders/lib/" + name + ".glsl"); std::string source = io::read_string(file); addHeader(name, ""); @@ -48,6 +53,10 @@ const std::string& GLSLExtension::getDefine(const std::string& name) const { return found->second; } +const std::unordered_map& GLSLExtension::getDefines() const { + return defines; +} + bool GLSLExtension::hasDefine(const std::string& name) const { return defines.find(name) != defines.end(); } @@ -72,76 +81,125 @@ inline std::runtime_error parsing_error( } inline void parsing_warning( - const io::path& file, uint linenum, const std::string& message + std::string_view file, uint linenum, const std::string& message ) { - std::cerr << "file " + file.string() + ": warning: " + message + - " at line " + std::to_string(linenum) - << std::endl; + logger.warning() << "file " + std::string(file) + ": warning: " + message + + " at line " + std::to_string(linenum); } inline void source_line(std::stringstream& ss, uint linenum) { ss << "#line " << linenum << "\n"; } +static std::optional param_type_from( + const std::string& name +) { + static const std::unordered_map typeNames { + {"float", Type::FLOAT}, + {"vec2", Type::VEC2}, + {"vec3", Type::VEC3}, + {"vec4", Type::VEC4}, + }; + const auto& found = typeNames.find(name); + if (found == typeNames.end()) { + return std::nullopt; + } + return found->second; +} + +class GLSLParser : public BasicParser { +public: + GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header) + : BasicParser(file, source), glsl(glsl) { + if (!header) { + ss << "#version " << GLSLExtension::VERSION << '\n'; + } + for (auto& entry : glsl.getDefines()) { + ss << "#define " << entry.first << " " << entry.second << '\n'; + } + uint linenum = 1; + source_line(ss, linenum); + + clikeComment = true; + } + + bool processPreprocessorDirective() { + skip(1); + + auto name = parseName(); + + if (name == "version") { + parsing_warning(filename, line, "removed #version directive"); + source_line(ss, line); + skipLine(); + return false; + } else if (name == "include") { + skipWhitespace(false); + if (peekNoJump() != '<') { + throw error("'<' expected"); + } + skip(1); + skipWhitespace(false); + auto headerName = parseName(); + skipWhitespace(false); + if (peekNoJump() != '>') { + throw error("'>' expected"); + } + skip(1); + skipWhitespace(false); + skipLine(); + + if (!glsl.hasHeader(headerName)) { + glsl.loadHeader(headerName); + } + ss << glsl.getHeader(headerName) << '\n'; + source_line(ss, line); + return false; + } else if (name == "param") { + skipWhitespace(false); + auto typeName = parseName(); + auto type = param_type_from(typeName); + if (!type.has_value()) { + throw error("unsupported param type " + util::quote(typeName)); + } + skipWhitespace(false); + auto paramName = parseName(); + if (params.find(paramName) != params.end()) { + throw error("duplicating param " + util::quote(paramName)); + } + skipLine(); + + ss << "uniform " << typeName << " " << paramName << ";\n"; + params[paramName] = PostEffect::Param(type.value()); + return false; + } + return true; + } + + std::string process() { + while (hasNext()) { + skipWhitespace(false); + if (!hasNext()) { + break; + } + if (source[pos] != '#' || processPreprocessorDirective()) { + pos = linestart; + ss << readUntilEOL() << '\n'; + skip(1); + } + } + return ss.str(); + } +private: + GLSLExtension& glsl; + std::unordered_map params; + std::stringstream ss; +}; + std::string GLSLExtension::process( const io::path& file, const std::string& source, bool header ) { - std::stringstream ss; - size_t pos = 0; - uint linenum = 1; - if (!header) { - ss << "#version " << version << '\n'; - } - for (auto& entry : defines) { - ss << "#define " << entry.first << " " << entry.second << '\n'; - } - source_line(ss, linenum); - while (pos < source.length()) { - size_t endline = source.find('\n', pos); - if (endline == std::string::npos) { - endline = source.length(); - } - // parsing preprocessor directives - if (source[pos] == '#') { - std::string line = source.substr(pos + 1, endline - pos); - util::trim(line); - // parsing 'include' directive - if (line.find("include") != std::string::npos) { - line = line.substr(7); - util::trim(line); - if (line.length() < 3) { - throw parsing_error( - file, linenum, "invalid 'include' syntax" - ); - } - if (line[0] != '<' || line[line.length() - 1] != '>') { - throw parsing_error( - file, linenum, "expected '#include ' syntax" - ); - } - std::string name = line.substr(1, line.length() - 2); - if (!hasHeader(name)) { - loadHeader(name); - } - source_line(ss, 1); - ss << getHeader(name) << '\n'; - pos = endline + 1; - linenum++; - source_line(ss, linenum); - continue; - } - // removing extra 'include' directives - else if (line.find("version") != std::string::npos) { - parsing_warning(file, linenum, "removed #version directive"); - pos = endline + 1; - linenum++; - source_line(ss, linenum); - continue; - } - } - linenum++; - ss << source.substr(pos, endline + 1 - pos); - pos = endline + 1; - } - return ss.str(); + std::string filename = file.string(); + GLSLParser parser(*this, filename, source, header); + return parser.process(); } diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index c619c9d0..63e15303 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -9,15 +9,8 @@ class ResPaths; class GLSLExtension { - std::unordered_map headers; - std::unordered_map defines; - std::string version = "330 core"; - - const ResPaths* paths = nullptr; - void loadHeader(const std::string& name); public: void setPaths(const ResPaths* paths); - void setVersion(std::string version); void define(const std::string& name, std::string value); void undefine(const std::string& name); @@ -26,12 +19,22 @@ public: const std::string& getHeader(const std::string& name) const; const std::string& getDefine(const std::string& name) const; + const std::unordered_map& getDefines() const; + bool hasHeader(const std::string& name) const; bool hasDefine(const std::string& name) const; + void loadHeader(const std::string& name); std::string process( const io::path& file, const std::string& source, bool header = false ); + + static inline std::string VERSION = "330 core"; +private: + std::unordered_map headers; + std::unordered_map defines; + + const ResPaths* paths = nullptr; }; diff --git a/src/coders/toml.cpp b/src/coders/toml.cpp index 1652745a..1a249435 100644 --- a/src/coders/toml.cpp +++ b/src/coders/toml.cpp @@ -16,7 +16,7 @@ using namespace toml; class TomlReader : BasicParser { dv::value root; - // modified version of BaseParser.parseString + // modified version of BasicParser.parseString // todo: extract common part std::string parseMultilineString() { pos += 2; diff --git a/src/graphics/core/PostEffect.cpp b/src/graphics/core/PostEffect.cpp new file mode 100644 index 00000000..5f8b41fc --- /dev/null +++ b/src/graphics/core/PostEffect.cpp @@ -0,0 +1,15 @@ +#include "PostEffect.hpp" + +#include "Shader.hpp" + +PostEffect::Param::Param() : type(Type::FLOAT) {} + +PostEffect::Param::Param(Type type) : type(type) {} + +PostEffect::PostEffect(std::unique_ptr shader) + : shader(std::move(shader)) { +} + +void PostEffect::use() { + shader->use(); +} diff --git a/src/graphics/core/PostEffect.hpp b/src/graphics/core/PostEffect.hpp new file mode 100644 index 00000000..78afda26 --- /dev/null +++ b/src/graphics/core/PostEffect.hpp @@ -0,0 +1,29 @@ +#pragma once + +#include +#include +#include +#include +#include + +class Shader; + +class PostEffect { +public: + struct Param { + enum class Type { FLOAT, VEC2, VEC3, VEC4 }; + using Value = std::variant; + + Type type; + + Param(); + Param(Type type); + }; + + PostEffect(std::unique_ptr shader); + + void use(); +private: + std::unique_ptr shader; + std::unordered_map params; +}; diff --git a/test/coders/GLSLExtension.cpp b/test/coders/GLSLExtension.cpp new file mode 100644 index 00000000..ff26ebdf --- /dev/null +++ b/test/coders/GLSLExtension.cpp @@ -0,0 +1,32 @@ +#include + +#include "coders/commons.hpp" +#include "coders/GLSLExtension.hpp" + +TEST(GLSLExtension, processing) { + GLSLExtension glsl; + glsl.addHeader("sum", + "// sum function for glsl\n" + "float sum(float a, float b) {\n" + " return a + b;\n" + "}\n" + ); + try { + auto processed = glsl.process("test.glsl", + "in vec2 v_uv;\n" + "uniform sampler2D u_screen;\n" + "\n" + "#include /* hell\no */ < sum >\n" + "#param float p_intensity\n" + "\n" + "vec4 effect() {\n" + " vec4 color = texture(u_screen, v_uv);\n" + " return mix(color, 1.0 - color, p_intensity);\n" + "}\n", + false); + std::cout << processed << std::endl; + } catch (const parsing_error& err) { + std::cerr << err.errorLog() << std::endl; + throw; + } +}