From b217f64bcf956e0e178cfd4932a1e4817c4882b9 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Sun, 27 Aug 2023 22:48:40 +1000 Subject: [PATCH] PostProcessing: Refactor config to use separate sections --- src/common/file_system.cpp | 8 +- src/common/file_system.h | 4 +- src/core/core.vcxproj | 2 +- src/core/core.vcxproj.filters | 2 +- src/core/fullscreen_ui.cpp | 183 ++--- src/core/gpu.cpp | 33 +- src/core/gpu.h | 6 - src/core/hotkeys.cpp | 5 +- src/core/settings.cpp | 7 - src/core/settings.h | 2 - src/core/system.cpp | 55 +- src/core/system.h | 8 +- src/duckstation-qt/CMakeLists.txt | 5 - src/duckstation-qt/displaysettingswidget.cpp | 1 - src/duckstation-qt/duckstation-qt.vcxproj | 9 - .../duckstation-qt.vcxproj.filters | 9 +- .../postprocessingchainconfigwidget.cpp | 219 ------ .../postprocessingchainconfigwidget.h | 58 -- .../postprocessingchainconfigwidget.ui | 163 ----- .../postprocessingsettingswidget.cpp | 395 ++++++++-- .../postprocessingsettingswidget.h | 56 +- .../postprocessingsettingswidget.ui | 141 +++- .../postprocessingshaderconfigwidget.cpp | 175 ----- .../postprocessingshaderconfigwidget.h | 55 -- src/duckstation-qt/qthost.cpp | 16 +- src/duckstation-qt/qthost.h | 1 + src/util/CMakeLists.txt | 4 +- src/util/postprocessing.cpp | 677 ++++++++++++++++++ src/util/postprocessing.h | 134 ++++ src/util/postprocessing_chain.cpp | 279 -------- src/util/postprocessing_chain.h | 61 -- src/util/postprocessing_shader.cpp | 199 ++--- src/util/postprocessing_shader.h | 77 +- src/util/postprocessing_shader_glsl.cpp | 108 +-- src/util/postprocessing_shader_glsl.h | 16 +- src/util/util.vcxproj | 6 +- src/util/util.vcxproj.filters | 6 +- 37 files changed, 1630 insertions(+), 1555 deletions(-) delete mode 100644 src/duckstation-qt/postprocessingchainconfigwidget.cpp delete mode 100644 src/duckstation-qt/postprocessingchainconfigwidget.h delete mode 100644 src/duckstation-qt/postprocessingchainconfigwidget.ui delete mode 100644 src/duckstation-qt/postprocessingshaderconfigwidget.cpp delete mode 100644 src/duckstation-qt/postprocessingshaderconfigwidget.h create mode 100644 src/util/postprocessing.cpp create mode 100644 src/util/postprocessing.h delete mode 100644 src/util/postprocessing_chain.cpp delete mode 100644 src/util/postprocessing_chain.h diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp index c83c31e07..3a8bc73c0 100644 --- a/src/common/file_system.cpp +++ b/src/common/file_system.cpp @@ -743,9 +743,9 @@ s64 FileSystem::GetPathFileSize(const char* Path) return sd.Size; } -std::optional> FileSystem::ReadBinaryFile(const char* filename) +std::optional> FileSystem::ReadBinaryFile(const char* filename, Error* error) { - ManagedCFilePtr fp = OpenManagedCFile(filename, "rb"); + ManagedCFilePtr fp = OpenManagedCFile(filename, "rb", error); if (!fp) return std::nullopt; @@ -767,9 +767,9 @@ std::optional> FileSystem::ReadBinaryFile(std::FILE* fp) return res; } -std::optional FileSystem::ReadFileToString(const char* filename) +std::optional FileSystem::ReadFileToString(const char* filename, Error* error) { - ManagedCFilePtr fp = OpenManagedCFile(filename, "rb"); + ManagedCFilePtr fp = OpenManagedCFile(filename, "rb", error); if (!fp) return std::nullopt; diff --git a/src/common/file_system.h b/src/common/file_system.h index 1bad77af0..6260d6346 100644 --- a/src/common/file_system.h +++ b/src/common/file_system.h @@ -140,9 +140,9 @@ private: }; #endif -std::optional> ReadBinaryFile(const char* filename); +std::optional> ReadBinaryFile(const char* filename, Error* error = nullptr); std::optional> ReadBinaryFile(std::FILE* fp); -std::optional ReadFileToString(const char* filename); +std::optional ReadFileToString(const char* filename, Error* error = nullptr); std::optional ReadFileToString(std::FILE* fp); bool WriteBinaryFile(const char* filename, const void* data, size_t data_length); bool WriteStringToFile(const char* filename, const std::string_view& sv); diff --git a/src/core/core.vcxproj b/src/core/core.vcxproj index a41b44cb0..47dfbbd09 100644 --- a/src/core/core.vcxproj +++ b/src/core/core.vcxproj @@ -190,4 +190,4 @@ - \ No newline at end of file + diff --git a/src/core/core.vcxproj.filters b/src/core/core.vcxproj.filters index 6aecabbdc..91b13258f 100644 --- a/src/core/core.vcxproj.filters +++ b/src/core/core.vcxproj.filters @@ -121,4 +121,4 @@ - \ No newline at end of file + diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index a1e7a75ef..f32d98ff4 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -24,9 +24,10 @@ #include "util/imgui_manager.h" #include "util/ini_settings_interface.h" #include "util/input_manager.h" -#include "util/postprocessing_chain.h" +#include "util/postprocessing.h" #include "common/byte_stream.h" +#include "common/error.h" #include "common/file_system.h" #include "common/log.h" #include "common/make_array.h" @@ -201,6 +202,12 @@ enum class GameListPage Count }; +struct PostProcessingStageInfo +{ + std::string name; + std::vector options; +}; + ////////////////////////////////////////////////////////////////////////// // Utility ////////////////////////////////////////////////////////////////////////// @@ -382,8 +389,7 @@ static void DrawFolderSetting(SettingsInterface* bsi, const char* title, const c static void PopulateGraphicsAdapterList(); static void PopulateGameListDirectoryCache(SettingsInterface* si); -static void PopulatePostProcessingChain(); -static void SavePostProcessingChain(); +static void PopulatePostProcessingChain(SettingsInterface* si); static void BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, const std::string_view& section, const std::string_view& key, const std::string_view& display_name); static void DrawInputBindingWindow(); @@ -398,7 +404,7 @@ static std::unique_ptr s_game_settings_entry; static std::vector> s_game_list_directories_cache; static std::vector s_graphics_adapter_list_cache; static std::vector s_fullscreen_mode_list_cache; -static PostProcessingChain s_postprocessing_chain; +static std::vector s_postprocessing_stages; static std::vector s_hotkey_list_cache; static std::atomic_bool s_settings_changed{false}; static std::atomic_bool s_game_settings_changed{false}; @@ -741,7 +747,7 @@ void FullscreenUI::Shutdown() s_cover_image_map.clear(); s_game_list_sorted_entries = {}; s_game_list_directories_cache = {}; - s_postprocessing_chain.ClearStages(); + s_postprocessing_stages = {}; s_fullscreen_mode_list_cache = {}; s_graphics_adapter_list_cache = {}; s_hotkey_list_cache = {}; @@ -2386,7 +2392,7 @@ void FullscreenUI::SwitchToSettings() s_game_settings_interface.reset(); PopulateGraphicsAdapterList(); - PopulatePostProcessingChain(); + PopulatePostProcessingChain(GetEditingSettingsInterface()); s_current_main_window = MainWindowType::Settings; s_settings_page = SettingsPage::Interface; @@ -3967,18 +3973,17 @@ void FullscreenUI::DrawDisplaySettingsPage() EndMenuButtons(); } -void FullscreenUI::PopulatePostProcessingChain() +void FullscreenUI::PopulatePostProcessingChain(SettingsInterface* si) { - std::string chain_value(GetEditingSettingsInterface()->GetStringValue("Display", "PostProcessChain", "")); - s_postprocessing_chain.CreateFromString(chain_value); -} - -void FullscreenUI::SavePostProcessingChain() -{ - SettingsInterface* bsi = GetEditingSettingsInterface(); - const std::string config(s_postprocessing_chain.GetConfigString()); - bsi->SetStringValue("Display", "PostProcessChain", config.c_str()); - SetSettingsChanged(bsi); + const u32 stages = PostProcessing::Config::GetStageCount(*si); + s_postprocessing_stages.reserve(stages); + for (u32 i = 0; i < stages; i++) + { + PostProcessingStageInfo psi; + psi.name = PostProcessing::Config::GetStageShaderName(*si, i); + psi.options = PostProcessing::Config::GetStageOptions(*si, i); + s_postprocessing_stages.push_back(std::move(psi)); + } } enum @@ -4005,7 +4010,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage() FSUI_CSTR("Reloads the shaders from disk, applying any changes."), bsi->GetBoolValue("Display", "PostProcessing", false))) { - if (!g_gpu || g_gpu->UpdatePostProcessingChain()) + if (System::IsValid() && PostProcessing::ReloadShaders()) ShowToast(std::string(), FSUI_STR("Post-processing shaders reloaded.")); } @@ -4013,25 +4018,32 @@ void FullscreenUI::DrawPostProcessingSettingsPage() if (MenuButton(FSUI_ICONSTR(ICON_FA_PLUS, "Add Shader"), FSUI_CSTR("Adds a new shader to the chain."))) { + std::vector> shaders = PostProcessing::GetAvailableShaderNames(); ImGuiFullscreen::ChoiceDialogOptions options; - for (std::string& name : PostProcessingChain::GetAvailableShaderNames()) - options.emplace_back(std::move(name), false); + options.reserve(shaders.size()); + for (auto& [display_name, name] : shaders) + options.emplace_back(std::move(display_name), false); OpenChoiceDialog(FSUI_ICONSTR(ICON_FA_PLUS, "Add Shader"), false, std::move(options), - [](s32 index, const std::string& title, bool checked) { - if (index < 0) + [shaders = std::move(shaders)](s32 index, const std::string& title, bool checked) { + if (index < 0 || static_cast(index) >= shaders.size()) return; - if (s_postprocessing_chain.AddStage(title)) + const std::string& shader_name = shaders[index].second; + SettingsInterface* bsi = GetEditingSettingsInterface(); + Error error; + if (PostProcessing::Config::AddStage(*bsi, shader_name, &error)) { ShowToast(std::string(), fmt::format(FSUI_FSTR("Shader {} added as stage {}."), title, - s_postprocessing_chain.GetStageCount())); - SavePostProcessingChain(); + PostProcessing::Config::GetStageCount(*bsi))); + PopulatePostProcessingChain(bsi); + SetSettingsChanged(bsi); } else { ShowToast(std::string(), - fmt::format(FSUI_FSTR("Failed to load shader {}. It may be invalid."), title)); + fmt::format(FSUI_FSTR("Failed to load shader {}. It may be invalid.\nError was:"), + title, error.GetDescription())); } CloseChoiceDialog(); @@ -4047,9 +4059,11 @@ void FullscreenUI::DrawPostProcessingSettingsPage() if (!confirmed) return; - s_postprocessing_chain.ClearStages(); + SettingsInterface* bsi = GetEditingSettingsInterface(); + PostProcessing::Config::ClearStages(*bsi); + PopulatePostProcessingChain(bsi); + SetSettingsChanged(bsi); ShowToast(std::string(), FSUI_STR("Post-processing chain cleared.")); - SavePostProcessingChain(); }); } @@ -4058,11 +4072,12 @@ void FullscreenUI::DrawPostProcessingSettingsPage() SmallString str; SmallString tstr; - for (u32 stage_index = 0; stage_index < s_postprocessing_chain.GetStageCount(); stage_index++) + for (u32 stage_index = 0; stage_index < static_cast(s_postprocessing_stages.size()); stage_index++) { + PostProcessingStageInfo& si = s_postprocessing_stages[stage_index]; + ImGui::PushID(stage_index); - PostProcessingShader* stage = s_postprocessing_chain.GetShaderStage(stage_index); - str.Fmt(FSUI_FSTR("Stage {}: {}"), stage_index + 1, stage->GetName()); + str.Fmt(FSUI_FSTR("Stage {}: {}"), stage_index + 1, si.name); MenuHeading(str); if (MenuButton(FSUI_ICONSTR(ICON_FA_TIMES, "Remove From Chain"), FSUI_CSTR("Removes this shader from the chain."))) @@ -4080,17 +4095,20 @@ void FullscreenUI::DrawPostProcessingSettingsPage() if (MenuButton(FSUI_ICONSTR(ICON_FA_ARROW_DOWN, "Move Down"), FSUI_CSTR("Moves this shader lower in the chain, applying it later."), - (stage_index != (s_postprocessing_chain.GetStageCount() - 1)))) + (stage_index != (s_postprocessing_stages.size() - 1)))) { postprocessing_action = POSTPROCESSING_ACTION_MOVE_DOWN; postprocessing_action_index = stage_index; } - for (PostProcessingShader::Option& opt : stage->GetOptions()) + for (PostProcessing::ShaderOption& opt : si.options) { + if (opt.ui_name.empty()) + continue; + switch (opt.type) { - case PostProcessingShader::Option::Type::Bool: + case PostProcessing::ShaderOption::Type::Bool: { bool value = (opt.value[0].int_value != 0); tstr.Fmt(ICON_FA_COGS "{}", opt.ui_name); @@ -4100,12 +4118,13 @@ void FullscreenUI::DrawPostProcessingSettingsPage() &value)) { opt.value[0].int_value = (value != 0); - SavePostProcessingChain(); + PostProcessing::Config::SetStageOption(*bsi, stage_index, opt); + SetSettingsChanged(bsi); } } break; - case PostProcessingShader::Option::Type::Float: + case PostProcessing::ShaderOption::Type::Float: { tstr.Fmt(ICON_FA_RULER_VERTICAL "{}##{}", opt.ui_name, opt.name); str.Fmt(FSUI_FSTR("Value: {} | Default: {} | Minimum: {} | Maximum: {}"), opt.value[0].float_value, @@ -4148,48 +4167,44 @@ void FullscreenUI::DrawPostProcessingSettingsPage() } #else ImGui::SetNextItemWidth(end); + + bool changed = false; switch (opt.vector_size) { case 1: { - if (ImGui::SliderFloat("##value", &opt.value[0].float_value, opt.min_value[0].float_value, - opt.max_value[0].float_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderFloat("##value", &opt.value[0].float_value, opt.min_value[0].float_value, + opt.max_value[0].float_value); } break; case 2: { - if (ImGui::SliderFloat2("##value", &opt.value[0].float_value, opt.min_value[0].float_value, - opt.max_value[0].float_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderFloat2("##value", &opt.value[0].float_value, opt.min_value[0].float_value, + opt.max_value[0].float_value); } break; case 3: { - if (ImGui::SliderFloat3("##value", &opt.value[0].float_value, opt.min_value[0].float_value, - opt.max_value[0].float_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderFloat3("##value", &opt.value[0].float_value, opt.min_value[0].float_value, + opt.max_value[0].float_value); } break; case 4: { - if (ImGui::SliderFloat4("##value", &opt.value[0].float_value, opt.min_value[0].float_value, - opt.max_value[0].float_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderFloat4("##value", &opt.value[0].float_value, opt.min_value[0].float_value, + opt.max_value[0].float_value); } break; } + + if (changed) + { + PostProcessing::Config::SetStageOption(*bsi, stage_index, opt); + SetSettingsChanged(bsi); + } #endif ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); @@ -4208,7 +4223,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage() } break; - case PostProcessingShader::Option::Type::Int: + case PostProcessing::ShaderOption::Type::Int: { tstr.Fmt(ICON_FA_RULER_VERTICAL "{}##{}", opt.ui_name, opt.name); str.Fmt(FSUI_FSTR("Value: {} | Default: {} | Minimum: {} | Maximum: {}"), opt.value[0].int_value, @@ -4250,49 +4265,44 @@ void FullscreenUI::DrawPostProcessingSettingsPage() } } #else + bool changed = false; ImGui::SetNextItemWidth(end); switch (opt.vector_size) { case 1: { - if (ImGui::SliderInt("##value", &opt.value[0].int_value, opt.min_value[0].int_value, - opt.max_value[0].int_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderInt("##value", &opt.value[0].int_value, opt.min_value[0].int_value, + opt.max_value[0].int_value); } break; case 2: { - if (ImGui::SliderInt2("##value", &opt.value[0].int_value, opt.min_value[0].int_value, - opt.max_value[0].int_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderInt2("##value", &opt.value[0].int_value, opt.min_value[0].int_value, + opt.max_value[0].int_value); } break; case 3: { - if (ImGui::SliderInt2("##value", &opt.value[0].int_value, opt.min_value[0].int_value, - opt.max_value[0].int_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderInt2("##value", &opt.value[0].int_value, opt.min_value[0].int_value, + opt.max_value[0].int_value); } break; case 4: { - if (ImGui::SliderInt4("##value", &opt.value[0].int_value, opt.min_value[0].int_value, - opt.max_value[0].int_value)) - { - SavePostProcessingChain(); - } + changed = ImGui::SliderInt4("##value", &opt.value[0].int_value, opt.min_value[0].int_value, + opt.max_value[0].int_value); } break; } + + if (changed) + { + PostProcessing::Config::SetStageOption(*bsi, stage_index, opt); + SetSettingsChanged(bsi); + } #endif ImGui::SetCursorPosY(ImGui::GetCursorPosY() + LayoutScale(10.0f)); @@ -4320,23 +4330,26 @@ void FullscreenUI::DrawPostProcessingSettingsPage() { case POSTPROCESSING_ACTION_REMOVE: { - PostProcessingShader* stage = s_postprocessing_chain.GetShaderStage(postprocessing_action_index); + const PostProcessingStageInfo& si = s_postprocessing_stages[postprocessing_action_index]; ShowToast(std::string(), - fmt::format(FSUI_FSTR("Removed stage {} ({})."), postprocessing_action_index + 1, stage->GetName())); - s_postprocessing_chain.RemoveStage(postprocessing_action_index); - SavePostProcessingChain(); + fmt::format(FSUI_FSTR("Removed stage {} ({})."), postprocessing_action_index + 1, si.name)); + PostProcessing::Config::RemoveStage(*bsi, postprocessing_action_index); + PopulatePostProcessingChain(bsi); + SetSettingsChanged(bsi); } break; case POSTPROCESSING_ACTION_MOVE_UP: { - s_postprocessing_chain.MoveStageUp(postprocessing_action_index); - SavePostProcessingChain(); + PostProcessing::Config::MoveStageUp(*bsi, postprocessing_action_index); + PopulatePostProcessingChain(bsi); + SetSettingsChanged(bsi); } break; case POSTPROCESSING_ACTION_MOVE_DOWN: { - s_postprocessing_chain.MoveStageDown(postprocessing_action_index); - SavePostProcessingChain(); + PostProcessing::Config::MoveStageDown(*bsi, postprocessing_action_index); + PopulatePostProcessingChain(bsi); + SetSettingsChanged(bsi); } break; default: diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index c1be83ddc..4de7048c6 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -12,7 +12,7 @@ #include "util/gpu_device.h" #include "util/imgui_manager.h" -#include "util/postprocessing_chain.h" +#include "util/postprocessing.h" #include "util/shadergen.h" #include "util/state_wrapper.h" @@ -65,8 +65,6 @@ bool GPU::Initialize() return false; } - UpdatePostProcessingChain(); - g_gpu_device->SetGPUTimingEnabled(g_settings.display_show_gpu); return true; @@ -1637,12 +1635,12 @@ bool GPU::RenderDisplay(GPUFramebuffer* target, const Common::Rectangle& dr (target && target->GetRT()) ? target->GetRT()->GetFormat() : g_gpu_device->GetWindowFormat(); const u32 target_width = target ? target->GetWidth() : g_gpu_device->GetWindowWidth(); const u32 target_height = target ? target->GetHeight() : g_gpu_device->GetWindowHeight(); - const bool really_postfx = (postfx && HasDisplayTexture() && m_post_processing_chain && - m_post_processing_chain->CheckTargets(hdformat, target_width, target_height)); + const bool really_postfx = (postfx && HasDisplayTexture() && PostProcessing::IsActive() && + PostProcessing::CheckTargets(hdformat, target_width, target_height)); if (really_postfx) { - g_gpu_device->ClearRenderTarget(m_post_processing_chain->GetInputTexture(), 0); - g_gpu_device->SetFramebuffer(m_post_processing_chain->GetInputFramebuffer()); + g_gpu_device->ClearRenderTarget(PostProcessing::GetInputTexture(), 0); + g_gpu_device->SetFramebuffer(PostProcessing::GetInputFramebuffer()); } else { @@ -1676,7 +1674,7 @@ bool GPU::RenderDisplay(GPUFramebuffer* target, const Common::Rectangle& dr if (really_postfx) { - return m_post_processing_chain->Apply(target, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), + return PostProcessing::Apply(target, draw_rect.left, draw_rect.top, draw_rect.GetWidth(), draw_rect.GetHeight(), m_display_texture_view_width, m_display_texture_view_height); } @@ -1686,25 +1684,6 @@ bool GPU::RenderDisplay(GPUFramebuffer* target, const Common::Rectangle& dr } } -bool GPU::UpdatePostProcessingChain() -{ - if (!g_settings.display_post_processing || g_settings.display_post_process_chain.empty()) - { - m_post_processing_chain.reset(); - return true; - } - - m_post_processing_chain = std::make_unique(); - if (!m_post_processing_chain->CreateFromString(g_settings.display_post_process_chain)) - { - Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Failed to load post processing shader chain."), 20.0f); - m_post_processing_chain.reset(); - return false; - } - - return true; -} - Common::Rectangle GPU::CalculateDrawRect(s32 window_width, s32 window_height, float* out_left_padding, float* out_top_padding, float* out_scale, float* out_x_scale, bool apply_aspect_ratio /* = true */) const diff --git a/src/core/gpu.h b/src/core/gpu.h index bd579be1b..18c55b423 100644 --- a/src/core/gpu.h +++ b/src/core/gpu.h @@ -27,8 +27,6 @@ class GPUFramebuffer; class GPUTexture; class GPUPipeline; -class PostProcessingChain; - class TimingEvent; namespace Threading { @@ -207,8 +205,6 @@ public: /// Draws the current display texture, with any post-processing. bool PresentDisplay(); - bool UpdatePostProcessingChain(); - protected: TickCount CRTCTicksToSystemTicks(TickCount crtc_ticks, TickCount fractional_ticks) const; TickCount SystemTicksToCRTCTicks(TickCount sysclk_ticks, TickCount* fractional_ticks) const; @@ -603,8 +599,6 @@ protected: s32 m_display_texture_view_width = 0; s32 m_display_texture_view_height = 0; - std::unique_ptr m_post_processing_chain; - struct Stats { u32 num_vram_reads; diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index 00d58a254..801d34a44 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -15,6 +15,7 @@ #include "util/gpu_device.h" #include "util/input_manager.h" +#include "util/postprocessing.h" #include "common/file_system.h" @@ -345,13 +346,13 @@ DEFINE_HOTKEY("DecreaseResolutionScale", TRANSLATE_NOOP("Hotkeys", "Graphics"), DEFINE_HOTKEY("TogglePostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Toggle Post-Processing"), [](s32 pressed) { if (!pressed && System::IsValid()) - System::TogglePostProcessing(); + PostProcessing::Toggle(); }) DEFINE_HOTKEY("ReloadPostProcessingShaders", TRANSLATE_NOOP("Hotkeys", "Graphics"), TRANSLATE_NOOP("Hotkeys", "Reload Post Processing Shaders"), [](s32 pressed) { if (!pressed && System::IsValid()) - System::ReloadPostProcessingShaders(); + PostProcessing::ReloadShaders(); }) DEFINE_HOTKEY("ReloadTextureReplacements", TRANSLATE_NOOP("Hotkeys", "Graphics"), diff --git a/src/core/settings.cpp b/src/core/settings.cpp index 66431b07b..9b91bb1fa 100644 --- a/src/core/settings.cpp +++ b/src/core/settings.cpp @@ -264,7 +264,6 @@ void Settings::Load(SettingsInterface& si) display_linear_filtering = si.GetBoolValue("Display", "LinearFiltering", true); display_integer_scaling = si.GetBoolValue("Display", "IntegerScaling", false); display_stretch = si.GetBoolValue("Display", "Stretch", false); - display_post_processing = si.GetBoolValue("Display", "PostProcessing", false); display_show_osd_messages = si.GetBoolValue("Display", "ShowOSDMessages", true); display_show_fps = si.GetBoolValue("Display", "ShowFPS", false); display_show_speed = si.GetBoolValue("Display", "ShowSpeed", false); @@ -279,7 +278,6 @@ void Settings::Load(SettingsInterface& si) display_internal_resolution_screenshots = si.GetBoolValue("Display", "InternalResolutionScreenshots", false); display_stretch_vertically = si.GetBoolValue("Display", "StretchVertically", false); video_sync_enabled = si.GetBoolValue("Display", "VSync", DEFAULT_VSYNC_VALUE); - display_post_process_chain = si.GetStringValue("Display", "PostProcessChain", ""); display_max_fps = si.GetFloatValue("Display", "MaxFPS", DEFAULT_DISPLAY_MAX_FPS); display_osd_scale = si.GetFloatValue("Display", "OSDScale", DEFAULT_OSD_SCALE); @@ -486,7 +484,6 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Display", "LinearFiltering", display_linear_filtering); si.SetBoolValue("Display", "IntegerScaling", display_integer_scaling); si.SetBoolValue("Display", "Stretch", display_stretch); - si.SetBoolValue("Display", "PostProcessing", display_post_processing); si.SetBoolValue("Display", "ShowOSDMessages", display_show_osd_messages); si.SetBoolValue("Display", "ShowFPS", display_show_fps); si.SetBoolValue("Display", "ShowSpeed", display_show_speed); @@ -501,10 +498,6 @@ void Settings::Save(SettingsInterface& si) const si.SetBoolValue("Display", "InternalResolutionScreenshots", display_internal_resolution_screenshots); si.SetBoolValue("Display", "StretchVertically", display_stretch_vertically); si.SetBoolValue("Display", "VSync", video_sync_enabled); - if (display_post_process_chain.empty()) - si.DeleteValue("Display", "PostProcessChain"); - else - si.SetStringValue("Display", "PostProcessChain", display_post_process_chain.c_str()); si.SetFloatValue("Display", "MaxFPS", display_max_fps); si.SetFloatValue("Display", "OSDScale", display_osd_scale); diff --git a/src/core/settings.h b/src/core/settings.h index 19cbda1d5..f423dfbe1 100644 --- a/src/core/settings.h +++ b/src/core/settings.h @@ -94,7 +94,6 @@ struct Settings GPURenderer gpu_renderer = DEFAULT_GPU_RENDERER; std::string gpu_adapter; - std::string display_post_process_chain; u32 gpu_resolution_scale = 1; u32 gpu_multisamples = 1; bool gpu_use_thread = true; @@ -132,7 +131,6 @@ struct Settings bool display_linear_filtering = true; bool display_integer_scaling = false; bool display_stretch = false; - bool display_post_processing = false; bool display_show_osd_messages = true; bool display_show_fps = false; bool display_show_speed = false; diff --git a/src/core/system.cpp b/src/core/system.cpp index 80e7fb09c..cb271e9fd 100644 --- a/src/core/system.cpp +++ b/src/core/system.cpp @@ -41,6 +41,7 @@ #include "util/input_manager.h" #include "util/iso_reader.h" #include "util/platform_misc.h" +#include "util/postprocessing.h" #include "util/state_wrapper.h" #include "common/error.h" @@ -117,7 +118,7 @@ static void DestroySystem(); static std::string GetMediaPathFromSaveState(const char* path); static bool DoLoadState(ByteStream* stream, bool force_software_renderer, bool update_display); static bool DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_display, bool is_memory_state); -static bool CreateGPU(GPURenderer renderer); +static bool CreateGPU(GPURenderer renderer, bool is_switching); static bool SaveUndoLoadState(); /// Throttles the system, i.e. sleeps until it's time to execute the next frame. @@ -869,7 +870,7 @@ std::string System::GetInputProfilePath(const std::string_view& name) return Path::Combine(EmuFolders::InputProfiles, fmt::format("{}.ini", name)); } -bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_display, bool update_display /* = true*/) +bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_device, bool update_display /* = true*/) { ClearMemorySaveStates(); g_gpu->RestoreGraphicsAPIState(); @@ -883,10 +884,10 @@ bool System::RecreateGPU(GPURenderer renderer, bool force_recreate_display, bool // create new renderer g_gpu.reset(); - if (force_recreate_display) + if (force_recreate_device) Host::ReleaseGPUDevice(); - if (!CreateGPU(renderer)) + if (!CreateGPU(renderer, true)) { if (!IsStartupCancelled()) Host::ReportErrorAsync("Error", "Failed to recreate GPU."); @@ -1530,7 +1531,7 @@ bool System::Initialize(bool force_software_renderer) return false; } - if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer)) + if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false)) { Bus::Shutdown(); CPU::Shutdown(); @@ -1571,6 +1572,7 @@ bool System::Initialize(bool force_software_renderer) MDEC::Initialize(); SIO::Initialize(); PCDrv::Initialize(); + PostProcessing::Initialize(); static constexpr float WARNING_DURATION = 15.0f; @@ -1619,6 +1621,8 @@ void System::DestroySystem() Host::ClearOSDMessages(); + PostProcessing::Shutdown(); + SaveStateSelectorUI::Close(true); FullscreenUI::OnSystemDestroyed(); @@ -1971,7 +1975,7 @@ void System::RecreateSystem() PauseSystem(true); } -bool System::CreateGPU(GPURenderer renderer) +bool System::CreateGPU(GPURenderer renderer, bool is_switching) { const RenderAPI api = Settings::GetRenderAPIForRenderer(renderer); @@ -1981,6 +1985,7 @@ bool System::CreateGPU(GPURenderer renderer) { Log_WarningPrintf("Recreating GPU device, expecting %s got %s", GPUDevice::RenderAPIToString(api), GPUDevice::RenderAPIToString(g_gpu_device->GetRenderAPI())); + PostProcessing::Shutdown(); } Host::ReleaseGPUDevice(); @@ -1989,6 +1994,9 @@ bool System::CreateGPU(GPURenderer renderer) Host::ReleaseRenderWindow(); return false; } + + if (is_switching) + PostProcessing::Initialize(); } if (renderer == GPURenderer::Software) @@ -3482,13 +3490,13 @@ void System::CheckForSettingsChanges(const Settings& old_settings) g_settings.gpu_threaded_presentation != old_settings.gpu_threaded_presentation)) { // if debug device/threaded presentation change, we need to recreate the whole display - const bool recreate_display = (g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device || + const bool recreate_device = (g_settings.gpu_use_debug_device != old_settings.gpu_use_debug_device || g_settings.gpu_threaded_presentation != old_settings.gpu_threaded_presentation); Host::AddFormattedOSDMessage(5.0f, TRANSLATE("OSDMessage", "Switching to %s%s GPU renderer."), Settings::GetRendererName(g_settings.gpu_renderer), g_settings.gpu_use_debug_device ? " (debug)" : ""); - RecreateGPU(g_settings.gpu_renderer, recreate_display); + RecreateGPU(g_settings.gpu_renderer, recreate_device); } if (IsValid()) @@ -3661,12 +3669,6 @@ void System::CheckForSettingsChanges(const Settings& old_settings) UpdateSpeedLimiterState(); } - if (g_settings.display_post_processing != old_settings.display_post_processing || - g_settings.display_post_process_chain != old_settings.display_post_process_chain) - { - g_gpu->UpdatePostProcessingChain(); - } - if (g_settings.inhibit_screensaver != old_settings.inhibit_screensaver) { if (g_settings.inhibit_screensaver) @@ -3674,6 +3676,8 @@ void System::CheckForSettingsChanges(const Settings& old_settings) else PlatformMisc::ResumeScreensaver(); } + + PostProcessing::UpdateSettings(); } if (g_gpu_device && g_settings.display_osd_scale != old_settings.display_osd_scale) @@ -4525,29 +4529,6 @@ void System::ApplyCheatCode(u32 index) } } -void System::TogglePostProcessing() -{ - if (!IsValid()) - return; - - g_settings.display_post_processing = !g_settings.display_post_processing; - Host::AddKeyedOSDMessage("PostProcessing", - g_settings.display_post_processing ? - TRANSLATE_STR("OSDMessage", "Post-processing is now enabled.") : - TRANSLATE_STR("OSDMessage", "Post-processing is now disabled."), - Host::OSD_QUICK_DURATION); - g_gpu->UpdatePostProcessingChain(); -} - -void System::ReloadPostProcessingShaders() -{ - if (!IsValid() || !g_settings.display_post_processing) - return; - - if (!g_gpu->UpdatePostProcessingChain()) - Host::AddOSDMessage(TRANSLATE_STR("OSDMessage", "Post-processing shaders reloaded."), Host::OSD_ERROR_DURATION); -} - void System::ToggleWidescreen() { g_settings.gpu_widescreen_hack = !g_settings.gpu_widescreen_hack; diff --git a/src/core/system.h b/src/core/system.h index c2bb8f0b9..37f84ad29 100644 --- a/src/core/system.h +++ b/src/core/system.h @@ -246,7 +246,7 @@ void Execute(); void RecreateSystem(); /// Recreates the GPU component, saving/loading the state so it is preserved. Call when the GPU renderer changes. -bool RecreateGPU(GPURenderer renderer, bool force_recreate_display = false, bool update_display = true); +bool RecreateGPU(GPURenderer renderer, bool force_recreate_device = false, bool update_display = true); void SingleStepCPU(); @@ -425,12 +425,6 @@ void SetCheatCodeState(u32 index, bool enabled, bool save_to_file); /// Immediately applies the specified cheat code. void ApplyCheatCode(u32 index); -/// Temporarily toggles post-processing on/off. -void TogglePostProcessing(); - -/// Reloads post processing shaders with the current configuration. -void ReloadPostProcessingShaders(); - /// Toggle Widescreen Hack and Aspect Ratio void ToggleWidescreen(); diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index 25029807c..7d9ff95ee 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -113,14 +113,9 @@ set(SRCS memorycardsettingswidget.h memoryviewwidget.cpp memoryviewwidget.h - postprocessingchainconfigwidget.cpp - postprocessingchainconfigwidget.h - postprocessingchainconfigwidget.ui postprocessingsettingswidget.cpp postprocessingsettingswidget.h postprocessingsettingswidget.ui - postprocessingshaderconfigwidget.cpp - postprocessingshaderconfigwidget.h qthost.cpp qthost.h qtkeycodes.cpp diff --git a/src/duckstation-qt/displaysettingswidget.cpp b/src/duckstation-qt/displaysettingswidget.cpp index c9a0c0797..e14c6a048 100644 --- a/src/duckstation-qt/displaysettingswidget.cpp +++ b/src/duckstation-qt/displaysettingswidget.cpp @@ -4,7 +4,6 @@ #include "displaysettingswidget.h" #include "core/gpu.h" #include "core/settings.h" -#include "postprocessingchainconfigwidget.h" #include "qtutils.h" #include "settingsdialog.h" #include "settingwidgetbinder.h" diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 6e8afe734..45c9e6249 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -41,8 +41,6 @@ - - @@ -93,8 +91,6 @@ - - @@ -139,9 +135,6 @@ Document - - Document - Document @@ -263,8 +256,6 @@ - - diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index 51d1f5050..eff8c903c 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -48,15 +48,11 @@ - - - - @@ -136,8 +132,6 @@ - - @@ -171,7 +165,6 @@ - @@ -264,4 +257,4 @@ translations - \ No newline at end of file + diff --git a/src/duckstation-qt/postprocessingchainconfigwidget.cpp b/src/duckstation-qt/postprocessingchainconfigwidget.cpp deleted file mode 100644 index 51e559eba..000000000 --- a/src/duckstation-qt/postprocessingchainconfigwidget.cpp +++ /dev/null @@ -1,219 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#include "postprocessingchainconfigwidget.h" -#include "postprocessingshaderconfigwidget.h" -#include "qthost.h" - -#include "util/postprocessing_chain.h" - -#include -#include -#include - -PostProcessingChainConfigWidget::PostProcessingChainConfigWidget(QWidget* parent) : QWidget(parent) -{ - m_ui.setupUi(this); - connectUi(); - updateButtonStates(std::nullopt); -} - -PostProcessingChainConfigWidget::~PostProcessingChainConfigWidget() = default; - -void PostProcessingChainConfigWidget::connectUi() -{ - connect(m_ui.add, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onAddButtonClicked); - connect(m_ui.remove, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onRemoveButtonClicked); - connect(m_ui.clear, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onClearButtonClicked); - connect(m_ui.moveUp, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onMoveUpButtonClicked); - connect(m_ui.moveDown, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onMoveDownButtonClicked); - // connect(m_ui.reload, &QToolButton::clicked, this, &PostProcessingChainConfigWidget::onReloadButtonClicked); - connect(m_ui.shaderSettings, &QToolButton::clicked, this, - &PostProcessingChainConfigWidget::onShaderConfigButtonClicked); - connect(m_ui.shaders, &QListWidget::itemSelectionChanged, this, - &PostProcessingChainConfigWidget::onSelectedShaderChanged); - - // m_ui.loadPreset->setEnabled(false); - // m_ui.savePreset->setEnabled(false); -} - -bool PostProcessingChainConfigWidget::setConfigString(const std::string_view& config_string) -{ - if (!m_chain.CreateFromString(config_string)) - return false; - - updateList(); - return true; -} - -void PostProcessingChainConfigWidget::setOptionsButtonVisible(bool visible) -{ - if (visible) - { - m_ui.shaderSettings->setVisible(true); - m_ui.horizontalLayout->addWidget(m_ui.shaderSettings); - } - else - { - m_ui.shaderSettings->setVisible(false); - m_ui.horizontalLayout->removeWidget(m_ui.shaderSettings); - } -} - -std::optional PostProcessingChainConfigWidget::getSelectedIndex() const -{ - QList selected_items = m_ui.shaders->selectedItems(); - return selected_items.empty() ? std::nullopt : - std::optional(selected_items.first()->data(Qt::UserRole).toUInt()); -} - -void PostProcessingChainConfigWidget::updateList() -{ - m_ui.shaders->clear(); - - for (u32 i = 0; i < m_chain.GetStageCount(); i++) - { - const PostProcessingShader* shader = m_chain.GetShaderStage(i); - - QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(shader->GetName()), m_ui.shaders); - item->setData(Qt::UserRole, QVariant(i)); - } - - updateButtonStates(std::nullopt); -} - -void PostProcessingChainConfigWidget::configChanged() -{ - if (m_chain.IsEmpty()) - chainConfigStringChanged(std::string()); - else - chainConfigStringChanged(m_chain.GetConfigString()); -} - -void PostProcessingChainConfigWidget::updateButtonStates(std::optional index) -{ - m_ui.remove->setEnabled(index.has_value()); - m_ui.clear->setEnabled(!m_chain.IsEmpty()); - // m_ui.reload->setEnabled(!m_chain.IsEmpty()); - m_ui.shaderSettings->setEnabled(index.has_value() && (index.value() < m_chain.GetStageCount()) && - m_chain.GetShaderStage(index.value())->HasOptions()); - - if (index.has_value()) - { - m_ui.moveUp->setEnabled(index.value() > 0); - m_ui.moveDown->setEnabled(index.value() < (m_chain.GetStageCount() - 1u)); - } - else - { - m_ui.moveUp->setEnabled(false); - m_ui.moveDown->setEnabled(false); - } -} - -void PostProcessingChainConfigWidget::onAddButtonClicked() -{ - QMenu menu; - - const std::vector shaders(PostProcessingChain::GetAvailableShaderNames()); - if (shaders.empty()) - { - menu.addAction(tr("No Shaders Available"))->setEnabled(false); - } - else - { - for (const std::string& shader : shaders) - { - QAction* action = menu.addAction(QString::fromStdString(shader)); - connect(action, &QAction::triggered, [this, &shader]() { - chainAboutToChange(); - - if (!m_chain.AddStage(shader)) - { - QMessageBox::critical(this, tr("Error"), tr("Failed to add shader. The log may contain more information.")); - return; - } - - updateList(); - configChanged(); - }); - } - } - - menu.exec(QCursor::pos()); -} - -void PostProcessingChainConfigWidget::onRemoveButtonClicked() -{ - QList selected_items = m_ui.shaders->selectedItems(); - if (selected_items.empty()) - return; - - QListWidgetItem* item = selected_items.first(); - u32 index = item->data(Qt::UserRole).toUInt(); - if (index < m_chain.GetStageCount()) - { - chainAboutToChange(); - m_chain.RemoveStage(index); - updateList(); - configChanged(); - } -} - -void PostProcessingChainConfigWidget::onClearButtonClicked() -{ - if (QMessageBox::question(this, tr("Question"), tr("Are you sure you want to clear all shader stages?"), - QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) - { - chainAboutToChange(); - m_chain.ClearStages(); - updateList(); - configChanged(); - } -} - -void PostProcessingChainConfigWidget::onMoveUpButtonClicked() -{ - std::optional index = getSelectedIndex(); - if (index.has_value()) - { - chainAboutToChange(); - m_chain.MoveStageUp(index.value()); - updateList(); - configChanged(); - } -} - -void PostProcessingChainConfigWidget::onMoveDownButtonClicked() -{ - std::optional index = getSelectedIndex(); - if (index.has_value()) - { - chainAboutToChange(); - m_chain.MoveStageDown(index.value()); - updateList(); - configChanged(); - } -} - -void PostProcessingChainConfigWidget::onShaderConfigButtonClicked() -{ - std::optional index = getSelectedIndex(); - if (index.has_value() && index.value() < m_chain.GetStageCount()) - { - PostProcessingShaderConfigDialog shader_config(this, m_chain.GetShaderStage(index.value())); - connect(&shader_config, &PostProcessingShaderConfigDialog::configChanged, [this]() { configChanged(); }); - shader_config.exec(); - } -} - -void PostProcessingChainConfigWidget::onReloadButtonClicked() -{ - g_emu_thread->reloadPostProcessingShaders(); -} - -void PostProcessingChainConfigWidget::onSelectedShaderChanged() -{ - std::optional index = getSelectedIndex(); - selectedShaderChanged(index.has_value() ? static_cast(index.value()) : -1); - updateButtonStates(index); -} diff --git a/src/duckstation-qt/postprocessingchainconfigwidget.h b/src/duckstation-qt/postprocessingchainconfigwidget.h deleted file mode 100644 index e3b3e65bb..000000000 --- a/src/duckstation-qt/postprocessingchainconfigwidget.h +++ /dev/null @@ -1,58 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#pragma once -#include "ui_postprocessingchainconfigwidget.h" - -#include "util/postprocessing_chain.h" - -#include "common/types.h" - -#include -#include -#include -#include - -namespace PlatformMisc { -class PostProcessingChain; -} - -class PostProcessingChainConfigWidget : public QWidget -{ - Q_OBJECT - -public: - PostProcessingChainConfigWidget(QWidget* parent); - ~PostProcessingChainConfigWidget(); - - ALWAYS_INLINE PostProcessingChain& getChain() { return m_chain; } - - bool setConfigString(const std::string_view& config_string); - void setOptionsButtonVisible(bool visible); - -Q_SIGNALS: - void selectedShaderChanged(qint32 index); - void chainAboutToChange(); - void chainConfigStringChanged(const std::string& new_config_string); - -private Q_SLOTS: - void onAddButtonClicked(); - void onRemoveButtonClicked(); - void onClearButtonClicked(); - void onMoveUpButtonClicked(); - void onMoveDownButtonClicked(); - void onShaderConfigButtonClicked(); - void onReloadButtonClicked(); - void onSelectedShaderChanged(); - -private: - void connectUi(); - std::optional getSelectedIndex() const; - void updateList(); - void configChanged(); - void updateButtonStates(std::optional index); - - Ui::PostProcessingChainConfigWidget m_ui; - - PostProcessingChain m_chain; -}; diff --git a/src/duckstation-qt/postprocessingchainconfigwidget.ui b/src/duckstation-qt/postprocessingchainconfigwidget.ui deleted file mode 100644 index b2d30d544..000000000 --- a/src/duckstation-qt/postprocessingchainconfigwidget.ui +++ /dev/null @@ -1,163 +0,0 @@ - - - PostProcessingChainConfigWidget - - - - 0 - 0 - 721 - 210 - - - - - 0 - 0 - - - - Form - - - - - - - 0 - 0 - - - - - 0 - 80 - - - - - - - - - - - 0 - 0 - - - - Add - - - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 0 - 0 - - - - Remove - - - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 0 - 0 - - - - Clear - - - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 0 - 0 - - - - Move Up - - - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 0 - 0 - - - - Move Down - - - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - 0 - 0 - - - - Options... - - - - - - Qt::ToolButtonTextBesideIcon - - - - - - - - - - - - diff --git a/src/duckstation-qt/postprocessingsettingswidget.cpp b/src/duckstation-qt/postprocessingsettingswidget.cpp index 7c75e6346..dc2a36d4a 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.cpp +++ b/src/duckstation-qt/postprocessingsettingswidget.cpp @@ -1,10 +1,20 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #include "postprocessingsettingswidget.h" #include "qthost.h" #include "settingwidgetbinder.h" + +#include "util/postprocessing.h" + +#include "common/error.h" + +#include +#include +#include +#include #include +#include PostProcessingSettingsWidget::PostProcessingSettingsWidget(SettingsDialog* dialog, QWidget* parent) : QWidget(parent), m_dialog(dialog) @@ -12,53 +22,101 @@ PostProcessingSettingsWidget::PostProcessingSettingsWidget(SettingsDialog* dialo SettingsInterface* sif = dialog->getSettingsInterface(); m_ui.setupUi(this); - m_ui.widget->setOptionsButtonVisible(false); - m_ui.reload->setEnabled(false); - updateShaderConfigPanel(-1); + + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enablePostProcessing, "PostProcessing", "Enabled", false); + + updateList(); + updateButtonsAndConfigPane(std::nullopt); connectUi(); - - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enablePostProcessing, "Display", "PostProcessing", false); - - std::string post_chain = m_dialog->getStringValue("Display", "PostProcessChain", "").value_or(std::string()); - if (!post_chain.empty()) - { - if (!m_ui.widget->setConfigString(post_chain)) - { - QMessageBox::critical(this, tr("Error"), tr("The current post-processing chain is invalid, it has been reset.")); - m_dialog->removeSettingValue("Display", "PostProcessChain"); - } - else - { - m_ui.reload->setEnabled(true); - } - } } PostProcessingSettingsWidget::~PostProcessingSettingsWidget() = default; +SettingsInterface& PostProcessingSettingsWidget::getSettingsInterfaceToUpdate() +{ + return m_dialog->isPerGameSettings() ? *m_dialog->getSettingsInterface() : *Host::Internal::GetBaseSettingsLayer(); +} + +void PostProcessingSettingsWidget::commitSettingsUpdate() +{ + if (m_dialog->isPerGameSettings()) + m_dialog->getSettingsInterface()->Save(); + else + Host::CommitBaseSettingChanges(); + + g_emu_thread->updatePostProcessingSettings(); +} + void PostProcessingSettingsWidget::connectUi() { - connect(m_ui.reload, &QPushButton::clicked, this, &PostProcessingSettingsWidget::onReloadClicked); - connect(m_ui.widget, &PostProcessingChainConfigWidget::chainAboutToChange, this, - &PostProcessingSettingsWidget::onChainAboutToChange); - connect(m_ui.widget, &PostProcessingChainConfigWidget::selectedShaderChanged, this, - &PostProcessingSettingsWidget::onChainSelectedShaderChanged); - connect(m_ui.widget, &PostProcessingChainConfigWidget::chainConfigStringChanged, this, - &PostProcessingSettingsWidget::onConfigChanged); + connect(m_ui.reload, &QPushButton::clicked, this, &PostProcessingSettingsWidget::onReloadButtonClicked); + connect(m_ui.add, &QToolButton::clicked, this, &PostProcessingSettingsWidget::onAddButtonClicked); + connect(m_ui.remove, &QToolButton::clicked, this, &PostProcessingSettingsWidget::onRemoveButtonClicked); + connect(m_ui.clear, &QToolButton::clicked, this, &PostProcessingSettingsWidget::onClearButtonClicked); + connect(m_ui.moveUp, &QToolButton::clicked, this, &PostProcessingSettingsWidget::onMoveUpButtonClicked); + connect(m_ui.moveDown, &QToolButton::clicked, this, &PostProcessingSettingsWidget::onMoveDownButtonClicked); + connect(m_ui.stages, &QListWidget::itemSelectionChanged, this, + &PostProcessingSettingsWidget::onSelectedShaderChanged); } -void PostProcessingSettingsWidget::onChainAboutToChange() +std::optional PostProcessingSettingsWidget::getSelectedIndex() const { - updateShaderConfigPanel(-1); + QList selected_items = m_ui.stages->selectedItems(); + return selected_items.empty() ? std::nullopt : + std::optional(selected_items.first()->data(Qt::UserRole).toUInt()); } -void PostProcessingSettingsWidget::onChainSelectedShaderChanged(qint32 index) +void PostProcessingSettingsWidget::selectIndex(s32 index) { - updateShaderConfigPanel(index); + if (index < 0 || index >= m_ui.stages->count()) + return; + + QSignalBlocker sb(m_ui.stages); + m_ui.stages->setCurrentItem(m_ui.stages->item(index)); + updateButtonsAndConfigPane(index); } -void PostProcessingSettingsWidget::updateShaderConfigPanel(s32 index) +void PostProcessingSettingsWidget::updateList() { + const auto lock = Host::GetSettingsLock(); + const SettingsInterface& si = getSettingsInterfaceToUpdate(); + + updateList(si); +} + +void PostProcessingSettingsWidget::updateList(const SettingsInterface& si) +{ + m_ui.stages->clear(); + + const u32 stage_count = PostProcessing::Config::GetStageCount(si); + + for (u32 i = 0; i < stage_count; i++) + { + const std::string stage_name = PostProcessing::Config::GetStageShaderName(si, i); + QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(stage_name), m_ui.stages); + item->setData(Qt::UserRole, QVariant(i)); + } + + m_ui.clear->setEnabled(stage_count > 0); + m_ui.reload->setEnabled(stage_count > 0); + updateButtonsAndConfigPane(std::nullopt); +} + +void PostProcessingSettingsWidget::updateButtonsAndConfigPane(std::optional index) +{ + m_ui.remove->setEnabled(index.has_value()); + + if (index.has_value()) + { + m_ui.moveUp->setEnabled(index.value() > 0); + m_ui.moveDown->setEnabled(index.value() < static_cast(m_ui.stages->count() - 1)); + } + else + { + m_ui.moveUp->setEnabled(false); + m_ui.moveDown->setEnabled(false); + } + m_ui.scrollArea->setWidget(nullptr); m_ui.scrollArea->setVisible(false); @@ -68,35 +126,282 @@ void PostProcessingSettingsWidget::updateShaderConfigPanel(s32 index) m_shader_config = nullptr; } - if (index < 0) + if (!index.has_value()) return; - PostProcessingShader* shader = m_ui.widget->getChain().GetShaderStage(static_cast(index)); - if (!shader->HasOptions()) + const auto lock = Host::GetSettingsLock(); + const SettingsInterface& si = getSettingsInterfaceToUpdate(); + std::vector options = PostProcessing::Config::GetStageOptions(si, index.value()); + if (options.empty()) return; - m_shader_config = new PostProcessingShaderConfigWidget(m_ui.scrollArea, shader); - connect(m_shader_config, &PostProcessingShaderConfigWidget::configChanged, - [this]() { onConfigChanged(m_ui.widget->getChain().GetConfigString()); }); + m_shader_config = new PostProcessingShaderConfigWidget(m_ui.scrollArea, this, index.value(), std::move(options)); m_ui.scrollArea->setWidget(m_shader_config); m_ui.scrollArea->setVisible(true); } -void PostProcessingSettingsWidget::onConfigChanged(const std::string& new_config) +void PostProcessingSettingsWidget::onAddButtonClicked() { - if (new_config.empty()) + QMenu menu; + + const std::vector> shaders = PostProcessing::GetAvailableShaderNames(); + if (shaders.empty()) { - m_dialog->removeSettingValue("Display", "PostProcessChain"); - m_ui.reload->setEnabled(false); + menu.addAction(tr("No Shaders Available"))->setEnabled(false); } else { - m_dialog->setStringSettingValue("Display", "PostProcessChain", new_config.c_str()); - m_ui.reload->setEnabled(true); + for (auto& [display_name, name] : shaders) + { + QAction* action = menu.addAction(QString::fromStdString(display_name)); + connect(action, &QAction::triggered, [this, shader = std::move(name)]() { + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = getSettingsInterfaceToUpdate(); + + Error error; + if (!PostProcessing::Config::AddStage(si, shader, &error)) + { + lock.unlock(); + QMessageBox::critical(this, tr("Error"), + tr("Failed to add shader: %1").arg(QString::fromStdString(error.GetDescription()))); + return; + } + + updateList(si); + lock.unlock(); + commitSettingsUpdate(); + }); + } + } + + menu.exec(QCursor::pos()); +} + +void PostProcessingSettingsWidget::onRemoveButtonClicked() +{ + QList selected_items = m_ui.stages->selectedItems(); + if (selected_items.empty()) + return; + + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = getSettingsInterfaceToUpdate(); + + QListWidgetItem* item = selected_items.first(); + u32 index = item->data(Qt::UserRole).toUInt(); + if (index < PostProcessing::Config::GetStageCount(si)) + { + PostProcessing::Config::RemoveStage(si, index); + updateList(si); + lock.unlock(); + commitSettingsUpdate(); } } -void PostProcessingSettingsWidget::onReloadClicked() +void PostProcessingSettingsWidget::onClearButtonClicked() +{ + if (QMessageBox::question(this, tr("Question"), tr("Are you sure you want to clear all shader stages?"), + QMessageBox::Yes, QMessageBox::No) == QMessageBox::Yes) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = getSettingsInterfaceToUpdate(); + PostProcessing::Config::ClearStages(si); + updateList(si); + lock.unlock(); + commitSettingsUpdate(); + } +} + +void PostProcessingSettingsWidget::onMoveUpButtonClicked() +{ + std::optional index = getSelectedIndex(); + if (index.has_value() && index.value() > 0) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = getSettingsInterfaceToUpdate(); + PostProcessing::Config::MoveStageUp(si, index.value()); + updateList(si); + lock.unlock(); + selectIndex(index.value() - 1); + commitSettingsUpdate(); + } +} + +void PostProcessingSettingsWidget::onMoveDownButtonClicked() +{ + std::optional index = getSelectedIndex(); + if (index.has_value() || index.value() < (static_cast(m_ui.stages->count() - 1))) + { + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = getSettingsInterfaceToUpdate(); + PostProcessing::Config::MoveStageDown(si, index.value()); + updateList(si); + lock.unlock(); + selectIndex(index.value() + 1); + commitSettingsUpdate(); + } +} + +void PostProcessingSettingsWidget::onReloadButtonClicked() { g_emu_thread->reloadPostProcessingShaders(); } + +void PostProcessingSettingsWidget::onSelectedShaderChanged() +{ + std::optional index = getSelectedIndex(); + updateButtonsAndConfigPane(index); +} + +PostProcessingShaderConfigWidget::PostProcessingShaderConfigWidget(QWidget* parent, + PostProcessingSettingsWidget* widget, + u32 stage_index, + std::vector options) + : QWidget(parent), m_widget(widget), m_stage_index(stage_index), m_options(std::move(options)) +{ + m_layout = new QGridLayout(this); + + createUi(); +} + +PostProcessingShaderConfigWidget::~PostProcessingShaderConfigWidget() = default; + +void PostProcessingShaderConfigWidget::updateConfigForOption(const PostProcessing::ShaderOption& option) +{ + const auto lock = Host::GetSettingsLock(); + SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate(); + PostProcessing::Config::SetStageOption(si, m_stage_index, option); + m_widget->commitSettingsUpdate(); +} + +void PostProcessingShaderConfigWidget::onResetDefaultsClicked() +{ + { + const auto lock = Host::GetSettingsLock(); + SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate(); + for (PostProcessing::ShaderOption& option : m_options) + { + if (std::memcmp(option.value.data(), option.default_value.data(), sizeof(option.value)) == 0) + continue; + + option.value = option.default_value; + PostProcessing::Config::UnsetStageOption(si, m_stage_index, option); + } + m_widget->commitSettingsUpdate(); + } + + // Toss and recreate UI. + for (auto it = m_widgets.rbegin(); it != m_widgets.rend(); ++it) + { + m_layout->removeWidget(*it); + delete *it; + } + m_widgets.clear(); + createUi(); +} + +void PostProcessingShaderConfigWidget::createUi() +{ + u32 row = 0; + + for (PostProcessing::ShaderOption& option : m_options) + { + if (option.ui_name.empty()) + continue; + + if (option.type == PostProcessing::ShaderOption::Type::Bool) + { + QCheckBox* checkbox = new QCheckBox(QString::fromStdString(option.ui_name), this); + checkbox->setChecked(option.value[0].int_value != 0); + connect(checkbox, &QCheckBox::stateChanged, [this, &option](int state) { + option.value[0].int_value = (state == Qt::Checked) ? 1 : 0; + updateConfigForOption(option); + }); + m_layout->addWidget(checkbox, row, 0, 1, 3, Qt::AlignLeft); + m_widgets.push_back(checkbox); + row++; + } + else + { + for (u32 i = 0; i < option.vector_size; i++) + { + QString label; + if (option.vector_size <= 1) + { + label = QString::fromStdString(option.ui_name); + } + else + { + static constexpr std::array suffixes = { + {QT_TR_NOOP("Red"), QT_TR_NOOP("Green"), QT_TR_NOOP("Blue"), QT_TR_NOOP("Alpha")}}; + label = tr("%1 (%2)").arg(QString::fromStdString(option.ui_name)).arg(tr(suffixes[i])); + } + + QWidget* label_w = new QLabel(label, this); + m_layout->addWidget(label_w, row, 0, 1, 1, Qt::AlignLeft); + m_widgets.push_back(label_w); + + QSlider* slider = new QSlider(Qt::Horizontal, this); + m_layout->addWidget(slider, row, 1, 1, 1, Qt::AlignLeft); + m_widgets.push_back(slider); + + QLabel* slider_label = new QLabel(this); + m_layout->addWidget(slider_label, row, 2, 1, 1, Qt::AlignLeft); + m_widgets.push_back(slider_label); + + if (option.type == PostProcessing::ShaderOption::Type::Int) + { + slider_label->setText(QString::number(option.value[i].int_value)); + + const int range = option.max_value[i].int_value - option.min_value[i].int_value; + const int step_value = + (option.step_value[i].int_value != 0) ? option.step_value[i].int_value : ((range + 99) / 100); + const int num_steps = range / step_value; + slider->setMinimum(0); + slider->setMaximum(num_steps); + slider->setSingleStep(1); + slider->setTickInterval(step_value); + slider->setValue((option.value[i].int_value - option.min_value[i].int_value) / step_value); + connect(slider, &QSlider::valueChanged, [this, &option, i, slider_label](int value) { + const int new_value = std::clamp(option.min_value[i].int_value + (value * option.step_value[i].int_value), + option.min_value[i].int_value, option.max_value[i].int_value); + option.value[i].int_value = new_value; + slider_label->setText(QString::number(new_value)); + updateConfigForOption(option); + }); + } + else + { + slider_label->setText(QString::number(option.value[i].float_value)); + + const float range = option.max_value[i].float_value - option.min_value[i].float_value; + const float step_value = + (option.step_value[i].float_value != 0) ? option.step_value[i].float_value : ((range + 99.0f) / 100.0f); + const float num_steps = std::ceil(range / step_value); + slider->setMinimum(0); + slider->setMaximum(num_steps); + slider->setSingleStep(1); + slider->setTickInterval(step_value); + slider->setValue( + static_cast((option.value[i].float_value - option.min_value[i].float_value) / step_value)); + connect(slider, &QSlider::valueChanged, [this, &option, i, slider_label](int value) { + const float new_value = std::clamp(option.min_value[i].float_value + + (static_cast(value) * option.step_value[i].float_value), + option.min_value[i].float_value, option.max_value[i].float_value); + option.value[i].float_value = new_value; + slider_label->setText(QString::number(new_value)); + updateConfigForOption(option); + }); + } + + row++; + } + } + } + + QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::RestoreDefaults, this); + connect(button_box, &QDialogButtonBox::clicked, this, &PostProcessingShaderConfigWidget::onResetDefaultsClicked); + m_layout->addWidget(button_box, row, 0, 1, -1); + + row++; + m_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), row, 0, 1, 3); +} diff --git a/src/duckstation-qt/postprocessingsettingswidget.h b/src/duckstation-qt/postprocessingsettingswidget.h index faabc3e16..3c44f530a 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.h +++ b/src/duckstation-qt/postprocessingsettingswidget.h @@ -2,34 +2,74 @@ // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once -#include "postprocessingchainconfigwidget.h" -#include "postprocessingshaderconfigwidget.h" + #include "ui_postprocessingsettingswidget.h" + +#include "util/postprocessing.h" + #include class SettingsDialog; +class PostProcessingShaderConfigWidget; class PostProcessingSettingsWidget : public QWidget { Q_OBJECT + friend PostProcessingShaderConfigWidget; + public: PostProcessingSettingsWidget(SettingsDialog* dialog, QWidget* parent); ~PostProcessingSettingsWidget(); private Q_SLOTS: - void onChainAboutToChange(); - void onChainSelectedShaderChanged(qint32 index); - void onConfigChanged(const std::string& new_config); - void onReloadClicked(); + void onAddButtonClicked(); + void onRemoveButtonClicked(); + void onClearButtonClicked(); + void onMoveUpButtonClicked(); + void onMoveDownButtonClicked(); + void onReloadButtonClicked(); + void onSelectedShaderChanged(); private: + SettingsInterface& getSettingsInterfaceToUpdate(); + void commitSettingsUpdate(); + void connectUi(); - void updateShaderConfigPanel(s32 index); - + void updateButtonsAndConfigPane(std::optional index); + std::optional getSelectedIndex() const; + void selectIndex(s32 index); + void updateList(const SettingsInterface& si); + void updateList(); + SettingsDialog* m_dialog; Ui::PostProcessingSettingsWidget m_ui; PostProcessingShaderConfigWidget* m_shader_config = nullptr; }; + +class PostProcessingShaderConfigWidget : public QWidget +{ + Q_OBJECT + +public: + PostProcessingShaderConfigWidget(QWidget* parent, PostProcessingSettingsWidget* widget, u32 stage_index, + std::vector options); + ~PostProcessingShaderConfigWidget(); + +private Q_SLOTS: + void onResetDefaultsClicked(); + +protected: + void createUi(); + void updateConfigForOption(const PostProcessing::ShaderOption& option); + + QGridLayout* m_layout; + + PostProcessingSettingsWidget* m_widget; + std::vector m_widgets; + + u32 m_stage_index; + std::vector m_options; +}; diff --git a/src/duckstation-qt/postprocessingsettingswidget.ui b/src/duckstation-qt/postprocessingsettingswidget.ui index 14ca8a79b..2ef4bacd4 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.ui +++ b/src/duckstation-qt/postprocessingsettingswidget.ui @@ -67,20 +67,125 @@ Post Processing Chain - - 0 - - - 0 - - - 0 - - - 0 - - + + + + 0 + 0 + + + + + 0 + 80 + + + + + + + + + + + 0 + 0 + + + + Add + + + + .. + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 0 + + + + Remove + + + + .. + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 0 + + + + Clear + + + + .. + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 0 + + + + Move Up + + + + .. + + + Qt::ToolButtonTextBesideIcon + + + + + + + + 0 + 0 + + + + Move Down + + + + .. + + + Qt::ToolButtonTextBesideIcon + + + + @@ -108,7 +213,7 @@ 0 0 679 - 438 + 238 @@ -129,14 +234,6 @@ - - - PostProcessingChainConfigWidget - QWidget -
duckstation-qt/postprocessingchainconfigwidget.h
- 1 -
-
diff --git a/src/duckstation-qt/postprocessingshaderconfigwidget.cpp b/src/duckstation-qt/postprocessingshaderconfigwidget.cpp deleted file mode 100644 index 435fc7e17..000000000 --- a/src/duckstation-qt/postprocessingshaderconfigwidget.cpp +++ /dev/null @@ -1,175 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#include "postprocessingshaderconfigwidget.h" -#include -#include -#include -#include -#include - -PostProcessingShaderConfigWidget::PostProcessingShaderConfigWidget(QWidget* parent, - PostProcessingShader* shader) - : QWidget(parent), m_shader(shader) -{ - createUi(); -} - -PostProcessingShaderConfigWidget::~PostProcessingShaderConfigWidget() = default; - -void PostProcessingShaderConfigWidget::createUi() -{ - m_layout = new QGridLayout(this); - u32 row = 0; - - for (PostProcessingShader::Option& option : m_shader->GetOptions()) - { - if (option.type == PostProcessingShader::Option::Type::Bool) - { - QCheckBox* checkbox = new QCheckBox(QString::fromStdString(option.ui_name), this); - checkbox->setChecked(option.value[0].int_value != 0); - connect(checkbox, &QCheckBox::stateChanged, [this, &option](int state) { - option.value[0].int_value = (state == Qt::Checked) ? 1 : 0; - configChanged(); - }); - connect(this, &PostProcessingShaderConfigWidget::resettingtoDefaults, [&option, checkbox]() { - QSignalBlocker sb(checkbox); - checkbox->setChecked(option.default_value[0].int_value != 0); - option.value = option.default_value; - }); - m_layout->addWidget(checkbox, row, 0, 1, 3, Qt::AlignLeft); - row++; - } - else - { - for (u32 i = 0; i < option.vector_size; i++) - { - QString label; - if (option.vector_size <= 1) - { - label = QString::fromStdString(option.ui_name); - } - else - { - static constexpr std::array suffixes = { - {QT_TR_NOOP("Red"), QT_TR_NOOP("Green"), QT_TR_NOOP("Blue"), QT_TR_NOOP("Alpha")}}; - label = tr("%1 (%2)").arg(QString::fromStdString(option.ui_name)).arg(tr(suffixes[i])); - } - - m_layout->addWidget(new QLabel(label, this), row, 0, 1, 1, Qt::AlignLeft); - - QSlider* slider = new QSlider(Qt::Horizontal, this); - m_layout->addWidget(slider, row, 1, 1, 1, Qt::AlignLeft); - - QLabel* slider_label = new QLabel(this); - m_layout->addWidget(slider_label, row, 2, 1, 1, Qt::AlignLeft); - - if (option.type == PostProcessingShader::Option::Type::Int) - { - slider_label->setText(QString::number(option.value[i].int_value)); - - const int range = std::max(option.max_value[i].int_value - option.min_value[i].int_value, 1); - const int step_value = - (option.step_value[i].int_value != 0) ? option.step_value[i].int_value : ((range + 99) / 100); - const int num_steps = range / step_value; - slider->setMinimum(0); - slider->setMaximum(num_steps); - slider->setSingleStep(1); - slider->setTickInterval(step_value); - slider->setValue((option.value[i].int_value - option.min_value[i].int_value) / step_value); - connect(slider, &QSlider::valueChanged, [this, &option, i, slider_label](int value) { - const int new_value = std::clamp(option.min_value[i].int_value + (value * option.step_value[i].int_value), - option.min_value[i].int_value, option.max_value[i].int_value); - option.value[i].int_value = new_value; - slider_label->setText(QString::number(new_value)); - configChanged(); - }); - connect(this, &PostProcessingShaderConfigWidget::resettingtoDefaults, - [&option, i, slider, slider_label, step_value]() { - QSignalBlocker sb(slider); - slider->setValue((option.default_value[i].int_value - option.min_value[i].int_value) / step_value); - slider_label->setText(QString::number(option.default_value[i].int_value)); - option.value = option.default_value; - }); - } - else - { - slider_label->setText(QString::number(option.value[i].float_value)); - - const float range = std::max(option.max_value[i].float_value - option.min_value[i].float_value, 1.0f); - const float step_value = - (option.step_value[i].float_value != 0) ? option.step_value[i].float_value : ((range + 99.0f) / 100.0f); - const float num_steps = range / step_value; - slider->setMinimum(0); - slider->setMaximum(num_steps); - slider->setSingleStep(1); - slider->setTickInterval(step_value); - slider->setValue( - static_cast((option.value[i].float_value - option.min_value[i].float_value) / step_value)); - connect(slider, &QSlider::valueChanged, [this, &option, i, slider_label](int value) { - const float new_value = std::clamp(option.min_value[i].float_value + - (static_cast(value) * option.step_value[i].float_value), - option.min_value[i].float_value, option.max_value[i].float_value); - option.value[i].float_value = new_value; - slider_label->setText(QString::number(new_value)); - configChanged(); - }); - connect(this, &PostProcessingShaderConfigWidget::resettingtoDefaults, - [&option, i, slider, slider_label, step_value]() { - QSignalBlocker sb(slider); - slider->setValue(static_cast( - (option.default_value[i].float_value - option.min_value[i].float_value) / step_value)); - slider_label->setText(QString::number(option.default_value[i].float_value)); - option.value = option.default_value; - }); - } - - row++; - } - } - } - - QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::RestoreDefaults, this); - connect(button_box, &QDialogButtonBox::clicked, this, &PostProcessingShaderConfigWidget::onResetToDefaultsClicked); - m_layout->addWidget(button_box, row, 0, 1, -1); - - row++; - m_layout->addItem(new QSpacerItem(1, 1, QSizePolicy::Minimum, QSizePolicy::Expanding), row, 0, 1, 3); -} - -void PostProcessingShaderConfigWidget::onResetToDefaultsClicked() -{ - resettingtoDefaults(); - configChanged(); -} - -PostProcessingShaderConfigDialog::PostProcessingShaderConfigDialog(QWidget* parent, - PostProcessingShader* shader) - : QDialog(parent) -{ - setWindowTitle(tr("%1 Shader Options").arg(QString::fromStdString(shader->GetName()))); - setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint); - - QGridLayout* layout = new QGridLayout(this); - m_widget = new PostProcessingShaderConfigWidget(this, shader); - layout->addWidget(m_widget); - - connect(m_widget, &PostProcessingShaderConfigWidget::configChanged, this, - &PostProcessingShaderConfigDialog::onConfigChanged); - - QDialogButtonBox* button_box = new QDialogButtonBox(QDialogButtonBox::Close, this); - connect(button_box, &QDialogButtonBox::rejected, this, &PostProcessingShaderConfigDialog::onCloseClicked); - m_widget->getLayout()->addWidget(button_box, m_widget->getLayout()->rowCount() - 1, 2, 1, 2); -} - -PostProcessingShaderConfigDialog::~PostProcessingShaderConfigDialog() = default; - -void PostProcessingShaderConfigDialog::onConfigChanged() -{ - configChanged(); -} - -void PostProcessingShaderConfigDialog::onCloseClicked() -{ - done(0); -} diff --git a/src/duckstation-qt/postprocessingshaderconfigwidget.h b/src/duckstation-qt/postprocessingshaderconfigwidget.h deleted file mode 100644 index c1cdf06c0..000000000 --- a/src/duckstation-qt/postprocessingshaderconfigwidget.h +++ /dev/null @@ -1,55 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#pragma once - -#include "util/postprocessing_shader.h" - -#include -#include - -class QGridLayout; - -class PostProcessingShaderConfigWidget : public QWidget -{ - Q_OBJECT - -public: - PostProcessingShaderConfigWidget(QWidget* parent, PostProcessingShader* shader); - ~PostProcessingShaderConfigWidget(); - - QGridLayout* getLayout() { return m_layout; } - -Q_SIGNALS: - void configChanged(); - void resettingtoDefaults(); - -private Q_SLOTS: - void onResetToDefaultsClicked(); - -protected: - void createUi(); - - PostProcessingShader* m_shader; - QGridLayout* m_layout; -}; - -class PostProcessingShaderConfigDialog : public QDialog -{ - Q_OBJECT - -public: - PostProcessingShaderConfigDialog(QWidget* parent, PostProcessingShader* shader); - ~PostProcessingShaderConfigDialog(); - -Q_SIGNALS: - void configChanged(); - -private Q_SLOTS: - void onConfigChanged(); - void onCloseClicked(); - -private: - PostProcessingShaderConfigWidget* m_widget; -}; - diff --git a/src/duckstation-qt/qthost.cpp b/src/duckstation-qt/qthost.cpp index 3abda8e7e..18605f143 100644 --- a/src/duckstation-qt/qthost.cpp +++ b/src/duckstation-qt/qthost.cpp @@ -33,6 +33,7 @@ #include "util/ini_settings_interface.h" #include "util/input_manager.h" #include "util/platform_misc.h" +#include "util/postprocessing.h" #include "scmversion/scmversion.h" @@ -947,7 +948,20 @@ void EmuThread::reloadPostProcessingShaders() return; } - System::ReloadPostProcessingShaders(); + if (System::IsValid()) + PostProcessing::ReloadShaders(); +} + +void EmuThread::updatePostProcessingSettings() +{ + if (!isOnThread()) + { + QMetaObject::invokeMethod(this, "updatePostProcessingSettings", Qt::QueuedConnection); + return; + } + + if (System::IsValid()) + PostProcessing::UpdateSettings(); } void EmuThread::clearInputBindStateFromSource(InputBindingKey key) diff --git a/src/duckstation-qt/qthost.h b/src/duckstation-qt/qthost.h index 93528cd0b..32f7747ff 100644 --- a/src/duckstation-qt/qthost.h +++ b/src/duckstation-qt/qthost.h @@ -188,6 +188,7 @@ public Q_SLOTS: void setCheatEnabled(quint32 index, bool enabled); void applyCheat(quint32 index); void reloadPostProcessingShaders(); + void updatePostProcessingSettings(); void clearInputBindStateFromSource(InputBindingKey key); private Q_SLOTS: diff --git a/src/util/CMakeLists.txt b/src/util/CMakeLists.txt index 73a640747..5192c67b1 100644 --- a/src/util/CMakeLists.txt +++ b/src/util/CMakeLists.txt @@ -48,8 +48,8 @@ add_library(util page_fault_handler.cpp page_fault_handler.h platform_misc.h - postprocessing_chain.cpp - postprocessing_chain.h + postprocessing.cpp + postprocessing.h postprocessing_shader.cpp postprocessing_shader.h postprocessing_shader_glsl.cpp diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp new file mode 100644 index 000000000..7d255ce03 --- /dev/null +++ b/src/util/postprocessing.cpp @@ -0,0 +1,677 @@ +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#include "postprocessing.h" +#include "gpu_device.h" +#include "host.h" +#include "imgui_manager.h" +#include "postprocessing_shader.h" +#include "postprocessing_shader_glsl.h" + +// TODO: Remove me +#include "core/host.h" +#include "core/settings.h" + +#include "IconsFontAwesome5.h" +#include "common/assert.h" +#include "common/error.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/path.h" +#include "common/string.h" +#include "common/string_util.h" +#include "common/timer.h" +#include "fmt/format.h" + +Log_SetChannel(PostProcessing); + +// TODO: ProgressCallbacks for shader compiling, it can be a bit slow. +// TODO: buffer width/height is wrong on resize, need to change it somehow. + +namespace PostProcessing { +template +static u32 ParseVector(const std::string_view& line, ShaderOption::ValueVector* values); + +static TinyString ValueToString(ShaderOption::Type type, u32 vector_size, const ShaderOption::ValueVector& value); + +static TinyString GetStageConfigSection(u32 index); +static void CopyStageConfig(SettingsInterface& si, u32 old_index, u32 new_index); +static void SwapStageConfig(SettingsInterface& si, u32 lhs_index, u32 rhs_index); +static std::unique_ptr TryLoadingShader(const std::string& shader_name, bool only_config, Error* error); +static void ClearStagesWithError(const Error& error); +static SettingsInterface& GetLoadSettingsInterface(); +static void LoadStages(); +static void DestroyTextures(); + +static std::vector> s_stages; +static bool s_enabled = false; + +static GPUTexture::Format s_target_format = GPUTexture::Format::Unknown; +static u32 s_target_width = 0; +static u32 s_target_height = 0; +static Common::Timer s_timer; + +static std::unique_ptr s_input_texture; +static std::unique_ptr s_input_framebuffer; + +static std::unique_ptr s_output_texture; +static std::unique_ptr s_output_framebuffer; + +static std::unordered_map> s_samplers; +static std::unique_ptr s_dummy_texture; +} // namespace PostProcessing + +template +u32 PostProcessing::ParseVector(const std::string_view& line, ShaderOption::ValueVector* values) +{ + u32 index = 0; + size_t start = 0; + while (index < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS) + { + while (start < line.size() && std::isspace(line[start])) + start++; + + if (start >= line.size()) + break; + + size_t end = line.find(',', start); + if (end == std::string_view::npos) + end = line.size(); + + const std::string_view component = line.substr(start, end - start); + T value = StringUtil::FromChars(component).value_or(static_cast(0)); + if constexpr (std::is_same_v) + (*values)[index++].float_value = value; + else if constexpr (std::is_same_v) + (*values)[index++].int_value = value; + + start = end + 1; + } + + const u32 size = index; + + for (; index < PostProcessing::ShaderOption::MAX_VECTOR_COMPONENTS; index++) + { + if constexpr (std::is_same_v) + (*values)[index++].float_value = 0.0f; + else if constexpr (std::is_same_v) + (*values)[index++].int_value = 0; + } + + return size; +} + +u32 PostProcessing::ShaderOption::ParseFloatVector(const std::string_view& line, ValueVector* values) +{ + return ParseVector(line, values); +} + +u32 PostProcessing::ShaderOption::ParseIntVector(const std::string_view& line, ValueVector* values) +{ + return ParseVector(line, values); +} + +TinyString PostProcessing::ValueToString(ShaderOption::Type type, u32 vector_size, + const ShaderOption::ValueVector& value) +{ + TinyString ret; + + for (u32 i = 0; i < vector_size; i++) + { + if (i > 0) + ret.AppendCharacter(','); + + switch (type) + { + case ShaderOption::Type::Bool: + ret.AppendString((value[i].int_value != 0) ? "true" : "false"); + break; + + case ShaderOption::Type::Int: + ret.AppendFmtString("{}", value[i].int_value); + break; + + case ShaderOption::Type::Float: + ret.AppendFmtString("{}", value[i].float_value); + break; + + default: + break; + } + } + + return ret; +} + +std::vector> PostProcessing::GetAvailableShaderNames() +{ + std::vector> names; + + FileSystem::FindResultsArray results; + FileSystem::FindFiles(Path::Combine(EmuFolders::Resources, "shaders").c_str(), "*.glsl", + FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS, &results); + FileSystem::FindFiles(EmuFolders::Shaders.c_str(), "*.glsl", + FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS | + FILESYSTEM_FIND_KEEP_ARRAY, + &results); + + for (FILESYSTEM_FIND_DATA& fd : results) + { + size_t pos = fd.FileName.rfind('.'); + if (pos != std::string::npos && pos > 0) + fd.FileName.erase(pos); + + // swap any backslashes for forward slashes so the config is cross-platform + for (size_t i = 0; i < fd.FileName.size(); i++) + { + if (fd.FileName[i] == '\\') + fd.FileName[i] = '/'; + } + + if (std::none_of(names.begin(), names.end(), [&fd](const auto& other) { return fd.FileName == other.second; })) + { + std::string display_name = fmt::format(TRANSLATE_FS("PostProcessing", "{} [GLSL]"), fd.FileName); + names.emplace_back(std::move(display_name), std::move(fd.FileName)); + } + } + + return names; +} + +TinyString PostProcessing::GetStageConfigSection(u32 index) +{ + return TinyString::FromFmt("PostProcessing/Stage{}", index + 1); +} + +void PostProcessing::CopyStageConfig(SettingsInterface& si, u32 old_index, u32 new_index) +{ + const TinyString old_section = GetStageConfigSection(old_index); + const TinyString new_section = GetStageConfigSection(new_index); + + si.ClearSection(new_section); + + for (const auto& [key, value] : si.GetKeyValueList(old_section)) + si.SetStringValue(new_section, key.c_str(), value.c_str()); +} + +void PostProcessing::SwapStageConfig(SettingsInterface& si, u32 lhs_index, u32 rhs_index) +{ + const TinyString lhs_section = GetStageConfigSection(lhs_index); + const TinyString rhs_section = GetStageConfigSection(rhs_index); + + const std::vector> lhs_kvs = si.GetKeyValueList(lhs_section); + si.ClearSection(lhs_section); + + const std::vector> rhs_kvs = si.GetKeyValueList(rhs_section); + si.ClearSection(rhs_section); + + for (const auto& [key, value] : rhs_kvs) + si.SetStringValue(lhs_section, key.c_str(), value.c_str()); + + for (const auto& [key, value] : lhs_kvs) + si.SetStringValue(rhs_section, key.c_str(), value.c_str()); +} + +u32 PostProcessing::Config::GetStageCount(const SettingsInterface& si) +{ + return si.GetUIntValue("PostProcessing", "StageCount", 0u); +} + +std::string PostProcessing::Config::GetStageShaderName(const SettingsInterface& si, u32 index) +{ + return si.GetStringValue(GetStageConfigSection(index), "ShaderName"); +} + +std::vector PostProcessing::Config::GetStageOptions(const SettingsInterface& si, + u32 index) +{ + std::vector ret; + + const TinyString section = GetStageConfigSection(index); + const std::string shader_name = si.GetStringValue(section, "ShaderName"); + if (shader_name.empty()) + return ret; + + std::unique_ptr shader = TryLoadingShader(shader_name, true, nullptr); + if (!shader) + return ret; + + ret = shader->TakeOptions(); + return ret; +} + +bool PostProcessing::Config::AddStage(SettingsInterface& si, const std::string& shader_name, Error* error) +{ + std::unique_ptr shader = TryLoadingShader(shader_name, true, error); + if (!shader) + return false; + + const u32 index = GetStageCount(si); + si.SetUIntValue("PostProcessing", "StageCount", index + 1); + + const TinyString section = GetStageConfigSection(index); + si.SetStringValue(section, "ShaderName", shader->GetName().c_str()); + +#if 0 + // Leave options unset for now. + for (const ShaderOption& option : shader->GetOptions()) + { + si.SetStringValue(section, option.name.c_str(), + ValueToString(option.type, option.vector_size, option.default_value)); + } +#endif + + return true; +} + +void PostProcessing::Config::RemoveStage(SettingsInterface& si, u32 index) +{ + const u32 stage_count = GetStageCount(si); + if (index >= stage_count) + return; + + for (u32 i = index; i < (stage_count - 1); i++) + CopyStageConfig(si, i + 1, i); + + si.ClearSection(GetStageConfigSection(stage_count - 1)); + si.SetUIntValue("PostProcessing", "StageCount", stage_count - 1); +} + +void PostProcessing::Config::MoveStageUp(SettingsInterface& si, u32 index) +{ + const u32 stage_count = GetStageCount(si); + if (index == 0 || index >= stage_count) + return; + + SwapStageConfig(si, index, index - 1); +} + +void PostProcessing::Config::MoveStageDown(SettingsInterface& si, u32 index) +{ + const u32 stage_count = GetStageCount(si); + if ((index + 1) >= stage_count) + return; + + SwapStageConfig(si, index, index + 1); +} + +void PostProcessing::Config::SetStageOption(SettingsInterface& si, u32 index, const ShaderOption& option) +{ + const TinyString section = GetStageConfigSection(index); + si.SetStringValue(section, option.name.c_str(), ValueToString(option.type, option.vector_size, option.value)); +} + +void PostProcessing::Config::UnsetStageOption(SettingsInterface& si, u32 index, const ShaderOption& option) +{ + const TinyString section = GetStageConfigSection(index); + si.DeleteValue(section, option.name.c_str()); +} + +void PostProcessing::Config::ClearStages(SettingsInterface& si) +{ + const u32 count = GetStageCount(si); + for (s32 i = static_cast(count - 1); i >= 0; i--) + si.ClearSection(GetStageConfigSection(static_cast(i))); + si.SetUIntValue("PostProcessing", "StageCount", 0); +} + +bool PostProcessing::IsActive() +{ + return s_enabled && !s_stages.empty(); +} + +bool PostProcessing::IsEnabled() +{ + return s_enabled; +} + +void PostProcessing::SetEnabled(bool enabled) +{ + s_enabled = enabled; +} + +std::unique_ptr PostProcessing::TryLoadingShader(const std::string& shader_name, + bool only_config, Error* error) +{ + std::string filename(Path::Combine(EmuFolders::Shaders, fmt::format("{}.glsl", shader_name))); + if (FileSystem::FileExists(filename.c_str())) + { + std::unique_ptr shader = std::make_unique(); + if (shader->LoadFromFile(std::string(shader_name), filename.c_str(), error)) + return shader; + } + + std::optional resource_str( + Host::ReadResourceFileToString(fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "{}.glsl", shader_name).c_str())); + if (resource_str.has_value()) + { + std::unique_ptr shader = std::make_unique(); + if (shader->LoadFromString(std::string(shader_name), std::move(resource_str.value()), error)) + return shader; + } + + Log_ErrorPrint(fmt::format("Failed to load shader '{}'", shader_name).c_str()); + return {}; +} + +void PostProcessing::ClearStagesWithError(const Error& error) +{ + std::string msg = error.GetDescription(); + Host::AddIconOSDMessage( + "PostProcessLoadFail", ICON_FA_EXCLAMATION_TRIANGLE, + fmt::format(TRANSLATE_FS("OSDMessage", "Failed to load post-processing chain: {}"), + msg.empty() ? TRANSLATE_SV("PostProcessing", "Unknown Error") : std::string_view(msg)), + Host::OSD_ERROR_DURATION); + s_stages.clear(); +} + +SettingsInterface& PostProcessing::GetLoadSettingsInterface() +{ + // If PostProcessing/Enable is set in the game settings interface, use that. + // Otherwise, use the base settings. + + SettingsInterface* game_si = Host::Internal::GetGameSettingsLayer(); + if (game_si && game_si->ContainsValue("PostProcessing", "Enabled")) + return *game_si; + else + return *Host::Internal::GetBaseSettingsLayer(); +} + +void PostProcessing::Initialize() +{ + LoadStages(); +} + +void PostProcessing::LoadStages() +{ + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = GetLoadSettingsInterface(); + + s_enabled = si.GetBoolValue("PostProcessing", "Enabled", false); + + const u32 stage_count = Config::GetStageCount(si); + if (stage_count == 0) + return; + + Error error; + + for (u32 i = 0; i < stage_count; i++) + { + std::string stage_name = Config::GetStageShaderName(si, i); + if (stage_name.empty()) + { + error.SetString(fmt::format("No stage name in stage {}.", i + 1)); + ClearStagesWithError(error); + return; + } + + lock.unlock(); + + std::unique_ptr shader = TryLoadingShader(stage_name, false, &error); + if (!shader) + { + ClearStagesWithError(error); + return; + } + + lock.lock(); + shader->LoadOptions(si, GetStageConfigSection(i)); + s_stages.push_back(std::move(shader)); + } + + if (stage_count > 0) + { + s_timer.Reset(); + Log_DevPrintf("Loaded %u post-processing stages.", stage_count); + } +} + +void PostProcessing::UpdateSettings() +{ + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = GetLoadSettingsInterface(); + + s_enabled = si.GetBoolValue("PostProcessing", "Enabled", false); + + const u32 stage_count = Config::GetStageCount(si); + if (stage_count == 0) + { + s_stages.clear(); + return; + } + + Error error; + + s_stages.resize(stage_count); + + for (u32 i = 0; i < stage_count; i++) + { + std::string stage_name = Config::GetStageShaderName(si, i); + if (stage_name.empty()) + { + error.SetString(fmt::format("No stage name in stage {}.", i + 1)); + ClearStagesWithError(error); + return; + } + + if (!s_stages[i] || stage_name != s_stages[i]->GetName()) + { + if (i < s_stages.size()) + s_stages[i].reset(); + + // Force recompile. + s_target_format = GPUTexture::Format::Unknown; + + lock.unlock(); + + std::unique_ptr shader = TryLoadingShader(stage_name, false, &error); + if (!shader) + { + ClearStagesWithError(error); + return; + } + + if (i < s_stages.size()) + s_stages[i] = std::move(shader); + else + s_stages.push_back(std::move(shader)); + + lock.lock(); + } + + s_stages[i]->LoadOptions(si, GetStageConfigSection(i)); + } + + if (stage_count > 0) + { + s_timer.Reset(); + Log_DevPrintf("Loaded %u post-processing stages.", stage_count); + } +} + +void PostProcessing::Toggle() +{ + if (s_stages.empty()) + { + Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER, + TRANSLATE_STR("OSDMessage", "No post-processing shaders are selected."), + Host::OSD_QUICK_DURATION); + return; + } + + const bool new_enabled = !s_enabled; + Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER, + new_enabled ? TRANSLATE_STR("OSDMessage", "Post-processing is now enabled.") : + TRANSLATE_STR("OSDMessage", "Post-processing is now disabled."), + Host::OSD_QUICK_DURATION); + s_enabled = new_enabled; + if (s_enabled) + s_timer.Reset(); +} + +bool PostProcessing::ReloadShaders() +{ + if (s_stages.empty()) + { + Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER, + TRANSLATE_STR("OSDMessage", "No post-processing shaders are selected."), + Host::OSD_QUICK_DURATION); + return false; + } + + decltype(s_stages)().swap(s_stages); + DestroyTextures(); + LoadStages(); + + Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER, + TRANSLATE_STR("OSDMessage", "Post-processing shaders reloaded."), Host::OSD_QUICK_DURATION); + return true; +} + +void PostProcessing::Shutdown() +{ + s_dummy_texture.reset(); + s_samplers.clear(); + s_enabled = false; + decltype(s_stages)().swap(s_stages); + DestroyTextures(); +} + +GPUTexture* PostProcessing::GetInputTexture() +{ + return s_input_texture.get(); +} + +GPUFramebuffer* PostProcessing::GetInputFramebuffer() +{ + return s_input_framebuffer.get(); +} + +const Common::Timer& PostProcessing::GetTimer() +{ + return s_timer; +} + +GPUSampler* PostProcessing::GetSampler(const GPUSampler::Config& config) +{ + auto it = s_samplers.find(config.key); + if (it != s_samplers.end()) + return it->second.get(); + + std::unique_ptr sampler = g_gpu_device->CreateSampler(config); + if (!sampler) + Log_ErrorPrint(fmt::format("Failed to create GPU sampler with config={:X}", config.key).c_str()); + + it = s_samplers.emplace(config.key, std::move(sampler)).first; + return it->second.get(); +} + +GPUTexture* PostProcessing::GetDummyTexture() +{ + if (s_dummy_texture) + return s_dummy_texture.get(); + + const u32 zero = 0; + s_dummy_texture = g_gpu_device->CreateTexture(1, 1, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8, + &zero, sizeof(zero)); + if (!s_dummy_texture) + Log_ErrorPrint("Failed to create dummy texture."); + + return s_dummy_texture.get(); +} + +bool PostProcessing::CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height) +{ + if (s_target_format == target_format && s_target_width == target_width && s_target_height == target_height) + return true; + + // In case any allocs fail. + DestroyTextures(); + + if (!(s_input_texture = g_gpu_device->CreateTexture(target_width, target_height, 1, 1, 1, + GPUTexture::Type::RenderTarget, target_format)) || + !(s_input_framebuffer = g_gpu_device->CreateFramebuffer(s_input_texture.get()))) + { + return false; + } + + if (!(s_output_texture = g_gpu_device->CreateTexture(target_width, target_height, 1, 1, 1, + GPUTexture::Type::RenderTarget, target_format)) || + !(s_output_framebuffer = g_gpu_device->CreateFramebuffer(s_output_texture.get()))) + { + return false; + } + + for (auto& shader : s_stages) + { + if (!shader->CompilePipeline(target_format, target_width, target_height) || + !shader->ResizeOutput(target_format, target_width, target_height)) + { + Log_ErrorPrintf("Failed to compile one or more post-processing shaders, disabling."); + Host::AddIconOSDMessage( + "PostProcessLoadFail", ICON_FA_EXCLAMATION_TRIANGLE, + fmt::format("Failed to compile post-processing shader '{}'. Disabling post-processing.", shader->GetName())); + s_enabled = false; + return false; + } + } + + s_target_format = target_format; + s_target_width = target_width; + s_target_height = target_height; + return true; +} + +void PostProcessing::DestroyTextures() +{ + s_target_format = GPUTexture::Format::Unknown; + s_target_width = 0; + s_target_height = 0; + + s_output_framebuffer.reset(); + s_output_texture.reset(); + + s_input_framebuffer.reset(); + s_input_texture.reset(); +} + +bool PostProcessing::Apply(GPUFramebuffer* final_target, s32 final_left, s32 final_top, s32 final_width, + s32 final_height, s32 orig_width, s32 orig_height) +{ + GL_SCOPE("PostProcessing Apply"); + + const u32 target_width = final_target ? final_target->GetWidth() : g_gpu_device->GetWindowWidth(); + const u32 target_height = final_target ? final_target->GetHeight() : g_gpu_device->GetWindowHeight(); + const GPUTexture::Format target_format = + final_target ? final_target->GetRT()->GetFormat() : g_gpu_device->GetWindowFormat(); + if (!CheckTargets(target_format, target_width, target_height)) + return false; + + g_gpu_device->SetViewportAndScissor(final_left, final_top, final_width, final_height); + + GPUTexture* input = s_input_texture.get(); + GPUFramebuffer* input_fb = s_input_framebuffer.get(); + GPUTexture* output = s_output_texture.get(); + GPUFramebuffer* output_fb = s_output_framebuffer.get(); + input->MakeReadyForSampling(); + + for (const std::unique_ptr& stage : s_stages) + { + const bool is_final = (stage.get() == s_stages.back().get()); + + if (!stage->Apply(input, is_final ? final_target : output_fb, final_left, final_top, final_width, final_height, + orig_width, orig_height, s_target_width, s_target_height)) + { + return false; + } + + if (!is_final) + { + output->MakeReadyForSampling(); + std::swap(input, output); + std::swap(input_fb, output_fb); + } + } + + return true; +} diff --git a/src/util/postprocessing.h b/src/util/postprocessing.h new file mode 100644 index 000000000..a4c4620cf --- /dev/null +++ b/src/util/postprocessing.h @@ -0,0 +1,134 @@ +// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) + +#pragma once + +#include "gpu_device.h" + +#include +#include +#include +#include + +namespace Common +{ +class Timer; +} + +class GPUSampler; +class GPUFramebuffer; +class GPUTexture; + +class Error; +class SettingsInterface; + +namespace PostProcessing { +struct ShaderOption +{ + enum : u32 + { + MAX_VECTOR_COMPONENTS = 4 + }; + + enum class Type + { + Invalid, + Bool, + Int, + Float + }; + + union Value + { + s32 int_value; + float float_value; + }; + static_assert(sizeof(Value) == sizeof(u32)); + + using ValueVector = std::array; + static_assert(sizeof(ValueVector) == sizeof(u32) * MAX_VECTOR_COMPONENTS); + + std::string name; + std::string ui_name; + std::string dependent_option; + Type type; + u32 vector_size; + u32 buffer_size; + u32 buffer_offset; + ValueVector default_value; + ValueVector min_value; + ValueVector max_value; + ValueVector step_value; + ValueVector value; + + static u32 ParseIntVector(const std::string_view& line, ValueVector* values); + static u32 ParseFloatVector(const std::string_view& line, ValueVector* values); + + static constexpr ValueVector MakeIntVector(s32 x, s32 y = 0, s32 z = 0, s32 w = 0) + { + ValueVector ret = {}; + ret[0].int_value = x; + ret[1].int_value = y; + ret[2].int_value = z; + ret[3].int_value = w; + return ret; + } + + static constexpr ValueVector MakeFloatVector(float x, float y = 0, float z = 0, float w = 0) + { + ValueVector ret = {}; + ret[0].float_value = x; + ret[1].float_value = y; + ret[2].float_value = z; + ret[3].float_value = w; + return ret; + } +}; + +// [display_name, filename] +std::vector> GetAvailableShaderNames(); + +namespace Config { +u32 GetStageCount(const SettingsInterface& si); +std::string GetStageShaderName(const SettingsInterface& si, u32 index); +std::vector GetStageOptions(const SettingsInterface& si, u32 index); + +bool AddStage(SettingsInterface& si, const std::string& shader_name, Error* error); +void RemoveStage(SettingsInterface& si, u32 index); +void MoveStageUp(SettingsInterface& si, u32 index); +void MoveStageDown(SettingsInterface& si, u32 index); +void SetStageOption(SettingsInterface& si, u32 index, const ShaderOption& option); +void UnsetStageOption(SettingsInterface& si, u32 index, const ShaderOption& option); +void ClearStages(SettingsInterface& si); +} // namespace Config + +bool IsActive(); +bool IsEnabled(); +void SetEnabled(bool enabled); + +void Initialize(); + +/// Reloads configuration. +void UpdateSettings(); + +/// Temporarily toggles post-processing on/off. +void Toggle(); + +/// Reloads post processing shaders with the current configuration. +bool ReloadShaders(); + +void Shutdown(); + +GPUTexture* GetInputTexture(); +GPUFramebuffer* GetInputFramebuffer(); +const Common::Timer& GetTimer(); + +bool CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height); + +bool Apply(GPUFramebuffer* final_target, s32 final_left, s32 final_top, s32 final_width, s32 final_height, + s32 orig_width, s32 orig_height); + +GPUSampler* GetSampler(const GPUSampler::Config& config); +GPUTexture* GetDummyTexture(); + +}; // namespace PostProcessing diff --git a/src/util/postprocessing_chain.cpp b/src/util/postprocessing_chain.cpp deleted file mode 100644 index cb61f27b3..000000000 --- a/src/util/postprocessing_chain.cpp +++ /dev/null @@ -1,279 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#include "postprocessing_chain.h" -#include "gpu_device.h" -#include "postprocessing_shader_glsl.h" - -#include "core/host.h" -#include "core/settings.h" - -#include "common/assert.h" -#include "common/file_system.h" -#include "common/log.h" -#include "common/path.h" -#include "common/string.h" - -#include "fmt/format.h" -#include - -Log_SetChannel(PostProcessingChain); - -static std::unique_ptr TryLoadingShader(const std::string_view& shader_name) -{ - std::string filename(Path::Combine(EmuFolders::Shaders, fmt::format("{}.glsl", shader_name))); - if (FileSystem::FileExists(filename.c_str())) - { - std::unique_ptr shader = std::make_unique(); - if (shader->LoadFromFile(std::string(shader_name), filename.c_str())) - return shader; - } - - std::optional resource_str( - Host::ReadResourceFileToString(fmt::format("shaders" FS_OSPATH_SEPARATOR_STR "{}.glsl", shader_name).c_str())); - if (resource_str.has_value()) - { - std::unique_ptr shader = std::make_unique(); - if (shader->LoadFromString(std::string(shader_name), std::move(resource_str.value()))) - return shader; - } - - Log_ErrorPrintf(fmt::format("Failed to load shader '{}'", shader_name).c_str()); - return {}; -} - -PostProcessingChain::PostProcessingChain() = default; - -PostProcessingChain::~PostProcessingChain() = default; - -void PostProcessingChain::AddShader(std::unique_ptr shader) -{ - m_shaders.push_back(std::move(shader)); -} - -bool PostProcessingChain::AddStage(const std::string_view& name) -{ - std::unique_ptr shader = TryLoadingShader(name); - if (!shader) - return false; - - m_shaders.push_back(std::move(shader)); - return true; -} - -std::string PostProcessingChain::GetConfigString() const -{ - std::stringstream ss; - bool first = true; - - for (const auto& shader : m_shaders) - { - if (!first) - ss << ':'; - else - first = false; - - ss << shader->GetName(); - std::string config_string = shader->GetConfigString(); - if (!config_string.empty()) - ss << ';' << config_string; - } - - return ss.str(); -} - -bool PostProcessingChain::CreateFromString(const std::string_view& chain_config) -{ - std::vector> shaders; - - size_t last_sep = 0; - while (last_sep < chain_config.size()) - { - size_t next_sep = chain_config.find(':', last_sep); - if (next_sep == std::string::npos) - next_sep = chain_config.size(); - - const std::string_view shader_config = chain_config.substr(last_sep, next_sep - last_sep); - size_t first_shader_sep = shader_config.find(';'); - if (first_shader_sep == std::string::npos) - first_shader_sep = shader_config.size(); - - const std::string_view shader_name = shader_config.substr(0, first_shader_sep); - if (!shader_name.empty()) - { - std::unique_ptr shader = TryLoadingShader(shader_name); - if (!shader) - return false; - - if (first_shader_sep < shader_config.size()) - shader->SetConfigString(shader_config.substr(first_shader_sep + 1)); - - shaders.push_back(std::move(shader)); - } - - last_sep = next_sep + 1; - } - - if (shaders.empty()) - { - Log_ErrorPrintf("Postprocessing chain is empty!"); - return false; - } - - m_shaders = std::move(shaders); - Log_InfoPrintf("Loaded postprocessing chain of %zu shaders", m_shaders.size()); - return true; -} - -std::vector PostProcessingChain::GetAvailableShaderNames() -{ - std::vector names; - - FileSystem::FindResultsArray results; - FileSystem::FindFiles(Path::Combine(EmuFolders::Resources, "shaders").c_str(), "*.glsl", - FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS, &results); - FileSystem::FindFiles(EmuFolders::Shaders.c_str(), "*.glsl", - FILESYSTEM_FIND_FILES | FILESYSTEM_FIND_RECURSIVE | FILESYSTEM_FIND_RELATIVE_PATHS | - FILESYSTEM_FIND_KEEP_ARRAY, - &results); - - for (FILESYSTEM_FIND_DATA& fd : results) - { - size_t pos = fd.FileName.rfind('.'); - if (pos != std::string::npos && pos > 0) - fd.FileName.erase(pos); - - // swap any backslashes for forward slashes so the config is cross-platform - for (size_t i = 0; i < fd.FileName.size(); i++) - { - if (fd.FileName[i] == '\\') - fd.FileName[i] = '/'; - } - - if (std::none_of(names.begin(), names.end(), [&fd](const std::string& other) { return fd.FileName == other; })) - { - names.push_back(std::move(fd.FileName)); - } - } - - return names; -} - -void PostProcessingChain::RemoveStage(u32 index) -{ - Assert(index < m_shaders.size()); - m_shaders.erase(m_shaders.begin() + index); -} - -void PostProcessingChain::MoveStageUp(u32 index) -{ - Assert(index < m_shaders.size()); - if (index == 0) - return; - - auto shader = std::move(m_shaders[index]); - m_shaders.erase(m_shaders.begin() + index); - m_shaders.insert(m_shaders.begin() + (index - 1u), std::move(shader)); -} - -void PostProcessingChain::MoveStageDown(u32 index) -{ - Assert(index < m_shaders.size()); - if (index == (m_shaders.size() - 1u)) - return; - - auto shader = std::move(m_shaders[index]); - m_shaders.erase(m_shaders.begin() + index); - m_shaders.insert(m_shaders.begin() + (index + 1u), std::move(shader)); -} - -void PostProcessingChain::ClearStages() -{ - m_shaders.clear(); -} - -bool PostProcessingChain::CheckTargets(GPUTexture::Format format, u32 target_width, u32 target_height) -{ - if (m_target_format == format && m_target_width == target_width && m_target_height == target_height) - return true; - - // In case any allocs fail. - m_target_format = GPUTexture::Format::Unknown; - m_target_width = 0; - m_target_height = 0; - m_output_framebuffer.reset(); - m_output_texture.reset(); - m_input_framebuffer.reset(); - m_input_texture.reset(); - - if (!(m_input_texture = - g_gpu_device->CreateTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget, format)) || - !(m_input_framebuffer = g_gpu_device->CreateFramebuffer(m_input_texture.get()))) - { - return false; - } - - if (!(m_output_texture = - g_gpu_device->CreateTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget, format)) || - !(m_output_framebuffer = g_gpu_device->CreateFramebuffer(m_output_texture.get()))) - { - return false; - } - - for (auto& shader : m_shaders) - { - if (!shader->CompilePipeline(format, target_width, target_height) || - !shader->ResizeOutput(format, target_width, target_height)) - { - Log_ErrorPrintf("Failed to compile one or more post-processing shaders, disabling."); - return false; - } - } - - m_target_format = format; - m_target_width = target_width; - m_target_height = target_height; - - return true; -} - -bool PostProcessingChain::Apply(GPUFramebuffer* final_target, s32 final_left, s32 final_top, s32 final_width, - s32 final_height, s32 orig_width, s32 orig_height) -{ - GL_SCOPE("PostProcessingChain Apply"); - - const u32 target_width = final_target ? final_target->GetWidth() : g_gpu_device->GetWindowWidth(); - const u32 target_height = final_target ? final_target->GetHeight() : g_gpu_device->GetWindowHeight(); - const GPUTexture::Format target_format = - final_target ? final_target->GetRT()->GetFormat() : g_gpu_device->GetWindowFormat(); - if (!CheckTargets(target_format, target_width, target_height)) - return false; - - g_gpu_device->SetViewportAndScissor(final_left, final_top, final_width, final_height); - - GPUTexture* input = m_input_texture.get(); - GPUFramebuffer* input_fb = m_input_framebuffer.get(); - GPUTexture* output = m_output_texture.get(); - GPUFramebuffer* output_fb = m_output_framebuffer.get(); - input->MakeReadyForSampling(); - - for (const std::unique_ptr& stage : m_shaders) - { - const bool is_final = (stage.get() == m_shaders.back().get()); - - if (!stage->Apply(input, is_final ? final_target : output_fb, final_left, final_top, final_width, final_height, - orig_width, orig_height, m_target_width, m_target_height)) - { - return false; - } - - if (!is_final) - { - output->MakeReadyForSampling(); - std::swap(input, output); - std::swap(input_fb, output_fb); - } - } - - return true; -} diff --git a/src/util/postprocessing_chain.h b/src/util/postprocessing_chain.h deleted file mode 100644 index a49b0421e..000000000 --- a/src/util/postprocessing_chain.h +++ /dev/null @@ -1,61 +0,0 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin -// SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) - -#pragma once -#include "gpu_device.h" -#include "postprocessing_shader.h" - -#include "common/timer.h" - -#include -#include -#include - -class GPUSampler; -class GPUFramebuffer; -class GPUTexture; - -class PostProcessingChain -{ -public: - PostProcessingChain(); - ~PostProcessingChain(); - - static std::vector GetAvailableShaderNames(); - - ALWAYS_INLINE bool IsEmpty() const { return m_shaders.empty(); } - ALWAYS_INLINE u32 GetStageCount() const { return static_cast(m_shaders.size()); } - ALWAYS_INLINE const PostProcessingShader* GetShaderStage(u32 i) const { return m_shaders[i].get(); } - ALWAYS_INLINE PostProcessingShader* GetShaderStage(u32 i) { return m_shaders[i].get(); } - ALWAYS_INLINE GPUTexture* GetInputTexture() const { return m_input_texture.get(); } - ALWAYS_INLINE GPUFramebuffer* GetInputFramebuffer() const { return m_input_framebuffer.get(); } - - void AddShader(std::unique_ptr shader); - bool AddStage(const std::string_view& name); - void RemoveStage(u32 index); - void MoveStageUp(u32 index); - void MoveStageDown(u32 index); - void ClearStages(); - - std::string GetConfigString() const; - - bool CreateFromString(const std::string_view& chain_config); - - bool CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height); - - bool Apply(GPUFramebuffer* final_target, s32 final_left, s32 final_top, s32 final_width, s32 final_height, - s32 orig_width, s32 orig_height); - -private: - std::vector> m_shaders; - - GPUTexture::Format m_target_format = GPUTexture::Format::Unknown; - u32 m_target_width = 0; - u32 m_target_height = 0; - - std::unique_ptr m_input_texture; - std::unique_ptr m_input_framebuffer; - - std::unique_ptr m_output_texture; - std::unique_ptr m_output_framebuffer; -}; diff --git a/src/util/postprocessing_shader.cpp b/src/util/postprocessing_shader.cpp index 7e4bee367..3fcfd6cd3 100644 --- a/src/util/postprocessing_shader.cpp +++ b/src/util/postprocessing_shader.cpp @@ -11,9 +11,9 @@ #include #include -Log_SetChannel(PostProcessingShader); +Log_SetChannel(PostProcessing); -void PostProcessingShader::ParseKeyValue(const std::string_view& line, std::string_view* key, std::string_view* value) +void PostProcessing::Shader::ParseKeyValue(const std::string_view& line, std::string_view* key, std::string_view* value) { size_t key_start = 0; while (key_start < line.size() && std::isspace(line[key_start])) @@ -48,168 +48,85 @@ void PostProcessingShader::ParseKeyValue(const std::string_view& line, std::stri *value = line.substr(value_start, value_end - value_start); } -template -u32 PostProcessingShader::ParseVector(const std::string_view& line, PostProcessingShader::Option::ValueVector* values) -{ - u32 index = 0; - size_t start = 0; - while (index < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS) - { - while (start < line.size() && std::isspace(line[start])) - start++; +PostProcessing::Shader::Shader() = default; - if (start >= line.size()) - break; - - size_t end = line.find(',', start); - if (end == std::string_view::npos) - end = line.size(); - - const std::string_view component = line.substr(start, end - start); - T value = StringUtil::FromChars(component).value_or(static_cast(0)); - if constexpr (std::is_same_v) - (*values)[index++].float_value = value; - else if constexpr (std::is_same_v) - (*values)[index++].int_value = value; - - start = end + 1; - } - - const u32 size = index; - - for (; index < PostProcessingShader::Option::MAX_VECTOR_COMPONENTS; index++) - { - if constexpr (std::is_same_v) - (*values)[index++].float_value = 0.0f; - else if constexpr (std::is_same_v) - (*values)[index++].int_value = 0; - } - - return size; -} - -template u32 PostProcessingShader::ParseVector(const std::string_view& line, - PostProcessingShader::Option::ValueVector* values); -template u32 PostProcessingShader::ParseVector(const std::string_view& line, - PostProcessingShader::Option::ValueVector* values); - -PostProcessingShader::PostProcessingShader() = default; - -PostProcessingShader::PostProcessingShader(std::string name) : m_name(std::move(name)) +PostProcessing::Shader::Shader(std::string name) : m_name(std::move(name)) { } -PostProcessingShader::~PostProcessingShader() = default; +PostProcessing::Shader::~Shader() = default; -bool PostProcessingShader::IsValid() const +bool PostProcessing::Shader::IsValid() const { return false; } -const PostProcessingShader::Option* PostProcessingShader::GetOptionByName(const std::string_view& name) const +std::vector PostProcessing::Shader::TakeOptions() { - for (const Option& option : m_options) - { - if (option.name == name) - return &option; - } - - return nullptr; + return std::move(m_options); } -PostProcessingShader::Option* PostProcessingShader::GetOptionByName(const std::string_view& name) +void PostProcessing::Shader::LoadOptions(SettingsInterface& si, const char* section) { - for (Option& option : m_options) + for (ShaderOption& option : m_options) { - if (option.name == name) - return &option; - } - - return nullptr; -} - -std::string PostProcessingShader::GetConfigString() const -{ - std::stringstream ss; - bool first = true; - for (const Option& option : m_options) - { - if (!first) - ss << ';'; - else - first = false; - - ss << option.name; - ss << '='; - - for (u32 i = 0; i < option.vector_size; i++) + if (option.type == ShaderOption::Type::Bool) { - if (i > 0) - ss << ","; - - switch (option.type) + const bool new_value = si.GetBoolValue(section, option.name.c_str(), option.default_value[0].int_value != 0); + if ((option.value[0].int_value != 0) != new_value) { - case Option::Type::Bool: - ss << ((option.value[i].int_value != 0) ? "true" : "false"); - break; - - case Option::Type::Int: - ss << option.value[i].int_value; - break; - - case Option::Type::Float: - ss << option.value[i].float_value; - break; - - default: - break; + option.value[0].int_value = new_value ? 1 : 0; + OnOptionChanged(option); } } - } - - return ss.str(); -} - -void PostProcessingShader::SetConfigString(const std::string_view& str) -{ - for (Option& option : m_options) - option.value = option.default_value; - - size_t last_sep = 0; - while (last_sep < str.size()) - { - size_t next_sep = str.find(';', last_sep); - if (next_sep == std::string_view::npos) - next_sep = str.size(); - - const std::string_view kv = str.substr(last_sep, next_sep - last_sep); - std::string_view key, value; - ParseKeyValue(kv, &key, &value); - if (!key.empty() && !value.empty()) + else { - Option* option = GetOptionByName(key); - if (option) + ShaderOption::ValueVector value = option.default_value; + + std::string config_value; + if (si.GetStringValue(section, option.name.c_str(), &config_value)) { - switch (option->type) + const u32 value_vector_size = (option.type == ShaderOption::Type::Int) ? + ShaderOption::ParseIntVector(config_value, &value) : + ShaderOption::ParseFloatVector(config_value, &value); + if (value_vector_size != option.vector_size) { - case Option::Type::Bool: - option->value[0].int_value = StringUtil::FromChars(value).value_or(false) ? 1 : 0; - break; - - case Option::Type::Int: - ParseVector(value, &option->value); - break; - - case Option::Type::Float: - ParseVector(value, &option->value); - break; - - default: - break; + Log_WarningPrintf("Only got %u of %u elements for '%s' in config section %s.", value_vector_size, + option.vector_size, option.name.c_str(), section); } } - } - last_sep = next_sep + 1; + if (std::memcmp(&option.value, &value, sizeof(value)) != 0) + { + option.value = value; + OnOptionChanged(option); + } + } } } + +const PostProcessing::ShaderOption* PostProcessing::Shader::GetOptionByName(const std::string_view& name) const +{ + for (const ShaderOption& option : m_options) + { + if (option.name == name) + return &option; + } + + return nullptr; +} + +PostProcessing::ShaderOption* PostProcessing::Shader::GetOptionByName(const std::string_view& name) +{ + for (ShaderOption& option : m_options) + { + if (option.name == name) + return &option; + } + + return nullptr; +} + +void PostProcessing::Shader::OnOptionChanged(const ShaderOption& option) +{ +} diff --git a/src/util/postprocessing_shader.h b/src/util/postprocessing_shader.h index 6833b21c6..fd8531e9c 100644 --- a/src/util/postprocessing_shader.h +++ b/src/util/postprocessing_shader.h @@ -3,7 +3,12 @@ #pragma once +#include "postprocessing.h" + +#include "gpu_texture.h" + #include "common/rectangle.h" +#include "common/settings_interface.h" #include "common/timer.h" #include "common/types.h" #include "gpu_device.h" @@ -16,68 +21,27 @@ class GPUPipeline; class GPUTexture; -class PostProcessingChain; +namespace PostProcessing { -class PostProcessingShader +class Shader { - friend PostProcessingChain; - public: - struct Option - { - enum : u32 - { - MAX_VECTOR_COMPONENTS = 4 - }; - - enum class Type - { - Invalid, - Bool, - Int, - Float - }; - - union Value - { - s32 int_value; - float float_value; - }; - static_assert(sizeof(Value) == sizeof(u32)); - - using ValueVector = std::array; - static_assert(sizeof(ValueVector) == sizeof(u32) * MAX_VECTOR_COMPONENTS); - - std::string name; - std::string ui_name; - std::string dependent_option; - Type type; - u32 vector_size; - u32 buffer_size; - u32 buffer_offset; - ValueVector default_value; - ValueVector min_value; - ValueVector max_value; - ValueVector step_value; - ValueVector value; - }; - - PostProcessingShader(); - PostProcessingShader(std::string name); - virtual ~PostProcessingShader(); + Shader(); + Shader(std::string name); + virtual ~Shader(); ALWAYS_INLINE const std::string& GetName() const { return m_name; } - ALWAYS_INLINE const std::vector
- + @@ -242,4 +242,4 @@ - \ No newline at end of file + diff --git a/src/util/util.vcxproj.filters b/src/util/util.vcxproj.filters index 1a4021dc3..b0e38a028 100644 --- a/src/util/util.vcxproj.filters +++ b/src/util/util.vcxproj.filters @@ -19,7 +19,7 @@ - + @@ -98,7 +98,7 @@ - + @@ -150,4 +150,4 @@ {e637fc5b-2483-4a31-abc3-89a16d45c223} - \ No newline at end of file +