diff --git a/src/util/postprocessing_shader_slang.cpp b/src/util/postprocessing_shader_slang.cpp index e08fa1a76..345ec3c2a 100644 --- a/src/util/postprocessing_shader_slang.cpp +++ b/src/util/postprocessing_shader_slang.cpp @@ -8,6 +8,8 @@ #include "shadergen.h" #include "spirv_module.h" +#include "core/host.h" // TODO: Remove after removing ReadResourceFile() + #include "common/assert.h" #include "common/bitutils.h" #include "common/error.h" @@ -31,6 +33,8 @@ LOG_CHANNEL(PostProcessing); +using namespace std::string_view_literals; + // TODO: // - Need some sort of cache for the UBO/push constant layout so we don't need to go through SPIR-V Cross every time. @@ -70,7 +74,7 @@ public: SlangPresetParser(); ~SlangPresetParser(); - bool Parse(std::string_view path, std::string_view contents, Error* error); + bool Parse(std::string_view path, std::string_view contents, u32 reference_nesting_level, Error* error); bool ContainsValue(std::string_view key) const; std::string_view GetStringValue(std::string_view key, std::string_view def) const; @@ -89,6 +93,9 @@ public: private: static bool GetLine(const std::string_view& contents, std::string_view* line, size_t& offset); + bool ParsePresetReference(const std::string_view& path, const std::string_view& line, u32 reference_nesting_level, + Error* error); + UnorderedStringMap m_options; }; @@ -196,7 +203,8 @@ inline bool PostProcessing::SlangPresetParser::GetLine(const std::string_view& c return true; } -inline bool PostProcessing::SlangPresetParser::Parse(std::string_view path, std::string_view contents, Error* error) +inline bool PostProcessing::SlangPresetParser::Parse(std::string_view path, std::string_view contents, + u32 reference_nesting_level, Error* error) { u32 line_number = 0; size_t offset = 0; @@ -210,6 +218,14 @@ inline bool PostProcessing::SlangPresetParser::Parse(std::string_view path, std: if (clean_line.empty()) continue; + if (clean_line.starts_with("#reference ")) + { + if (!ParsePresetReference(path, clean_line, reference_nesting_level, error)) + return false; + + continue; + } + // despite having c-style comments, presets can also use # as a comment, but #reference is a thing // ughhhhhh what a mess if (clean_line.starts_with('#')) @@ -240,6 +256,110 @@ inline bool PostProcessing::SlangPresetParser::Parse(std::string_view path, std: return true; } +inline bool PostProcessing::SlangPresetParser::ParsePresetReference(const std::string_view& path, + const std::string_view& line, + u32 reference_nesting_level, Error* error) +{ + if (reference_nesting_level == MAX_SLANG_INCLUDE_DEPTH) + { + Error::SetStringFmt(error, "{}:{} Too many nested references", Path::GetFileName(path), line); + return false; + } + + const std::string_view reference_quoted_path = StringUtil::StripWhitespace(line.substr(11)); + if (reference_quoted_path.size() < 3 || !reference_quoted_path.starts_with('"') || + !reference_quoted_path.ends_with('"')) + { + Error::SetStringFmt(error, "{}:{} Malformed preset reference", Path::GetFileName(path), line); + return false; + } + + const std::string_view reference_unquoted_path = reference_quoted_path.substr(1, reference_quoted_path.size() - 2); + std::string reference_path = Path::BuildRelativePath(path, reference_unquoted_path); + Path::ToNativePath(&reference_path); + + std::optional reference_contents; + if (!Path::IsAbsolute(reference_path)) + reference_contents = Host::ReadResourceFileToString(reference_path, true, error); + else + reference_contents = FileSystem::ReadFileToString(reference_path.c_str(), error); + if (!reference_contents.has_value()) + { + Error::AddPrefixFmt(error, "Failed to read referenced preset {}: ", reference_unquoted_path); + return false; + } + + SlangPresetParser pp; + if (!pp.Parse(reference_path, reference_contents.value(), reference_nesting_level + 1, error)) + { + Error::AddPrefixFmt(error, "In referenced preset {}: ", Path::GetFileName(reference_path)); + return false; + } + + // once we hit a full preset, we need to fix up the paths so that they're relative to the original preset + if (const auto iter = pp.m_options.find("shaders"sv); iter != pp.m_options.end()) + { + const u32 num_shaders = StringUtil::FromChars(iter->second).value_or(0); + for (u32 i = 0; i < num_shaders; i++) + { + const TinyString key = TinyString::from_format("shader{}", i); + const auto siter = pp.m_options.find(key.view()); + if (siter == pp.m_options.end()) + continue; + + // if it's already absolute, no need to do anything + if (Path::IsAbsolute(siter->second)) + continue; + + // if not, we need to make it absolute, relative to the current preset file + std::string fixed_path = Path::BuildRelativePath(reference_path, siter->second); + Path::ToNativePath(&fixed_path); + DEV_LOG("Fixing up shader path in reference '{}' to '{}' (ref {})", siter->second, fixed_path, + reference_unquoted_path); + siter->second = std::move(fixed_path); + } + } + + // same for textures... + if (const auto iter = pp.m_options.find("textures"sv); iter != pp.m_options.end()) + { + const std::vector texture_names = StringUtil::SplitString(iter->second, ';'); + for (const std::string_view orig_name : texture_names) + { + const std::string_view name = StringUtil::StripWhitespace(orig_name); + if (name.empty()) + continue; + + const auto titer = pp.m_options.find(name); + if (titer == pp.m_options.end()) + continue; + + // if it's already absolute, no need to do anything + if (Path::IsAbsolute(titer->second)) + continue; + + // if not, we need to make it absolute, relative to the current preset file + std::string fixed_path = Path::BuildRelativePath(reference_path, titer->second); + Path::ToNativePath(&fixed_path); + DEV_LOG("Fixing up texture path in reference '{}' to '{}' (ref {})", titer->second, fixed_path, + reference_unquoted_path); + titer->second = std::move(fixed_path); + } + } + + // now merge the options back + for (auto iter = pp.m_options.begin(); iter != pp.m_options.end(); ++iter) + { + const auto biter = m_options.find(iter->first); + if (biter != m_options.end()) + biter->second = std::move(iter->second); + else + m_options.emplace(iter->first, std::move(iter->second)); + } + + return true; +} + inline bool PostProcessing::SlangPresetParser::ContainsValue(std::string_view key) const { return (m_options.find(key) != m_options.end()); @@ -795,7 +915,7 @@ bool PostProcessing::SlangShader::LoadFromString(std::string name, std::string_v bool PostProcessing::SlangShader::ParsePresetFile(std::string_view path, std::string_view code, Error* error) { SlangPresetParser pp; - if (!pp.Parse(path, code, error)) + if (!pp.Parse(path, code, 0, error)) return false; const u32 num_shaders = pp.GetUIntValue("shaders", 0); @@ -1069,6 +1189,24 @@ bool PostProcessing::SlangShader::ParsePresetPass(std::string_view preset_path, m_options.push_back(std::move(option)); } } + + // Extract any options from the preset if overridden. + for (ShaderOption& option : m_options) + { + if (!parser.ContainsValue(option.name)) + continue; + + if (option.type == ShaderOption::Type::Bool) + { + option.default_value[0].int_value = option.value[0].int_value = + parser.GetBoolValue(option.name, option.default_value[0].int_value); + } + else + { + option.default_value[0].float_value = option.value[0].float_value = + parser.GetFloatValue(option.name, option.default_value[0].float_value); + } + } } m_passes.push_back(std::move(pass));