From 1feee3a809134651a605cc8a4f835cdfda98eea4 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 3 Apr 2025 21:46:12 +0300 Subject: [PATCH 1/9] refactor GLSLExtension.cpp & add 'param' shader preprocessor directive & add PostEffect class (WIP) --- src/coders/BasicParser.hpp | 5 +- src/coders/BasicParser.inl | 70 ++++++++---- src/coders/GLSLExtension.cpp | 190 ++++++++++++++++++++----------- src/coders/GLSLExtension.hpp | 17 +-- src/coders/toml.cpp | 2 +- src/graphics/core/PostEffect.cpp | 15 +++ src/graphics/core/PostEffect.hpp | 29 +++++ test/coders/GLSLExtension.cpp | 32 ++++++ 8 files changed, 263 insertions(+), 97 deletions(-) create mode 100644 src/graphics/core/PostEffect.cpp create mode 100644 src/graphics/core/PostEffect.hpp create mode 100644 test/coders/GLSLExtension.cpp 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; + } +} From 6043ae8331f233974ebbd0d39bdcd551bb8287d1 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Thu, 3 Apr 2025 21:57:46 +0300 Subject: [PATCH 2/9] refactor GLSLExtension --- src/assets/assetload_funcs.cpp | 7 ++- src/coders/GLSLExtension.cpp | 105 ++++++++++++++++++--------------- src/coders/GLSLExtension.hpp | 8 ++- test/coders/GLSLExtension.cpp | 2 +- 4 files changed, 71 insertions(+), 51 deletions(-) diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 01c251b4..7558bbdc 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -73,9 +73,10 @@ assetload::postfunc assetload::shader( std::string vertexSource = io::read_string(vertexFile); std::string fragmentSource = io::read_string(fragmentFile); - vertexSource = Shader::preprocessor->process(vertexFile, vertexSource); - fragmentSource = - Shader::preprocessor->process(fragmentFile, fragmentSource); + auto& preprocessor = *Shader::preprocessor; + + vertexSource = preprocessor.process(vertexFile, vertexSource).code; + fragmentSource = preprocessor.process(fragmentFile, fragmentSource).code; return [=](auto assets) { assets->store( diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index fcacc372..76f2394a 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -9,7 +9,6 @@ #include "typedefs.hpp" #include "util/stringutil.hpp" #include "coders/BasicParser.hpp" -#include "graphics/core/PostEffect.hpp" static debug::Logger logger("glsl-extension"); @@ -26,7 +25,9 @@ void GLSLExtension::loadHeader(const std::string& name) { io::path file = paths->find("shaders/lib/" + name + ".glsl"); std::string source = io::read_string(file); addHeader(name, ""); - addHeader(name, process(file, source, true)); + + auto [code, _] = process(file, source, true); + addHeader(name, std::move(code)); } void GLSLExtension::addHeader(const std::string& name, std::string source) { @@ -123,60 +124,72 @@ public: clikeComment = true; } + bool processIncludeDirective() { + 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; + } + + bool processVersionDirective() { + parsing_warning(filename, line, "removed #version directive"); + source_line(ss, line); + skipLine(); + return false; + } + + bool processParamDirective() { + 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; + } + bool processPreprocessorDirective() { skip(1); auto name = parseName(); if (name == "version") { - parsing_warning(filename, line, "removed #version directive"); - source_line(ss, line); - skipLine(); - return false; + return processVersionDirective(); } 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; + return processIncludeDirective(); } 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 processParamDirective(); } return true; } - std::string process() { + GLSLExtension::ProcessingResult process() { while (hasNext()) { skipWhitespace(false); if (!hasNext()) { @@ -188,7 +201,7 @@ public: skip(1); } } - return ss.str(); + return {ss.str(), std::move(params)}; } private: GLSLExtension& glsl; @@ -196,7 +209,7 @@ private: std::stringstream ss; }; -std::string GLSLExtension::process( +GLSLExtension::ProcessingResult GLSLExtension::process( const io::path& file, const std::string& source, bool header ) { std::string filename = file.string(); diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index 63e15303..e78f06d7 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -5,6 +5,7 @@ #include #include "io/io.hpp" +#include "graphics/core/PostEffect.hpp" class ResPaths; @@ -25,7 +26,12 @@ public: bool hasDefine(const std::string& name) const; void loadHeader(const std::string& name); - std::string process( + struct ProcessingResult { + std::string code; + std::unordered_map params; + }; + + ProcessingResult process( const io::path& file, const std::string& source, bool header = false diff --git a/test/coders/GLSLExtension.cpp b/test/coders/GLSLExtension.cpp index ff26ebdf..8ad71898 100644 --- a/test/coders/GLSLExtension.cpp +++ b/test/coders/GLSLExtension.cpp @@ -24,7 +24,7 @@ TEST(GLSLExtension, processing) { " return mix(color, 1.0 - color, p_intensity);\n" "}\n", false); - std::cout << processed << std::endl; + std::cout << processed.code << std::endl; } catch (const parsing_error& err) { std::cerr << err.errorLog() << std::endl; throw; From 70cf30877156502377ca224bc9b49322f256fc71 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 4 Apr 2025 13:19:25 +0300 Subject: [PATCH 3/9] add '#param' default value syntax --- src/coders/GLSLExtension.cpp | 72 ++++++++++++++++++++++++++++++-- src/graphics/core/PostEffect.cpp | 4 +- src/graphics/core/PostEffect.hpp | 3 +- test/coders/GLSLExtension.cpp | 1 + 4 files changed, 74 insertions(+), 6 deletions(-) diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index 76f2394a..f205b9b4 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -8,11 +8,14 @@ #include "io/engine_paths.hpp" #include "typedefs.hpp" #include "util/stringutil.hpp" +#include "coders/json.hpp" +#include "data/dv_util.hpp" #include "coders/BasicParser.hpp" static debug::Logger logger("glsl-extension"); using Type = PostEffect::Param::Type; +using Value = PostEffect::Param::Value; void GLSLExtension::setPaths(const ResPaths* paths) { this->paths = paths; @@ -92,10 +95,10 @@ inline void source_line(std::stringstream& ss, uint linenum) { ss << "#line " << linenum << "\n"; } -static std::optional param_type_from( +static std::optional param_type_from( const std::string& name ) { - static const std::unordered_map typeNames { + static const std::unordered_map typeNames { {"float", Type::FLOAT}, {"vec2", Type::VEC2}, {"vec3", Type::VEC3}, @@ -108,6 +111,21 @@ static std::optional param_type_from( return found->second; } +static Value default_value_for(Type type) { + switch (type) { + case Type::FLOAT: + return 0.0f; + case Type::VEC2: + return glm::vec2 {0.0f, 0.0f}; + case Type::VEC3: + return glm::vec3 {0.0f, 0.0f, 0.0f}; + case Type::VEC4: + return glm::vec4 {0.0f, 0.0f, 0.0f, 0.0f}; + default: + throw std::runtime_error("unsupported type"); + } +} + class GLSLParser : public BasicParser { public: GLSLParser(GLSLExtension& glsl, std::string_view file, std::string_view source, bool header) @@ -155,22 +173,68 @@ public: return false; } + template + Value parseVectorValue() { + if (peekNoJump() != '[') { + throw error("'[' expected"); + } + // may be more efficient but ok + auto value = json::parse( + filename, + std::string_view(source.data() + pos, source.size() - pos) + ); + glm::vec vec {}; + try { + dv::get_vec(value, vec); + return vec; + } catch (const std::exception& err) { + throw error(err.what()); + } + } + + Value parseDefaultValue(Type type, const std::string& name) { + switch (type) { + case Type::FLOAT: + return static_cast(parseNumber(1).asNumber()); + case Type::VEC2: + return parseVectorValue<2>(); + case Type::VEC3: + return parseVectorValue<3>(); + case Type::VEC4: + return parseVectorValue<4>(); + default: + throw error("unsupported default value for type " + name); + } + } + bool processParamDirective() { skipWhitespace(false); + // Parse type name auto typeName = parseName(); auto type = param_type_from(typeName); if (!type.has_value()) { throw error("unsupported param type " + util::quote(typeName)); } skipWhitespace(false); + // Parse parameter name auto paramName = parseName(); if (params.find(paramName) != params.end()) { throw error("duplicating param " + util::quote(paramName)); } + skipWhitespace(false); + ss << "uniform " << typeName << " " << paramName << ";\n"; + + auto defValue = default_value_for(type.value()); + // Parse default value + if (peekNoJump() == '=') { + skip(1); + skipWhitespace(false); + defValue = parseDefaultValue(type.value(), typeName); + } + skipLine(); - ss << "uniform " << typeName << " " << paramName << ";\n"; - params[paramName] = PostEffect::Param(type.value()); + params[paramName] = PostEffect::Param(type.value(), std::move(defValue)); return false; } diff --git a/src/graphics/core/PostEffect.cpp b/src/graphics/core/PostEffect.cpp index 5f8b41fc..6af987c5 100644 --- a/src/graphics/core/PostEffect.cpp +++ b/src/graphics/core/PostEffect.cpp @@ -4,7 +4,9 @@ PostEffect::Param::Param() : type(Type::FLOAT) {} -PostEffect::Param::Param(Type type) : type(type) {} +PostEffect::Param::Param(Type type, Value defValue) + : type(type), defValue(std::move(defValue)) { +} PostEffect::PostEffect(std::unique_ptr shader) : shader(std::move(shader)) { diff --git a/src/graphics/core/PostEffect.hpp b/src/graphics/core/PostEffect.hpp index 78afda26..f77e121f 100644 --- a/src/graphics/core/PostEffect.hpp +++ b/src/graphics/core/PostEffect.hpp @@ -15,9 +15,10 @@ public: using Value = std::variant; Type type; + Value defValue; Param(); - Param(Type type); + Param(Type type, Value defValue); }; PostEffect(std::unique_ptr shader); diff --git a/test/coders/GLSLExtension.cpp b/test/coders/GLSLExtension.cpp index 8ad71898..87690ad6 100644 --- a/test/coders/GLSLExtension.cpp +++ b/test/coders/GLSLExtension.cpp @@ -18,6 +18,7 @@ TEST(GLSLExtension, processing) { "\n" "#include /* hell\no */ < sum >\n" "#param float p_intensity\n" + "#param vec3 p_pos = [0, 0, 0]\n" "\n" "vec4 effect() {\n" " vec4 color = texture(u_screen, v_uv);\n" From 22e97b176646fcba915230d5fa53758738e24895 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Fri, 4 Apr 2025 21:35:16 +0300 Subject: [PATCH 4/9] remove garbage includes from WorldRenderer.hpp --- src/graphics/render/WorldRenderer.hpp | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/graphics/render/WorldRenderer.hpp b/src/graphics/render/WorldRenderer.hpp index f3189fb0..fbcf5480 100644 --- a/src/graphics/render/WorldRenderer.hpp +++ b/src/graphics/render/WorldRenderer.hpp @@ -3,11 +3,8 @@ #include #include #include -#include #include -#include - #include "typedefs.hpp" #include "presets/WeatherPreset.hpp" From ba170035ef24b970eb34dddacf61ccaf92f7326a Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 5 Apr 2025 00:41:25 +0300 Subject: [PATCH 5/9] feat: post-processing effects --- res/preload.json | 4 +- res/shaders/effect.glslf | 13 +++++ res/shaders/{screen.glslv => effect.glslv} | 7 +-- res/shaders/effects/default.glsl | 3 ++ res/shaders/screen.glslf | 25 --------- src/assets/Assets.hpp | 25 ++++++++- src/assets/AssetsLoader.cpp | 4 ++ src/assets/assetload_funcs.cpp | 63 ++++++++++++++++++---- src/assets/assetload_funcs.hpp | 7 +++ src/coders/GLSLExtension.cpp | 20 ++++--- src/coders/GLSLExtension.hpp | 20 +++---- src/constants.hpp | 1 + src/graphics/core/PostEffect.cpp | 15 ++++-- src/graphics/core/PostEffect.hpp | 14 ++++- src/graphics/core/PostProcessing.cpp | 50 ++++++++++++++--- src/graphics/core/PostProcessing.hpp | 9 ++-- src/graphics/render/WorldRenderer.cpp | 7 +-- src/typedefs.hpp | 2 +- test/coders/GLSLExtension.cpp | 11 ++-- 19 files changed, 216 insertions(+), 84 deletions(-) create mode 100644 res/shaders/effect.glslf rename res/shaders/{screen.glslv => effect.glslv} (56%) create mode 100644 res/shaders/effects/default.glsl delete mode 100644 res/shaders/screen.glslf diff --git a/res/preload.json b/res/preload.json index 1cfd0747..cac23425 100644 --- a/res/preload.json +++ b/res/preload.json @@ -5,10 +5,12 @@ "main", "lines", "entity", - "screen", "background", "skybox_gen" ], + "post-effects": [ + "default" + ], "textures": [ "gui/menubg", "gui/delete_icon", diff --git a/res/shaders/effect.glslf b/res/shaders/effect.glslf new file mode 100644 index 00000000..a6325cca --- /dev/null +++ b/res/shaders/effect.glslf @@ -0,0 +1,13 @@ +in vec2 v_uv; +out vec4 f_color; + +uniform sampler2D u_screen; +uniform ivec2 u_screenSize; +uniform float u_intensity; + +#include <__effect__> + +void main() { + f_color = effect(); +} + diff --git a/res/shaders/screen.glslv b/res/shaders/effect.glslv similarity index 56% rename from res/shaders/screen.glslv rename to res/shaders/effect.glslv index 593e5f19..97ac08a7 100644 --- a/res/shaders/screen.glslv +++ b/res/shaders/effect.glslv @@ -1,13 +1,10 @@ layout (location = 0) in vec2 v_position; -out vec2 v_coord; +out vec2 v_uv; uniform ivec2 u_screenSize; -uniform float u_timer; -uniform float u_dayTime; void main(){ - v_coord = v_position * 0.5 + 0.5; + v_uv = v_position * 0.5 + 0.5; gl_Position = vec4(v_position, 0.0, 1.0); } - diff --git a/res/shaders/effects/default.glsl b/res/shaders/effects/default.glsl new file mode 100644 index 00000000..c2f9effa --- /dev/null +++ b/res/shaders/effects/default.glsl @@ -0,0 +1,3 @@ +vec4 effect() { + return texture(u_screen, v_uv); +} diff --git a/res/shaders/screen.glslf b/res/shaders/screen.glslf deleted file mode 100644 index e7523d10..00000000 --- a/res/shaders/screen.glslf +++ /dev/null @@ -1,25 +0,0 @@ -in vec2 v_coord; -out vec4 f_color; - -uniform sampler2D u_texture0; -uniform ivec2 u_screenSize; - -// Vignette -vec4 apply_vignette(vec4 color) { - vec2 position = (gl_FragCoord.xy / u_screenSize) - vec2(0.5); - float dist = length(position); - - float radius = 1.3; - float softness = 1.0; - float vignette = smoothstep(radius, radius - softness, dist); - - color.rgb = color.rgb * vignette; - - return color; -} - -void main() { - f_color = texture(u_texture0, v_coord); - f_color = apply_vignette(f_color); -} - diff --git a/src/assets/Assets.hpp b/src/assets/Assets.hpp index c126079d..c0853b0b 100644 --- a/src/assets/Assets.hpp +++ b/src/assets/Assets.hpp @@ -16,7 +16,16 @@ class Assets; -enum class AssetType { TEXTURE, SHADER, FONT, ATLAS, LAYOUT, SOUND, MODEL }; +enum class AssetType { + TEXTURE, + SHADER, + FONT, + ATLAS, + LAYOUT, + SOUND, + MODEL, + POST_EFFECT +}; namespace assetload { /// @brief final work to do in the main thread @@ -91,6 +100,20 @@ public: return static_cast(found->second.get()); } + template + std::shared_ptr getShared(const std::string& name) const { + const auto& mapIter = assets.find(typeid(T)); + if (mapIter == assets.end()) { + return nullptr; + } + const auto& map = mapIter->second; + const auto& found = map.find(name); + if (found == map.end()) { + return nullptr; + } + return std::static_pointer_cast(found->second); + } + template T& require(const std::string& name) const { T* asset = get(name); diff --git a/src/assets/AssetsLoader.cpp b/src/assets/AssetsLoader.cpp index 10f3f7ea..cdbb28e2 100644 --- a/src/assets/AssetsLoader.cpp +++ b/src/assets/AssetsLoader.cpp @@ -34,6 +34,7 @@ AssetsLoader::AssetsLoader(Engine& engine, Assets& assets, const ResPaths& paths addLoader(AssetType::LAYOUT, assetload::layout); addLoader(AssetType::SOUND, assetload::sound); addLoader(AssetType::MODEL, assetload::model); + addLoader(AssetType::POST_EFFECT, assetload::posteffect); } void AssetsLoader::addLoader(AssetType tag, aloader_func func) { @@ -131,6 +132,8 @@ static std::string assets_def_folder(AssetType tag) { return SOUNDS_FOLDER; case AssetType::MODEL: return MODELS_FOLDER; + case AssetType::POST_EFFECT: + return POST_EFFECTS_FOLDER; } return ""; } @@ -196,6 +199,7 @@ void AssetsLoader::processPreloadConfig(const io::path& file) { processPreloadList(AssetType::TEXTURE, root["textures"]); processPreloadList(AssetType::SOUND, root["sounds"]); processPreloadList(AssetType::MODEL, root["models"]); + processPreloadList(AssetType::POST_EFFECT, root["post-effects"]); // layouts are loaded automatically } diff --git a/src/assets/assetload_funcs.cpp b/src/assets/assetload_funcs.cpp index 7558bbdc..2afae28c 100644 --- a/src/assets/assetload_funcs.cpp +++ b/src/assets/assetload_funcs.cpp @@ -60,13 +60,7 @@ assetload::postfunc assetload::texture( } } -assetload::postfunc assetload::shader( - AssetsLoader*, - const ResPaths& paths, - const std::string& filename, - const std::string& name, - const std::shared_ptr& -) { +static auto process_program(const ResPaths& paths, const std::string& filename) { io::path vertexFile = paths.find(filename + ".glslv"); io::path fragmentFile = paths.find(filename + ".glslf"); @@ -75,8 +69,25 @@ assetload::postfunc assetload::shader( auto& preprocessor = *Shader::preprocessor; - vertexSource = preprocessor.process(vertexFile, vertexSource).code; - fragmentSource = preprocessor.process(fragmentFile, fragmentSource).code; + auto vertex = preprocessor.process(vertexFile, vertexSource); + auto fragment = preprocessor.process(fragmentFile, fragmentSource); + return std::make_pair(vertex, fragment); +} + +assetload::postfunc assetload::shader( + AssetsLoader*, + const ResPaths& paths, + const std::string& filename, + const std::string& name, + const std::shared_ptr& +) { + auto [vertex, fragment] = process_program(paths, filename); + + io::path vertexFile = paths.find(filename + ".glslv"); + io::path fragmentFile = paths.find(filename + ".glslf"); + + std::string vertexSource = std::move(vertex.code); + std::string fragmentSource = std::move(fragment.code); return [=](auto assets) { assets->store( @@ -91,6 +102,40 @@ assetload::postfunc assetload::shader( }; } +assetload::postfunc assetload::posteffect( + AssetsLoader*, + const ResPaths& paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& settings +) { + io::path effectFile = paths.find(file + ".glsl"); + std::string effectSource = io::read_string(effectFile); + + auto& preprocessor = *Shader::preprocessor; + preprocessor.addHeader( + "__effect__", preprocessor.process(effectFile, effectSource, true) + ); + + auto [vertex, fragment] = process_program(paths, SHADERS_FOLDER + "/effect"); + auto params = std::move(fragment.params); + + std::string vertexSource = std::move(vertex.code); + std::string fragmentSource = std::move(fragment.code); + + return [=](auto assets) { + auto program = Shader::create( + effectFile.string(), + effectFile.string(), + vertexSource, + fragmentSource + ); + assets->store( + std::make_shared(std::move(program), params), name + ); + }; +} + static bool append_atlas(AtlasBuilder& atlas, const io::path& file) { std::string name = file.stem(); // skip duplicates diff --git a/src/assets/assetload_funcs.hpp b/src/assets/assetload_funcs.hpp index eb26dc62..97ed60ea 100644 --- a/src/assets/assetload_funcs.hpp +++ b/src/assets/assetload_funcs.hpp @@ -62,4 +62,11 @@ namespace assetload { const std::string& name, const std::shared_ptr& settings ); + postfunc posteffect( + AssetsLoader*, + const ResPaths& paths, + const std::string& file, + const std::string& name, + const std::shared_ptr& settings + ); } diff --git a/src/coders/GLSLExtension.cpp b/src/coders/GLSLExtension.cpp index f205b9b4..8af419e0 100644 --- a/src/coders/GLSLExtension.cpp +++ b/src/coders/GLSLExtension.cpp @@ -27,21 +27,21 @@ void GLSLExtension::loadHeader(const std::string& name) { } io::path file = paths->find("shaders/lib/" + name + ".glsl"); std::string source = io::read_string(file); - addHeader(name, ""); - - auto [code, _] = process(file, source, true); - addHeader(name, std::move(code)); + addHeader(name, {}); + addHeader(name, process(file, source, true)); } -void GLSLExtension::addHeader(const std::string& name, std::string source) { - headers[name] = std::move(source); +void GLSLExtension::addHeader(const std::string& name, ProcessingResult header) { + headers[name] = std::move(header); } void GLSLExtension::define(const std::string& name, std::string value) { defines[name] = std::move(value); } -const std::string& GLSLExtension::getHeader(const std::string& name) const { +const GLSLExtension::ProcessingResult& GLSLExtension::getHeader( + const std::string& name +) const { auto found = headers.find(name); if (found == headers.end()) { throw std::runtime_error("no header '" + name + "' loaded"); @@ -161,7 +161,11 @@ public: if (!glsl.hasHeader(headerName)) { glsl.loadHeader(headerName); } - ss << glsl.getHeader(headerName) << '\n'; + const auto& header = glsl.getHeader(headerName); + for (const auto& [name, param] : header.params) { + params[name] = param; + } + ss << header.code << '\n'; source_line(ss, line); return false; } diff --git a/src/coders/GLSLExtension.hpp b/src/coders/GLSLExtension.hpp index e78f06d7..2d795d2c 100644 --- a/src/coders/GLSLExtension.hpp +++ b/src/coders/GLSLExtension.hpp @@ -11,13 +11,20 @@ class ResPaths; class GLSLExtension { public: + using ParamsMap = std::unordered_map; + + struct ProcessingResult { + std::string code; + ParamsMap params; + }; + void setPaths(const ResPaths* paths); void define(const std::string& name, std::string value); void undefine(const std::string& name); - void addHeader(const std::string& name, std::string source); + void addHeader(const std::string& name, ProcessingResult header); - const std::string& getHeader(const std::string& name) const; + const ProcessingResult& getHeader(const std::string& name) const; const std::string& getDefine(const std::string& name) const; const std::unordered_map& getDefines() const; @@ -25,12 +32,7 @@ public: bool hasHeader(const std::string& name) const; bool hasDefine(const std::string& name) const; void loadHeader(const std::string& name); - - struct ProcessingResult { - std::string code; - std::unordered_map params; - }; - + ProcessingResult process( const io::path& file, const std::string& source, @@ -39,7 +41,7 @@ public: static inline std::string VERSION = "330 core"; private: - std::unordered_map headers; + std::unordered_map headers; std::unordered_map defines; const ResPaths* paths = nullptr; diff --git a/src/constants.hpp b/src/constants.hpp index 32acc83e..de954717 100644 --- a/src/constants.hpp +++ b/src/constants.hpp @@ -68,5 +68,6 @@ inline const std::string LAYOUTS_FOLDER = "layouts"; inline const std::string SOUNDS_FOLDER = "sounds"; inline const std::string MODELS_FOLDER = "models"; inline const std::string SKELETONS_FOLDER = "skeletons"; +inline const std::string POST_EFFECTS_FOLDER = "shaders/effects"; inline const std::string FONT_DEFAULT = "normal"; diff --git a/src/graphics/core/PostEffect.cpp b/src/graphics/core/PostEffect.cpp index 6af987c5..7fcc5162 100644 --- a/src/graphics/core/PostEffect.cpp +++ b/src/graphics/core/PostEffect.cpp @@ -8,10 +8,19 @@ PostEffect::Param::Param(Type type, Value defValue) : type(type), defValue(std::move(defValue)) { } -PostEffect::PostEffect(std::unique_ptr shader) - : shader(std::move(shader)) { +PostEffect::PostEffect( + std::unique_ptr shader, + std::unordered_map params +) + : shader(std::move(shader)), params(std::move(params)) { } -void PostEffect::use() { +Shader& PostEffect::use() { shader->use(); + shader->uniform1f("u_intensity", intensity); + return *shader; +} + +void PostEffect::setIntensity(float value) { + intensity = value; } diff --git a/src/graphics/core/PostEffect.hpp b/src/graphics/core/PostEffect.hpp index f77e121f..ed8f2753 100644 --- a/src/graphics/core/PostEffect.hpp +++ b/src/graphics/core/PostEffect.hpp @@ -21,10 +21,20 @@ public: Param(Type type, Value defValue); }; - PostEffect(std::unique_ptr shader); + PostEffect( + std::unique_ptr shader, + std::unordered_map params + ); - void use(); + Shader& use(); + + void setIntensity(float value); + + bool isActive() { + return intensity > 1e-4f; + } private: std::unique_ptr shader; std::unordered_map params; + float intensity = 0.0f; }; diff --git a/src/graphics/core/PostProcessing.cpp b/src/graphics/core/PostProcessing.cpp index 9cfe6414..f41ad661 100644 --- a/src/graphics/core/PostProcessing.cpp +++ b/src/graphics/core/PostProcessing.cpp @@ -4,13 +4,15 @@ #include "Texture.hpp" #include "Framebuffer.hpp" #include "DrawContext.hpp" +#include "PostEffect.hpp" +#include "assets/Assets.hpp" #include PostProcessing::PostProcessing() { // Fullscreen quad mesh bulding float vertices[] { - -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, + -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, -1.0f }; VertexAttribute attrs[] {{2}, {0}}; @@ -23,22 +25,56 @@ void PostProcessing::use(DrawContext& context) { const auto& vp = context.getViewport(); if (fbo) { fbo->resize(vp.x, vp.y); + fboSecond->resize(vp.x, vp.y); } else { fbo = std::make_unique(vp.x, vp.y); + fboSecond = std::make_unique(vp.x, vp.y); } context.setFramebuffer(fbo.get()); } -void PostProcessing::render(const DrawContext& context, Shader* screenShader) { +void PostProcessing::render( + const DrawContext& context, const Assets& assets, float timer +) { if (fbo == nullptr) { throw std::runtime_error("'use(...)' was never called"); } + int totalPasses = 0; + for (const auto& effect : effectSlots) { + totalPasses += (effect != nullptr && effect->isActive()); + } - const auto& viewport = context.getViewport(); - screenShader->use(); - screenShader->uniform2i("u_screenSize", viewport); - fbo->getTexture()->bind(); - quadMesh->draw(); + if (totalPasses == 0) { + auto& effect = assets.require("default"); + effect.use(); + fbo->getTexture()->bind(); + quadMesh->draw(); + return; + } + + int currentPass = 1; + for (const auto& effect : effectSlots) { + if (effect == nullptr || !effect->isActive()) { + continue; + } + auto& shader = effect->use(); + + const auto& viewport = context.getViewport(); + shader.uniform1i("u_screen", 0); + shader.uniform2i("u_screenSize", viewport); + shader.uniform1f("u_timer", timer); + + fbo->getTexture()->bind(); + if (currentPass < totalPasses) { + fboSecond->bind(); + } + quadMesh->draw(); + if (currentPass < totalPasses) { + fboSecond->unbind(); + std::swap(fbo, fboSecond); + } + currentPass++; + } } std::unique_ptr PostProcessing::toImage() { diff --git a/src/graphics/core/PostProcessing.hpp b/src/graphics/core/PostProcessing.hpp index b31a1369..888c6694 100644 --- a/src/graphics/core/PostProcessing.hpp +++ b/src/graphics/core/PostProcessing.hpp @@ -1,12 +1,14 @@ #pragma once +#include #include class Mesh; -class Shader; +class Assets; class Framebuffer; class DrawContext; class ImageData; +class PostEffect; /// @brief Framebuffer with blitting with shaders. /// @attention Current implementation does not support multiple render passes @@ -14,8 +16,10 @@ class ImageData; class PostProcessing { /// @brief Main framebuffer (lasy field) std::unique_ptr fbo; + std::unique_ptr fboSecond; /// @brief Fullscreen quad mesh as the post-processing canvas std::unique_ptr quadMesh; + std::vector> effectSlots; public: PostProcessing(); ~PostProcessing(); @@ -27,9 +31,8 @@ public: /// @brief Render fullscreen quad using the passed shader /// with framebuffer texture bound /// @param context graphics context - /// @param screenShader shader used for fullscreen quad /// @throws std::runtime_error if use(...) wasn't called before - void render(const DrawContext& context, Shader* screenShader); + void render(const DrawContext& context, const Assets& assets, float timer); /// @brief Make an image from the last rendered frame std::unique_ptr toImage(); diff --git a/src/graphics/render/WorldRenderer.cpp b/src/graphics/render/WorldRenderer.cpp index 73ab27a2..04a0e427 100644 --- a/src/graphics/render/WorldRenderer.cpp +++ b/src/graphics/render/WorldRenderer.cpp @@ -388,12 +388,7 @@ void WorldRenderer::draw( renderBlockOverlay(wctx); } - // Rendering fullscreen quad - auto screenShader = assets.get("screen"); - screenShader->use(); - screenShader->uniform1f("u_timer", timer); - screenShader->uniform1f("u_dayTime", worldInfo.daytime); - postProcessing.render(pctx, screenShader); + postProcessing.render(pctx, assets, timer); } void WorldRenderer::renderBlockOverlay(const DrawContext& wctx) { diff --git a/src/typedefs.hpp b/src/typedefs.hpp index e598b9da..f075e998 100644 --- a/src/typedefs.hpp +++ b/src/typedefs.hpp @@ -1,7 +1,7 @@ #pragma once #include -#include +#include using scriptenv = std::shared_ptr; diff --git a/test/coders/GLSLExtension.cpp b/test/coders/GLSLExtension.cpp index 87690ad6..bcdd5316 100644 --- a/test/coders/GLSLExtension.cpp +++ b/test/coders/GLSLExtension.cpp @@ -6,10 +6,13 @@ 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" + glsl.process("sum.glsl", + "// sum function for glsl\n" + "float sum(float a, float b) {\n" + " return a + b;\n" + "}\n", + true + ) ); try { auto processed = glsl.process("test.glsl", From 5678700826c08f07e6c10db424853005b15af1bb Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 5 Apr 2025 00:41:58 +0300 Subject: [PATCH 6/9] add test effects: grayscale and negative --- res/shaders/effects/grayscale.glsl | 5 +++++ res/shaders/effects/negative.glsl | 6 ++++++ 2 files changed, 11 insertions(+) create mode 100644 res/shaders/effects/grayscale.glsl create mode 100644 res/shaders/effects/negative.glsl diff --git a/res/shaders/effects/grayscale.glsl b/res/shaders/effects/grayscale.glsl new file mode 100644 index 00000000..e2702281 --- /dev/null +++ b/res/shaders/effects/grayscale.glsl @@ -0,0 +1,5 @@ +vec4 effect() { + vec3 color = texture(u_screen, v_uv).rgb; + float m = (color.r + color.g + color.b) / 3.0; + return vec4(mix(color, vec3(m), u_intensity), 1.0); +} diff --git a/res/shaders/effects/negative.glsl b/res/shaders/effects/negative.glsl new file mode 100644 index 00000000..6388a99d --- /dev/null +++ b/res/shaders/effects/negative.glsl @@ -0,0 +1,6 @@ +vec4 effect() { + vec4 color = texture(u_screen, v_uv); + color = mix(color, 1.0 - color, u_intensity); + color.a = 1.0; + return color; +} From c3bc084e766eba320f6662703649ab7ddd0bd16e Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 6 Apr 2025 12:45:58 +0300 Subject: [PATCH 7/9] update Shader class --- src/graphics/core/Shader.cpp | 12 ++++++++---- src/graphics/core/Shader.hpp | 9 +++++---- 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/src/graphics/core/Shader.cpp b/src/graphics/core/Shader.cpp index 71a7288b..122495ff 100644 --- a/src/graphics/core/Shader.cpp +++ b/src/graphics/core/Shader.cpp @@ -38,7 +38,7 @@ uint Shader::getUniformLocation(const std::string& name) { return found->second; } -void Shader::uniformMatrix(const std::string& name, glm::mat4 matrix){ +void Shader::uniformMatrix(const std::string& name, const glm::mat4& matrix){ glUniformMatrix4fv(getUniformLocation(name), 1, GL_FALSE, glm::value_ptr(matrix)); } @@ -54,11 +54,11 @@ void Shader::uniform2f(const std::string& name, float x, float y){ glUniform2f(getUniformLocation(name), x, y); } -void Shader::uniform2f(const std::string& name, glm::vec2 xy){ +void Shader::uniform2f(const std::string& name, const glm::vec2& xy){ glUniform2f(getUniformLocation(name), xy.x, xy.y); } -void Shader::uniform2i(const std::string& name, glm::ivec2 xy){ +void Shader::uniform2i(const std::string& name, const glm::ivec2& xy){ glUniform2i(getUniformLocation(name), xy.x, xy.y); } @@ -66,10 +66,14 @@ void Shader::uniform3f(const std::string& name, float x, float y, float z){ glUniform3f(getUniformLocation(name), x,y,z); } -void Shader::uniform3f(const std::string& name, glm::vec3 xyz){ +void Shader::uniform3f(const std::string& name, const glm::vec3& xyz){ glUniform3f(getUniformLocation(name), xyz.x, xyz.y, xyz.z); } +void Shader::uniform4f(const std::string& name, const glm::vec4& xyzw) { + glUniform4f(getUniformLocation(name), xyzw.x, xyzw.y, xyzw.z, xyzw.w); +} + inline auto shader_deleter = [](GLuint* shader) { glDeleteShader(*shader); diff --git a/src/graphics/core/Shader.hpp b/src/graphics/core/Shader.hpp index dd399751..5c970d4f 100644 --- a/src/graphics/core/Shader.hpp +++ b/src/graphics/core/Shader.hpp @@ -21,14 +21,15 @@ public: ~Shader(); void use(); - void uniformMatrix(const std::string&, glm::mat4 matrix); + void uniformMatrix(const std::string&, const glm::mat4& matrix); void uniform1i(const std::string& name, int x); void uniform1f(const std::string& name, float x); void uniform2f(const std::string& name, float x, float y); - void uniform2f(const std::string& name, glm::vec2 xy); - void uniform2i(const std::string& name, glm::ivec2 xy); + void uniform2f(const std::string& name, const glm::vec2& xy); + void uniform2i(const std::string& name, const glm::ivec2& xy); void uniform3f(const std::string& name, float x, float y, float z); - void uniform3f(const std::string& name, glm::vec3 xyz); + void uniform3f(const std::string& name, const glm::vec3& xyz); + void uniform4f(const std::string& name, const glm::vec4& xyzw); /// @brief Create shader program using vertex and fragment shaders source. /// @param vertexFile vertex shader file name From 64039f0e431226117891754d830723ad0c31a5da Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 6 Apr 2025 12:47:25 +0300 Subject: [PATCH 8/9] add post-effect-slot resource & add gfx.posteffects library --- src/content/Content.hpp | 4 ++ src/content/content_fwd.hpp | 6 +- src/frontend/screens/LevelScreen.cpp | 8 ++- src/graphics/core/PostEffect.cpp | 61 ++++++++++++++++- src/graphics/core/PostEffect.hpp | 13 +++- src/graphics/core/PostProcessing.cpp | 11 ++- src/graphics/core/PostProcessing.hpp | 6 +- src/logic/scripting/lua/libs/api_lua.hpp | 3 +- .../scripting/lua/libs/libposteffects.cpp | 68 +++++++++++++++++++ src/logic/scripting/scripting_hud.cpp | 8 ++- src/logic/scripting/scripting_hud.hpp | 6 +- 11 files changed, 182 insertions(+), 12 deletions(-) create mode 100644 src/logic/scripting/lua/libs/libposteffects.cpp diff --git a/src/content/Content.hpp b/src/content/Content.hpp index 94650b25..bbdf2013 100644 --- a/src/content/Content.hpp +++ b/src/content/Content.hpp @@ -189,6 +189,8 @@ constexpr const char* to_string(ResourceType type) { switch (type) { case ResourceType::CAMERA: return "camera"; + case ResourceType::POST_EFFECT_SLOT: + return "post-effect-slot"; default: return "unknown"; } @@ -197,6 +199,8 @@ constexpr const char* to_string(ResourceType type) { inline std::optional ResourceType_from(std::string_view str) { if (str == "camera") { return ResourceType::CAMERA; + } else if (str == "post-effect-slot") { + return ResourceType::POST_EFFECT_SLOT; } return std::nullopt; } diff --git a/src/content/content_fwd.hpp b/src/content/content_fwd.hpp index 076a433f..f6e2ffb7 100644 --- a/src/content/content_fwd.hpp +++ b/src/content/content_fwd.hpp @@ -7,7 +7,11 @@ class ContentPackRuntime; enum class ContentType { NONE, BLOCK, ITEM, ENTITY, GENERATOR }; -enum class ResourceType : size_t { CAMERA, LAST = CAMERA }; +enum class ResourceType : size_t { + CAMERA, + POST_EFFECT_SLOT, + LAST = POST_EFFECT_SLOT +}; inline constexpr auto RESOURCE_TYPES_COUNT = static_cast(ResourceType::LAST) + 1; diff --git a/src/frontend/screens/LevelScreen.cpp b/src/frontend/screens/LevelScreen.cpp index 44b81765..76f7e950 100644 --- a/src/frontend/screens/LevelScreen.cpp +++ b/src/frontend/screens/LevelScreen.cpp @@ -42,7 +42,9 @@ LevelScreen::LevelScreen( ) : Screen(engine), world(*levelPtr->getWorld()), - postProcessing(std::make_unique()), + postProcessing(std::make_unique( + levelPtr->content.getIndices(ResourceType::POST_EFFECT_SLOT).size() + )), gui(engine.getGUI()), input(engine.getInput()) { Level* level = levelPtr.get(); @@ -115,7 +117,9 @@ void LevelScreen::initializeContent() { for (auto& entry : content.getPacks()) { initializePack(entry.second.get()); } - scripting::on_frontend_init(hud.get(), renderer.get()); + scripting::on_frontend_init( + hud.get(), renderer.get(), postProcessing.get() + ); } void LevelScreen::initializePack(ContentPackRuntime* pack) { diff --git a/src/graphics/core/PostEffect.cpp b/src/graphics/core/PostEffect.cpp index 7fcc5162..d634c636 100644 --- a/src/graphics/core/PostEffect.cpp +++ b/src/graphics/core/PostEffect.cpp @@ -1,15 +1,16 @@ #include "PostEffect.hpp" #include "Shader.hpp" +#include "data/dv_util.hpp" PostEffect::Param::Param() : type(Type::FLOAT) {} PostEffect::Param::Param(Type type, Value defValue) - : type(type), defValue(std::move(defValue)) { + : type(type), defValue(defValue), value(defValue) { } PostEffect::PostEffect( - std::unique_ptr shader, + std::shared_ptr shader, std::unordered_map params ) : shader(std::move(shader)), params(std::move(params)) { @@ -18,9 +19,65 @@ PostEffect::PostEffect( Shader& PostEffect::use() { shader->use(); shader->uniform1f("u_intensity", intensity); + for (auto& [name, param] : params) { + if (!param.dirty) { + continue; + } + switch (param.type) { + case Param::Type::FLOAT: + shader->uniform1f(name, std::get(param.value)); + break; + case Param::Type::VEC2: + shader->uniform2f(name, std::get(param.value)); + break; + case Param::Type::VEC3: + shader->uniform3f(name, std::get(param.value)); + break; + case Param::Type::VEC4: + shader->uniform4f(name, std::get(param.value)); + break; + default: + assert(false); + } + param.dirty = false; + } return *shader; } +float PostEffect::getIntensity() const { + return intensity; +} + void PostEffect::setIntensity(float value) { intensity = value; } + +template +static void set_value(PostEffect::Param::Value& dst, const dv::value& value) { + glm::vec vec; + dv::get_vec(value, vec); + dst = vec; +} + +void PostEffect::setParam(const std::string& name, const dv::value& value) { + const auto& found = params.find(name); + if (found == params.end()) { + return; + } + auto& param = found->second; + switch (param.type) { + case Param::Type::FLOAT: + param.value = static_cast(value.asNumber()); + break; + case Param::Type::VEC2: + set_value<2>(param.value, value); + break; + case Param::Type::VEC3: + set_value<3>(param.value, value); + break; + case Param::Type::VEC4: + set_value<4>(param.value, value); + break; + } + param.dirty = true; +} diff --git a/src/graphics/core/PostEffect.hpp b/src/graphics/core/PostEffect.hpp index ed8f2753..4624ec40 100644 --- a/src/graphics/core/PostEffect.hpp +++ b/src/graphics/core/PostEffect.hpp @@ -6,6 +6,8 @@ #include #include +#include "data/dv_fwd.hpp" + class Shader; class PostEffect { @@ -16,25 +18,32 @@ public: Type type; Value defValue; + Value value; + bool dirty = true; Param(); Param(Type type, Value defValue); }; PostEffect( - std::unique_ptr shader, + std::shared_ptr shader, std::unordered_map params ); + explicit PostEffect(const PostEffect&) = default; + Shader& use(); + float getIntensity() const; void setIntensity(float value); + void setParam(const std::string& name, const dv::value& value); + bool isActive() { return intensity > 1e-4f; } private: - std::unique_ptr shader; + std::shared_ptr shader; std::unordered_map params; float intensity = 0.0f; }; diff --git a/src/graphics/core/PostProcessing.cpp b/src/graphics/core/PostProcessing.cpp index f41ad661..c15aa8fb 100644 --- a/src/graphics/core/PostProcessing.cpp +++ b/src/graphics/core/PostProcessing.cpp @@ -9,7 +9,8 @@ #include -PostProcessing::PostProcessing() { +PostProcessing::PostProcessing(size_t effectSlotsCount) + : effectSlots(effectSlotsCount) { // Fullscreen quad mesh bulding float vertices[] { -1.0f, -1.0f, -1.0f, 1.0f, 1.0f, 1.0f, @@ -77,6 +78,14 @@ void PostProcessing::render( } } +void PostProcessing::setEffect(size_t slot, std::shared_ptr effect) { + effectSlots.at(slot) = std::move(effect); +} + +PostEffect* PostProcessing::getEffect(size_t slot) { + return effectSlots.at(slot).get(); +} + std::unique_ptr PostProcessing::toImage() { return fbo->getTexture()->readData(); } diff --git a/src/graphics/core/PostProcessing.hpp b/src/graphics/core/PostProcessing.hpp index 888c6694..57a081c9 100644 --- a/src/graphics/core/PostProcessing.hpp +++ b/src/graphics/core/PostProcessing.hpp @@ -21,7 +21,7 @@ class PostProcessing { std::unique_ptr quadMesh; std::vector> effectSlots; public: - PostProcessing(); + PostProcessing(size_t effectSlotsCount); ~PostProcessing(); /// @brief Prepare and bind framebuffer @@ -34,6 +34,10 @@ public: /// @throws std::runtime_error if use(...) wasn't called before void render(const DrawContext& context, const Assets& assets, float timer); + void setEffect(size_t slot, std::shared_ptr effect); + + PostEffect* getEffect(size_t slot); + /// @brief Make an image from the last rendered frame std::unique_ptr toImage(); diff --git a/src/logic/scripting/lua/libs/api_lua.hpp b/src/logic/scripting/lua/libs/api_lua.hpp index 39d09241..7b4253c5 100644 --- a/src/logic/scripting/lua/libs/api_lua.hpp +++ b/src/logic/scripting/lua/libs/api_lua.hpp @@ -38,6 +38,7 @@ extern const luaL_Reg networklib[]; extern const luaL_Reg packlib[]; extern const luaL_Reg particleslib[]; // gfx.particles extern const luaL_Reg playerlib[]; +extern const luaL_Reg posteffectslib[]; // gfx.posteffects extern const luaL_Reg quatlib[]; extern const luaL_Reg text3dlib[]; // gfx.text3d extern const luaL_Reg timelib[]; @@ -46,7 +47,7 @@ extern const luaL_Reg utf8lib[]; extern const luaL_Reg vec2lib[]; // vecn.cpp extern const luaL_Reg vec3lib[]; // vecn.cpp extern const luaL_Reg vec4lib[]; // vecn.cpp -extern const luaL_Reg weatherlib[]; +extern const luaL_Reg weatherlib[]; // gfx.weather extern const luaL_Reg worldlib[]; extern const luaL_Reg yamllib[]; diff --git a/src/logic/scripting/lua/libs/libposteffects.cpp b/src/logic/scripting/lua/libs/libposteffects.cpp new file mode 100644 index 00000000..ccdb34aa --- /dev/null +++ b/src/logic/scripting/lua/libs/libposteffects.cpp @@ -0,0 +1,68 @@ +#include "libhud.hpp" + +#include "assets/Assets.hpp" +#include "content/Content.hpp" +#include "graphics/core/PostEffect.hpp" +#include "graphics/core/PostProcessing.hpp" + +using namespace scripting; + +static int l_index(lua::State* L) { + auto name = lua::require_string(L, 1); + auto& indices = content->getIndices(ResourceType::POST_EFFECT_SLOT); + return lua::pushinteger(L, indices.indexOf(name)); +} + +static int l_set_effect(lua::State* L) { + size_t index = static_cast(lua::tointeger(L, 1)); + auto name = lua::require_string(L, 2); + auto& assets = *engine->getAssets(); + auto effect = std::make_shared(assets.require(name)); + post_processing->setEffect(index, std::move(effect)); + return 0; +} + +static int l_get_intensity(lua::State* L) { + size_t index = static_cast(lua::tointeger(L, 1)); + auto effect = post_processing->getEffect(index); + return lua::pushnumber(L, effect ? effect->getIntensity() : 0.0f); +} + +static int l_set_intensity(lua::State* L) { + size_t index = static_cast(lua::tointeger(L, 1)); + float value = lua::tonumber(L, 2); + post_processing->getEffect(index)->setIntensity(value); + return 0; +} + +static int l_is_active(lua::State* L) { + size_t index = static_cast(lua::tointeger(L, 1)); + auto effect = post_processing->getEffect(index); + return lua::pushboolean(L, effect ? effect->isActive() : false); +} + +static int l_set_params(lua::State* L) { + size_t index = static_cast(lua::tointeger(L, 1)); + auto table = lua::tovalue(L, 2); + if (!table.isObject()) { + throw std::runtime_error("params table expected"); + } + auto effect = post_processing->getEffect(index); + if (effect == nullptr) { + return 0; + } + for (const auto& [key, value] : table.asObject()) { + effect->setParam(key, value); + } + return 0; +} + +const luaL_Reg posteffectslib[] = { + {"index", lua::wrap}, + {"set_effect", lua::wrap}, + {"get_intensity", lua::wrap}, + {"set_intensity", lua::wrap}, + {"is_active", lua::wrap}, + {"set_params", lua::wrap}, + {NULL, NULL} +}; diff --git a/src/logic/scripting/scripting_hud.cpp b/src/logic/scripting/scripting_hud.cpp index 305cb6f9..da72b031 100644 --- a/src/logic/scripting/scripting_hud.cpp +++ b/src/logic/scripting/scripting_hud.cpp @@ -19,6 +19,7 @@ static debug::Logger logger("scripting-hud"); Hud* scripting::hud = nullptr; WorldRenderer* scripting::renderer = nullptr; +PostProcessing* scripting::post_processing = nullptr; static void load_script(const std::string& name) { auto file = io::path("res:scripts") / name; @@ -28,9 +29,12 @@ static void load_script(const std::string& name) { lua::execute(lua::get_main_state(), 0, src, file.string()); } -void scripting::on_frontend_init(Hud* hud, WorldRenderer* renderer) { +void scripting::on_frontend_init( + Hud* hud, WorldRenderer* renderer, PostProcessing* postProcessing +) { scripting::hud = hud; scripting::renderer = renderer; + scripting::post_processing = postProcessing; auto L = lua::get_main_state(); @@ -39,6 +43,7 @@ void scripting::on_frontend_init(Hud* hud, WorldRenderer* renderer) { lua::openlib(L, "gfx", "particles", particleslib); lua::openlib(L, "gfx", "weather", weatherlib); lua::openlib(L, "gfx", "text3d", text3dlib); + lua::openlib(L, "gfx", "posteffects", posteffectslib); load_script("hud_classes.lua"); @@ -85,6 +90,7 @@ void scripting::on_frontend_close() { scripting::renderer = nullptr; scripting::hud = nullptr; + scripting::post_processing = nullptr; } void scripting::load_hud_script( diff --git a/src/logic/scripting/scripting_hud.hpp b/src/logic/scripting/scripting_hud.hpp index 1df47df2..ddfdc1e3 100644 --- a/src/logic/scripting/scripting_hud.hpp +++ b/src/logic/scripting/scripting_hud.hpp @@ -9,6 +9,7 @@ class Hud; class WorldRenderer; +class PostProcessing; namespace gui { class UINode; @@ -18,8 +19,11 @@ namespace gui { namespace scripting { extern Hud *hud; extern WorldRenderer* renderer; + extern PostProcessing* post_processing; - void on_frontend_init(Hud* hud, WorldRenderer* renderer); + void on_frontend_init( + Hud* hud, WorldRenderer* renderer, PostProcessing* postProcessing + ); void on_frontend_render(); void on_frontend_close(); From 4157bde1560f12a0d039fe02688fea6f48bf692b Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sun, 6 Apr 2025 13:10:34 +0300 Subject: [PATCH 9/9] add vignette effect --- res/shaders/effects/vignette.glsl | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 res/shaders/effects/vignette.glsl diff --git a/res/shaders/effects/vignette.glsl b/res/shaders/effects/vignette.glsl new file mode 100644 index 00000000..23d9dc86 --- /dev/null +++ b/res/shaders/effects/vignette.glsl @@ -0,0 +1,15 @@ +#include + +#param float p_radius = 1.1 +#param float p_softness = 0.7 + +vec4 apply_vignette(vec4 color) { + vec2 position = v_uv - vec2(0.5); + float dist = length(position); + float vignette = smoothstep(p_radius, p_radius - p_softness, dist); + return vec4(color.rgb * vignette, 1.0); +} + +vec4 effect() { + return apply_vignette(texture(u_screen, v_uv)); +}