From 7bbf04ab995f4c0fc53f8697bb11be302eab37f1 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Thu, 23 Nov 2023 19:52:28 +1000 Subject: [PATCH] PostProcessing/FX: Support reading shaders from resources --- src/duckstation-nogui/nogui_host.cpp | 6 ++ src/duckstation-qt/qthost.cpp | 6 ++ src/duckstation-regtest/regtest_host.cpp | 6 ++ src/util/host.h | 3 + src/util/postprocessing.cpp | 27 ++++---- src/util/postprocessing_shader_fx.cpp | 86 +++++++++++++++++++++--- src/util/postprocessing_shader_fx.h | 5 +- 7 files changed, 116 insertions(+), 23 deletions(-) diff --git a/src/duckstation-nogui/nogui_host.cpp b/src/duckstation-nogui/nogui_host.cpp index f71e16499..8e86d1ca3 100644 --- a/src/duckstation-nogui/nogui_host.cpp +++ b/src/duckstation-nogui/nogui_host.cpp @@ -350,6 +350,12 @@ s32 Host::Internal::GetTranslatedStringImpl(const std::string_view& context, con return static_cast(msg.size()); } +bool Host::ResourceFileExists(const char* filename) +{ + const std::string path(Path::Combine(EmuFolders::Resources, filename)); + return FileSystem::FileExists(path.c_str()); +} + std::optional> Host::ReadResourceFile(const char* filename) { const std::string path(Path::Combine(EmuFolders::Resources, filename)); diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 1b2f0ceca..002d2bd27 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -1441,6 +1441,12 @@ void Host::OnInputDeviceDisconnected(const std::string_view& identifier) identifier.empty() ? QString() : QString::fromUtf8(identifier.data(), identifier.size())); } +bool Host::ResourceFileExists(const char* filename) +{ + const std::string path(Path::Combine(EmuFolders::Resources, filename)); + return FileSystem::FileExists(path.c_str()); +} + std::optional> Host::ReadResourceFile(const char* filename) { const std::string path(Path::Combine(EmuFolders::Resources, filename)); diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp index 974ba5ad9..6c264ca97 100644 --- a/src/duckstation-regtest/regtest_host.cpp +++ b/src/duckstation-regtest/regtest_host.cpp @@ -171,6 +171,12 @@ void Host::CommitBaseSettingChanges() // noop, in memory } +bool Host::ResourceFileExists(const char* filename) +{ + const std::string path(Path::Combine(EmuFolders::Resources, filename)); + return FileSystem::FileExists(path.c_str()); +} + std::optional> Host::ReadResourceFile(const char* filename) { const std::string path(Path::Combine(EmuFolders::Resources, filename)); diff --git a/src/util/host.h b/src/util/host.h index 4d79ecf57..27bd4d750 100644 --- a/src/util/host.h +++ b/src/util/host.h @@ -12,6 +12,9 @@ #include namespace Host { +/// Returns true if the specified resource file exists. +bool ResourceFileExists(const char* filename); + /// Reads a file from the resources directory of the application. /// This may be outside of the "normal" filesystem on platforms such as Mac. std::optional> ReadResourceFile(const char* filename); diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp index 12b350f1c..716def5be 100644 --- a/src/util/postprocessing.cpp +++ b/src/util/postprocessing.cpp @@ -368,16 +368,6 @@ std::unique_ptr PostProcessing::TryLoadingShader(const s filename = Path::Combine( EmuFolders::Shaders, fmt::format("reshade" FS_OSPATH_SEPARATOR_STR "Shaders" FS_OSPATH_SEPARATOR_STR "{}.fx", shader_name)); - - // TODO: Won't work on Android. Who cares? All the homies are tired of demanding Android users. - if (!FileSystem::FileExists(filename.c_str())) - { - filename = Path::Combine(EmuFolders::Resources, - fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "reshade" FS_OSPATH_SEPARATOR_STR - "Shaders" FS_OSPATH_SEPARATOR_STR "{}.fx", - shader_name)); - } - if (FileSystem::FileExists(filename.c_str())) { std::unique_ptr shader = std::make_unique(); @@ -393,8 +383,21 @@ std::unique_ptr PostProcessing::TryLoadingShader(const s return shader; } - resource_str = - Host::ReadResourceFileToString(fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "{}.glsl", shader_name).c_str()); + filename = + fmt::format("shaders/reshade" FS_OSPATH_SEPARATOR_STR "Shaders" FS_OSPATH_SEPARATOR_STR "{}.fx", shader_name); + resource_str = Host::ReadResourceFileToString(filename.c_str()); + if (resource_str.has_value()) + { + std::unique_ptr shader = std::make_unique(); + if (shader->LoadFromString(std::string(shader_name), std::move(filename), std::move(resource_str.value()), + only_config, error)) + { + return shader; + } + } + + filename = fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "{}.glsl", shader_name); + resource_str = Host::ReadResourceFileToString(filename.c_str()); if (resource_str.has_value()) { std::unique_ptr shader = std::make_unique(); diff --git a/src/util/postprocessing_shader_fx.cpp b/src/util/postprocessing_shader_fx.cpp index 33a8402ff..58177d077 100644 --- a/src/util/postprocessing_shader_fx.cpp +++ b/src/util/postprocessing_shader_fx.cpp @@ -39,6 +39,28 @@ static RenderAPI GetRenderAPI() return g_gpu_device ? g_gpu_device->GetRenderAPI() : RenderAPI::D3D11; } +static bool PreprocessorFileExistsCallback(const std::string& path) +{ + if (Path::IsAbsolute(path)) + return FileSystem::FileExists(path.c_str()); + + return Host::ResourceFileExists(path.c_str()); +} + +static bool PreprocessorReadFileCallback(const std::string& path, std::string& data) +{ + std::optional rdata; + if (Path::IsAbsolute(path)) + rdata = FileSystem::ReadFileToString(path.c_str()); + else + rdata = Host::ReadResourceFileToString(path.c_str()); + if (!rdata.has_value()) + return false; + + data = std::move(rdata.value()); + return true; +} + static std::unique_ptr CreateRFXCodegen() { const bool debug_info = g_gpu_device ? g_gpu_device->IsDebugDevice() : false; @@ -253,18 +275,38 @@ PostProcessing::ReShadeFXShader::ReShadeFXShader() = default; PostProcessing::ReShadeFXShader::~ReShadeFXShader() = default; -bool PostProcessing::ReShadeFXShader::LoadFromFile(std::string name, const char* filename, bool only_config, +bool PostProcessing::ReShadeFXShader::LoadFromFile(std::string name, std::string filename, bool only_config, Error* error) +{ + std::optional data = FileSystem::ReadFileToString(filename.c_str(), error); + if (!data.has_value()) + { + Log_ErrorFmt("Failed to read '{}'.", filename); + return false; + } + + return LoadFromString(std::move(name), std::move(filename), std::move(data.value()), only_config, error); +} + +bool PostProcessing::ReShadeFXShader::LoadFromString(std::string name, std::string filename, std::string code, + bool only_config, Error* error) { DebugAssert(only_config || g_gpu_device); - m_filename = filename; m_name = std::move(name); + m_filename = std::move(filename); + + // Reshade's preprocessor expects this. + if (code.empty() || code.back() != '\n') + code.push_back('\n'); reshadefx::module temp_module; if (!CreateModule(only_config ? DEFAULT_BUFFER_WIDTH : g_gpu_device->GetWindowWidth(), - only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), &temp_module, error)) + only_config ? DEFAULT_BUFFER_HEIGHT : g_gpu_device->GetWindowHeight(), &temp_module, + std::move(code), error)) + { return false; + } if (!CreateOptions(temp_module, error)) return false; @@ -318,12 +360,27 @@ bool PostProcessing::ReShadeFXShader::IsValid() const } bool PostProcessing::ReShadeFXShader::CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::module* mod, - Error* error) + std::string code, Error* error) { reshadefx::preprocessor pp; - pp.add_include_path(std::string(Path::GetDirectory(m_filename))); - pp.add_include_path(std::string(Path::Combine( - EmuFolders::Resources, "shaders" FS_OSPATH_SEPARATOR_STR "reshade" FS_OSPATH_SEPARATOR_STR "Shaders"))); + pp.set_include_callbacks(PreprocessorFileExistsCallback, PreprocessorReadFileCallback); + + if (Path::IsAbsolute(m_filename)) + { + // we're a real file, so include that directory + pp.add_include_path(std::string(Path::GetDirectory(m_filename))); + } + else + { + // we're a resource, include the resource subdirectory, if there is one + if (std::string_view resdir = Path::GetDirectory(m_filename); !resdir.empty()) + pp.add_include_path(std::string(resdir)); + } + + // root of the user directory, and resources + pp.add_include_path(Path::Combine(EmuFolders::Shaders, "reshade" FS_OSPATH_SEPARATOR_STR "Shaders")); + pp.add_include_path("shaders/reshade/Shaders"); + pp.add_macro_definition("__RESHADE__", "50901"); pp.add_macro_definition("BUFFER_WIDTH", std::to_string(buffer_width)); // TODO: can we make these uniforms? pp.add_macro_definition("BUFFER_HEIGHT", std::to_string(buffer_height)); @@ -350,7 +407,7 @@ bool PostProcessing::ReShadeFXShader::CreateModule(s32 buffer_width, s32 buffer_ break; } - if (!pp.append_file(m_filename)) + if (!pp.append_string(std::move(code), m_filename)) { Error::SetString(error, fmt::format("Failed to preprocess:\n{}", pp.errors())); return false; @@ -1013,9 +1070,20 @@ bool PostProcessing::ReShadeFXShader::CompilePipeline(GPUTexture::Format format, m_textures.clear(); m_passes.clear(); + std::string fxcode; + if (!PreprocessorReadFileCallback(m_filename, fxcode)) + { + Log_ErrorFmt("Failed to re-read shader for pipeline: '{}'", m_filename); + return false; + } + + // Reshade's preprocessor expects this. + if (fxcode.empty() || fxcode.back() != '\n') + fxcode.push_back('\n'); + Error error; reshadefx::module mod; - if (!CreateModule(width, height, &mod, &error)) + if (!CreateModule(width, height, &mod, std::move(fxcode), &error)) { Log_ErrorPrintf("Failed to create module for '%s': %s", m_name.c_str(), error.GetDescription().c_str()); return false; diff --git a/src/util/postprocessing_shader_fx.h b/src/util/postprocessing_shader_fx.h index 2d09e43ce..4aabcbcc9 100644 --- a/src/util/postprocessing_shader_fx.h +++ b/src/util/postprocessing_shader_fx.h @@ -24,7 +24,8 @@ public: bool IsValid() const override; - bool LoadFromFile(std::string name, const char* filename, bool only_config, Error* error); + bool LoadFromFile(std::string name, std::string filename, bool only_config, Error* error); + bool LoadFromString(std::string name, std::string filename, std::string code, bool only_config, Error* error); bool ResizeOutput(GPUTexture::Format format, u32 width, u32 height) override; bool CompilePipeline(GPUTexture::Format format, u32 width, u32 height) override; @@ -73,7 +74,7 @@ private: ShaderOption::ValueVector value; }; - bool CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::module* mod, Error* error); + bool CreateModule(s32 buffer_width, s32 buffer_height, reshadefx::module* mod, std::string code, Error* error); bool CreateOptions(const reshadefx::module& mod, Error* error); bool GetSourceOption(const reshadefx::uniform_info& ui, SourceOptionType* si, Error* error); bool CreatePasses(GPUTexture::Format backbuffer_format, reshadefx::module& mod, Error* error);