From 0c3cf1f5f8dfc26f2b5db2e082fc75a0bcaf2043 Mon Sep 17 00:00:00 2001 From: Stenzek Date: Fri, 28 Jun 2024 15:11:14 +1000 Subject: [PATCH] PostProcessing: Split into internal and display chains --- src/core/fullscreen_ui.cpp | 41 +- src/core/gpu.cpp | 83 +- src/core/gpu_hw.cpp | 4 +- src/core/hotkeys.cpp | 8 +- src/duckstation-qt/CMakeLists.txt | 2 +- src/duckstation-qt/duckstation-qt.vcxproj | 2 +- .../duckstation-qt.vcxproj.filters | 2 +- ....ui => postprocessingchainconfigwidget.ui} | 56 +- .../postprocessingsettingswidget.cpp | 96 ++- .../postprocessingsettingswidget.h | 31 +- src/util/postprocessing.cpp | 712 +++++++++--------- src/util/postprocessing.h | 96 ++- 12 files changed, 615 insertions(+), 518 deletions(-) rename src/duckstation-qt/{postprocessingsettingswidget.ui => postprocessingchainconfigwidget.ui} (80%) diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp index 409af8434..49e03d93e 100644 --- a/src/core/fullscreen_ui.cpp +++ b/src/core/fullscreen_ui.cpp @@ -390,7 +390,7 @@ static void DrawFolderSetting(SettingsInterface* bsi, const char* title, const c static void PopulateGraphicsAdapterList(); static void PopulateGameListDirectoryCache(SettingsInterface* si); -static void PopulatePostProcessingChain(SettingsInterface* si); +static void PopulatePostProcessingChain(SettingsInterface* si, const char* section); static void BeginInputBinding(SettingsInterface* bsi, InputBindingInfo::Type type, std::string_view section, std::string_view key, std::string_view display_name); static void DrawInputBindingWindow(); @@ -2733,7 +2733,7 @@ void FullscreenUI::SwitchToSettings() s_game_settings_interface.reset(); PopulateGraphicsAdapterList(); - PopulatePostProcessingChain(GetEditingSettingsInterface()); + PopulatePostProcessingChain(GetEditingSettingsInterface(), PostProcessing::Config::DISPLAY_CHAIN_SECTION); s_current_main_window = MainWindowType::Settings; s_settings_page = SettingsPage::Interface; @@ -4489,16 +4489,16 @@ void FullscreenUI::DrawDisplaySettingsPage() EndMenuButtons(); } -void FullscreenUI::PopulatePostProcessingChain(SettingsInterface* si) +void FullscreenUI::PopulatePostProcessingChain(SettingsInterface* si, const char* section) { - const u32 stages = PostProcessing::Config::GetStageCount(*si); + const u32 stages = PostProcessing::Config::GetStageCount(*si, section); s_postprocessing_stages.clear(); 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); + psi.name = PostProcessing::Config::GetStageShaderName(*si, section, i); + psi.options = PostProcessing::Config::GetStageOptions(*si, section, i); s_postprocessing_stages.push_back(std::move(psi)); } } @@ -4514,6 +4514,7 @@ enum void FullscreenUI::DrawPostProcessingSettingsPage() { SettingsInterface* bsi = GetEditingSettingsInterface(); + static constexpr const char* section = PostProcessing::Config::DISPLAY_CHAIN_SECTION; BeginMenuButtons(); @@ -4549,11 +4550,11 @@ void FullscreenUI::DrawPostProcessingSettingsPage() const std::string& shader_name = shaders[index].second; SettingsInterface* bsi = GetEditingSettingsInterface(); Error error; - if (PostProcessing::Config::AddStage(*bsi, shader_name, &error)) + if (PostProcessing::Config::AddStage(*bsi, section, shader_name, &error)) { ShowToast(std::string(), fmt::format(FSUI_FSTR("Shader {} added as stage {}."), title, - PostProcessing::Config::GetStageCount(*bsi))); - PopulatePostProcessingChain(bsi); + PostProcessing::Config::GetStageCount(*bsi, section))); + PopulatePostProcessingChain(bsi, section); SetSettingsChanged(bsi); } else @@ -4577,8 +4578,8 @@ void FullscreenUI::DrawPostProcessingSettingsPage() return; SettingsInterface* bsi = GetEditingSettingsInterface(); - PostProcessing::Config::ClearStages(*bsi); - PopulatePostProcessingChain(bsi); + PostProcessing::Config::ClearStages(*bsi, section); + PopulatePostProcessingChain(bsi, section); SetSettingsChanged(bsi); ShowToast(std::string(), FSUI_STR("Post-processing chain cleared.")); }); @@ -4635,7 +4636,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage() &value)) { opt.value[0].int_value = (value != 0); - PostProcessing::Config::SetStageOption(*bsi, stage_index, opt); + PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt); SetSettingsChanged(bsi); } } @@ -4719,7 +4720,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage() if (changed) { - PostProcessing::Config::SetStageOption(*bsi, stage_index, opt); + PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt); SetSettingsChanged(bsi); } #endif @@ -4817,7 +4818,7 @@ void FullscreenUI::DrawPostProcessingSettingsPage() if (changed) { - PostProcessing::Config::SetStageOption(*bsi, stage_index, opt); + PostProcessing::Config::SetStageOption(*bsi, section, stage_index, opt); SetSettingsChanged(bsi); } #endif @@ -4853,22 +4854,22 @@ void FullscreenUI::DrawPostProcessingSettingsPage() const PostProcessingStageInfo& si = s_postprocessing_stages[postprocessing_action_index]; ShowToast(std::string(), fmt::format(FSUI_FSTR("Removed stage {} ({})."), postprocessing_action_index + 1, si.name)); - PostProcessing::Config::RemoveStage(*bsi, postprocessing_action_index); - PopulatePostProcessingChain(bsi); + PostProcessing::Config::RemoveStage(*bsi, section, postprocessing_action_index); + PopulatePostProcessingChain(bsi, section); SetSettingsChanged(bsi); } break; case POSTPROCESSING_ACTION_MOVE_UP: { - PostProcessing::Config::MoveStageUp(*bsi, postprocessing_action_index); - PopulatePostProcessingChain(bsi); + PostProcessing::Config::MoveStageUp(*bsi, section, postprocessing_action_index); + PopulatePostProcessingChain(bsi, section); SetSettingsChanged(bsi); } break; case POSTPROCESSING_ACTION_MOVE_DOWN: { - PostProcessing::Config::MoveStageDown(*bsi, postprocessing_action_index); - PopulatePostProcessingChain(bsi); + PostProcessing::Config::MoveStageDown(*bsi, section, postprocessing_action_index); + PopulatePostProcessingChain(bsi, section); SetSettingsChanged(bsi); } break; diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp index 38322fa6a..1ca56caad 100644 --- a/src/core/gpu.cpp +++ b/src/core/gpu.cpp @@ -56,6 +56,8 @@ static u64 s_active_gpu_cycles = 0; static u32 s_active_gpu_cycles_frames = 0; #endif +static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture::Format::RGBA8; + GPU::GPU() { ResetStatistics(); @@ -1932,24 +1934,53 @@ bool GPU::PresentDisplay() bool GPU::RenderDisplay(GPUTexture* target, const Common::Rectangle& draw_rect, bool postfx) { - GL_SCOPE_FMT("RenderDisplay: {}x{} at {},{}", draw_rect.left, draw_rect.top, draw_rect.GetWidth(), - draw_rect.GetHeight()); + GL_SCOPE_FMT("RenderDisplay: {}x{} at {},{}", draw_rect.GetWidth(), draw_rect.GetHeight(), draw_rect.left, + draw_rect.top); if (m_display_texture) m_display_texture->MakeReadyForSampling(); + // Internal post-processing. + GPUTexture* display_texture = m_display_texture; + s32 display_texture_view_x = m_display_texture_view_x; + s32 display_texture_view_y = m_display_texture_view_y; + s32 display_texture_view_width = m_display_texture_view_width; + s32 display_texture_view_height = m_display_texture_view_height; + if (postfx && display_texture && PostProcessing::InternalChain.IsActive() && + PostProcessing::InternalChain.CheckTargets(DISPLAY_INTERNAL_POSTFX_FORMAT, display_texture_view_width, + display_texture_view_height)) + { + DebugAssert(display_texture_view_x == 0 && display_texture_view_y == 0 && + static_cast(display_texture->GetWidth()) == display_texture_view_width && + static_cast(display_texture->GetHeight()) == display_texture_view_height); + + // Now we can apply the post chain. + GPUTexture* post_output_texture = PostProcessing::InternalChain.GetOutputTexture(); + if (PostProcessing::InternalChain.Apply(display_texture, post_output_texture, 0, 0, display_texture_view_width, + display_texture_view_height, display_texture_view_width, + display_texture_view_height, m_crtc_state.display_width, + m_crtc_state.display_height)) + { + display_texture_view_x = 0; + display_texture_view_y = 0; + display_texture = post_output_texture; + display_texture->MakeReadyForSampling(); + } + } + const GPUTexture::Format hdformat = target ? target->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 && PostProcessing::IsActive() && !g_gpu_device->GetWindowInfo().IsSurfaceless() && - hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 && - PostProcessing::CheckTargets(hdformat, target_width, target_height)); + const bool really_postfx = + (postfx && PostProcessing::DisplayChain.IsActive() && !g_gpu_device->GetWindowInfo().IsSurfaceless() && + hdformat != GPUTexture::Format::Unknown && target_width > 0 && target_height > 0 && + PostProcessing::DisplayChain.CheckTargets(hdformat, target_width, target_height)); const Common::Rectangle real_draw_rect = g_gpu_device->UsesLowerLeftOrigin() ? GPUDevice::FlipToLowerLeft(draw_rect, target_height) : draw_rect; if (really_postfx) { - g_gpu_device->ClearRenderTarget(PostProcessing::GetInputTexture(), 0); - g_gpu_device->SetRenderTarget(PostProcessing::GetInputTexture()); + g_gpu_device->ClearRenderTarget(PostProcessing::DisplayChain.GetInputTexture(), 0); + g_gpu_device->SetRenderTarget(PostProcessing::DisplayChain.GetInputTexture()); } else { @@ -1959,7 +1990,7 @@ bool GPU::RenderDisplay(GPUTexture* target, const Common::Rectangle& draw_r return false; } - if (m_display_texture) + if (display_texture) { bool texture_filter_linear = false; @@ -2003,26 +2034,25 @@ bool GPU::RenderDisplay(GPUTexture* target, const Common::Rectangle& draw_r } g_gpu_device->SetPipeline(m_display_pipeline.get()); - g_gpu_device->SetTextureSampler(0, m_display_texture, - texture_filter_linear ? g_gpu_device->GetLinearSampler() : - g_gpu_device->GetNearestSampler()); + g_gpu_device->SetTextureSampler( + 0, display_texture, texture_filter_linear ? g_gpu_device->GetLinearSampler() : g_gpu_device->GetNearestSampler()); // For bilinear, clamp to 0.5/SIZE-0.5 to avoid bleeding from the adjacent texels in VRAM. This is because // 1.0 in UV space is not the bottom-right texel, but a mix of the bottom-right and wrapped/next texel. - const float rcp_width = 1.0f / static_cast(m_display_texture->GetWidth()); - const float rcp_height = 1.0f / static_cast(m_display_texture->GetHeight()); - uniforms.src_rect[0] = static_cast(m_display_texture_view_x) * rcp_width; - uniforms.src_rect[1] = static_cast(m_display_texture_view_y) * rcp_height; - uniforms.src_rect[2] = static_cast(m_display_texture_view_width) * rcp_width; - uniforms.src_rect[3] = static_cast(m_display_texture_view_height) * rcp_height; - uniforms.clamp_rect[0] = (static_cast(m_display_texture_view_x) + 0.5f) * rcp_width; - uniforms.clamp_rect[1] = (static_cast(m_display_texture_view_y) + 0.5f) * rcp_height; + const float rcp_width = 1.0f / static_cast(display_texture->GetWidth()); + const float rcp_height = 1.0f / static_cast(display_texture->GetHeight()); + uniforms.src_rect[0] = static_cast(display_texture_view_x) * rcp_width; + uniforms.src_rect[1] = static_cast(display_texture_view_y) * rcp_height; + uniforms.src_rect[2] = static_cast(display_texture_view_width) * rcp_width; + uniforms.src_rect[3] = static_cast(display_texture_view_height) * rcp_height; + uniforms.clamp_rect[0] = (static_cast(display_texture_view_x) + 0.5f) * rcp_width; + uniforms.clamp_rect[1] = (static_cast(display_texture_view_y) + 0.5f) * rcp_height; uniforms.clamp_rect[2] = - (static_cast(m_display_texture_view_x + m_display_texture_view_width) - 0.5f) * rcp_width; + (static_cast(display_texture_view_x + display_texture_view_width) - 0.5f) * rcp_width; uniforms.clamp_rect[3] = - (static_cast(m_display_texture_view_y + m_display_texture_view_height) - 0.5f) * rcp_height; - uniforms.src_size[0] = static_cast(m_display_texture->GetWidth()); - uniforms.src_size[1] = static_cast(m_display_texture->GetHeight()); + (static_cast(display_texture_view_y + display_texture_view_height) - 0.5f) * rcp_height; + uniforms.src_size[0] = static_cast(display_texture->GetWidth()); + uniforms.src_size[1] = static_cast(display_texture->GetHeight()); uniforms.src_size[2] = rcp_width; uniforms.src_size[3] = rcp_height; g_gpu_device->PushUniformBuffer(&uniforms, sizeof(uniforms)); @@ -2044,9 +2074,10 @@ bool GPU::RenderDisplay(GPUTexture* target, const Common::Rectangle& draw_r const s32 orig_width = static_cast(std::ceil(static_cast(m_crtc_state.display_width) * upscale_x)); const s32 orig_height = static_cast(std::ceil(static_cast(m_crtc_state.display_height) * upscale_y)); - return PostProcessing::Apply(target, real_draw_rect.left, real_draw_rect.top, real_draw_rect.GetWidth(), - real_draw_rect.GetHeight(), orig_width, orig_height, m_crtc_state.display_width, - m_crtc_state.display_height); + return PostProcessing::DisplayChain.Apply(PostProcessing::DisplayChain.GetInputTexture(), target, + real_draw_rect.left, real_draw_rect.top, real_draw_rect.GetWidth(), + real_draw_rect.GetHeight(), orig_width, orig_height, + m_crtc_state.display_width, m_crtc_state.display_height); } else { diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp index 988df86d0..b2bac5056 100644 --- a/src/core/gpu_hw.cpp +++ b/src/core/gpu_hw.cpp @@ -11,6 +11,7 @@ #include "system.h" #include "util/imgui_manager.h" +#include "util/postprocessing.h" #include "util/state_wrapper.h" #include "common/align.h" @@ -3311,7 +3312,8 @@ void GPU_HW::UpdateDisplay() } else if (!m_GPUSTAT.display_area_color_depth_24 && !IsUsingMultisampling() && (scaled_vram_offset_x + scaled_display_width) <= m_vram_texture->GetWidth() && - (scaled_vram_offset_y + scaled_display_height) <= m_vram_texture->GetHeight()) + (scaled_vram_offset_y + scaled_display_height) <= m_vram_texture->GetHeight() && + !PostProcessing::InternalChain.IsActive()) { SetDisplayTexture(m_vram_texture.get(), scaled_vram_offset_x, scaled_vram_offset_y, scaled_display_width, read_height); diff --git a/src/core/hotkeys.cpp b/src/core/hotkeys.cpp index 9a002684b..296c17666 100644 --- a/src/core/hotkeys.cpp +++ b/src/core/hotkeys.cpp @@ -406,7 +406,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()) - PostProcessing::Toggle(); + PostProcessing::DisplayChain.Toggle(); + }) + + DEFINE_HOTKEY("ToggleInternalPostProcessing", TRANSLATE_NOOP("Hotkeys", "Graphics"), + TRANSLATE_NOOP("Hotkeys", "Toggle Internal Post-Processing"), [](s32 pressed) { + if (!pressed && System::IsValid()) + PostProcessing::InternalChain.Toggle(); }) DEFINE_HOTKEY("ReloadPostProcessingShaders", TRANSLATE_NOOP("Hotkeys", "Graphics"), diff --git a/src/duckstation-qt/CMakeLists.txt b/src/duckstation-qt/CMakeLists.txt index dca1e3f4d..f4ee00202 100644 --- a/src/duckstation-qt/CMakeLists.txt +++ b/src/duckstation-qt/CMakeLists.txt @@ -126,7 +126,7 @@ set(SRCS memoryviewwidget.h postprocessingsettingswidget.cpp postprocessingsettingswidget.h - postprocessingsettingswidget.ui + postprocessingchainconfigwidget.ui qthost.cpp qthost.h qtkeycodes.cpp diff --git a/src/duckstation-qt/duckstation-qt.vcxproj b/src/duckstation-qt/duckstation-qt.vcxproj index 1696c6390..8f05fc0b9 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj +++ b/src/duckstation-qt/duckstation-qt.vcxproj @@ -134,7 +134,7 @@ Document - + Document diff --git a/src/duckstation-qt/duckstation-qt.vcxproj.filters b/src/duckstation-qt/duckstation-qt.vcxproj.filters index cfe5d458f..c4459a40b 100644 --- a/src/duckstation-qt/duckstation-qt.vcxproj.filters +++ b/src/duckstation-qt/duckstation-qt.vcxproj.filters @@ -255,7 +255,7 @@ - + diff --git a/src/duckstation-qt/postprocessingsettingswidget.ui b/src/duckstation-qt/postprocessingchainconfigwidget.ui similarity index 80% rename from src/duckstation-qt/postprocessingsettingswidget.ui rename to src/duckstation-qt/postprocessingchainconfigwidget.ui index 18c395f85..73ff4744c 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.ui +++ b/src/duckstation-qt/postprocessingchainconfigwidget.ui @@ -1,7 +1,7 @@ - PostProcessingSettingsWidget - + PostProcessingChainConfigWidget + 0 @@ -11,18 +11,6 @@ - - 0 - - - 0 - - - 0 - - - 0 - @@ -35,7 +23,7 @@ - Qt::Horizontal + Qt::Orientation::Horizontal @@ -51,8 +39,7 @@ &Reload Shaders - - .. + @@ -94,11 +81,10 @@ Add - - .. + - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -114,11 +100,10 @@ Remove - - .. + - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -134,11 +119,10 @@ Clear - - .. + - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -154,11 +138,10 @@ Move Up - - .. + - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -174,11 +157,10 @@ Move Down - - .. + - Qt::ToolButtonTextBesideIcon + Qt::ToolButtonStyle::ToolButtonTextBesideIcon @@ -196,10 +178,10 @@ - QFrame::WinPanel + QFrame::Shape::WinPanel - QFrame::Sunken + QFrame::Shadow::Sunken true @@ -209,8 +191,8 @@ 0 0 - 679 - 238 + 661 + 220 @@ -219,7 +201,7 @@ - Qt::Vertical + Qt::Orientation::Vertical diff --git a/src/duckstation-qt/postprocessingsettingswidget.cpp b/src/duckstation-qt/postprocessingsettingswidget.cpp index 0942e22df..1cefb02f6 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.cpp +++ b/src/duckstation-qt/postprocessingsettingswidget.cpp @@ -16,28 +16,40 @@ #include #include -PostProcessingSettingsWidget::PostProcessingSettingsWidget(SettingsWindow* dialog, QWidget* parent) - : QWidget(parent), m_dialog(dialog) +PostProcessingSettingsWidget::PostProcessingSettingsWidget(SettingsWindow* dialog, QWidget* parent) : QTabWidget(parent) +{ + addTab(new PostProcessingChainConfigWidget(dialog, this, PostProcessing::Config::DISPLAY_CHAIN_SECTION), + tr("Display")); + addTab(new PostProcessingChainConfigWidget(dialog, this, PostProcessing::Config::INTERNAL_CHAIN_SECTION), + tr("Internal")); + setDocumentMode(true); +} + +PostProcessingSettingsWidget::~PostProcessingSettingsWidget() = default; + +PostProcessingChainConfigWidget::PostProcessingChainConfigWidget(SettingsWindow* dialog, QWidget* parent, + const char* section) + : QWidget(parent), m_dialog(dialog), m_section(section) { SettingsInterface* sif = dialog->getSettingsInterface(); m_ui.setupUi(this); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enablePostProcessing, "PostProcessing", "Enabled", false); + SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.enablePostProcessing, section, "Enabled", false); updateList(); updateButtonsAndConfigPane(std::nullopt); connectUi(); } -PostProcessingSettingsWidget::~PostProcessingSettingsWidget() = default; +PostProcessingChainConfigWidget::~PostProcessingChainConfigWidget() = default; -SettingsInterface& PostProcessingSettingsWidget::getSettingsInterfaceToUpdate() +SettingsInterface& PostProcessingChainConfigWidget::getSettingsInterfaceToUpdate() { return m_dialog->isPerGameSettings() ? *m_dialog->getSettingsInterface() : *Host::Internal::GetBaseSettingsLayer(); } -void PostProcessingSettingsWidget::commitSettingsUpdate() +void PostProcessingChainConfigWidget::commitSettingsUpdate() { if (m_dialog->isPerGameSettings()) { @@ -50,26 +62,26 @@ void PostProcessingSettingsWidget::commitSettingsUpdate() } } -void PostProcessingSettingsWidget::connectUi() +void PostProcessingChainConfigWidget::connectUi() { - 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.reload, &QPushButton::clicked, this, &PostProcessingChainConfigWidget::onReloadButtonClicked); + 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.stages, &QListWidget::itemSelectionChanged, this, - &PostProcessingSettingsWidget::onSelectedShaderChanged); + &PostProcessingChainConfigWidget::onSelectedShaderChanged); } -std::optional PostProcessingSettingsWidget::getSelectedIndex() const +std::optional PostProcessingChainConfigWidget::getSelectedIndex() const { QList selected_items = m_ui.stages->selectedItems(); return selected_items.empty() ? std::nullopt : std::optional(selected_items.first()->data(Qt::UserRole).toUInt()); } -void PostProcessingSettingsWidget::selectIndex(s32 index) +void PostProcessingChainConfigWidget::selectIndex(s32 index) { if (index < 0 || index >= m_ui.stages->count()) return; @@ -79,7 +91,7 @@ void PostProcessingSettingsWidget::selectIndex(s32 index) updateButtonsAndConfigPane(index); } -void PostProcessingSettingsWidget::updateList() +void PostProcessingChainConfigWidget::updateList() { const auto lock = Host::GetSettingsLock(); const SettingsInterface& si = getSettingsInterfaceToUpdate(); @@ -87,15 +99,15 @@ void PostProcessingSettingsWidget::updateList() updateList(si); } -void PostProcessingSettingsWidget::updateList(const SettingsInterface& si) +void PostProcessingChainConfigWidget::updateList(const SettingsInterface& si) { m_ui.stages->clear(); - const u32 stage_count = PostProcessing::Config::GetStageCount(si); + const u32 stage_count = PostProcessing::Config::GetStageCount(si, m_section); for (u32 i = 0; i < stage_count; i++) { - const std::string stage_name = PostProcessing::Config::GetStageShaderName(si, i); + const std::string stage_name = PostProcessing::Config::GetStageShaderName(si, m_section, i); QListWidgetItem* item = new QListWidgetItem(QString::fromStdString(stage_name), m_ui.stages); item->setData(Qt::UserRole, QVariant(i)); } @@ -105,7 +117,7 @@ void PostProcessingSettingsWidget::updateList(const SettingsInterface& si) updateButtonsAndConfigPane(std::nullopt); } -void PostProcessingSettingsWidget::updateButtonsAndConfigPane(std::optional index) +void PostProcessingChainConfigWidget::updateButtonsAndConfigPane(std::optional index) { m_ui.remove->setEnabled(index.has_value()); @@ -134,16 +146,18 @@ void PostProcessingSettingsWidget::updateButtonsAndConfigPane(std::optional const auto lock = Host::GetSettingsLock(); const SettingsInterface& si = getSettingsInterfaceToUpdate(); - std::vector options = PostProcessing::Config::GetStageOptions(si, index.value()); + std::vector options = + PostProcessing::Config::GetStageOptions(si, m_section, index.value()); if (options.empty()) return; - m_shader_config = new PostProcessingShaderConfigWidget(m_ui.scrollArea, this, index.value(), std::move(options)); + m_shader_config = + new PostProcessingShaderConfigWidget(m_ui.scrollArea, this, m_section, index.value(), std::move(options)); m_ui.scrollArea->setWidget(m_shader_config); m_ui.scrollArea->setVisible(true); } -void PostProcessingSettingsWidget::onAddButtonClicked() +void PostProcessingChainConfigWidget::onAddButtonClicked() { QMenu menu; @@ -162,7 +176,7 @@ void PostProcessingSettingsWidget::onAddButtonClicked() SettingsInterface& si = getSettingsInterfaceToUpdate(); Error error; - if (!PostProcessing::Config::AddStage(si, shader, &error)) + if (!PostProcessing::Config::AddStage(si, m_section, shader, &error)) { lock.unlock(); QMessageBox::critical(this, tr("Error"), @@ -180,7 +194,7 @@ void PostProcessingSettingsWidget::onAddButtonClicked() menu.exec(QCursor::pos()); } -void PostProcessingSettingsWidget::onRemoveButtonClicked() +void PostProcessingChainConfigWidget::onRemoveButtonClicked() { QList selected_items = m_ui.stages->selectedItems(); if (selected_items.empty()) @@ -191,37 +205,37 @@ void PostProcessingSettingsWidget::onRemoveButtonClicked() QListWidgetItem* item = selected_items.first(); u32 index = item->data(Qt::UserRole).toUInt(); - if (index < PostProcessing::Config::GetStageCount(si)) + if (index < PostProcessing::Config::GetStageCount(si, m_section)) { - PostProcessing::Config::RemoveStage(si, index); + PostProcessing::Config::RemoveStage(si, m_section, index); updateList(si); lock.unlock(); commitSettingsUpdate(); } } -void PostProcessingSettingsWidget::onClearButtonClicked() +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) { auto lock = Host::GetSettingsLock(); SettingsInterface& si = getSettingsInterfaceToUpdate(); - PostProcessing::Config::ClearStages(si); + PostProcessing::Config::ClearStages(si, m_section); updateList(si); lock.unlock(); commitSettingsUpdate(); } } -void PostProcessingSettingsWidget::onMoveUpButtonClicked() +void PostProcessingChainConfigWidget::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()); + PostProcessing::Config::MoveStageUp(si, m_section, index.value()); updateList(si); lock.unlock(); selectIndex(index.value() - 1); @@ -229,14 +243,14 @@ void PostProcessingSettingsWidget::onMoveUpButtonClicked() } } -void PostProcessingSettingsWidget::onMoveDownButtonClicked() +void PostProcessingChainConfigWidget::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()); + PostProcessing::Config::MoveStageDown(si, m_section, index.value()); updateList(si); lock.unlock(); selectIndex(index.value() + 1); @@ -244,22 +258,22 @@ void PostProcessingSettingsWidget::onMoveDownButtonClicked() } } -void PostProcessingSettingsWidget::onReloadButtonClicked() +void PostProcessingChainConfigWidget::onReloadButtonClicked() { g_emu_thread->reloadPostProcessingShaders(); } -void PostProcessingSettingsWidget::onSelectedShaderChanged() +void PostProcessingChainConfigWidget::onSelectedShaderChanged() { std::optional index = getSelectedIndex(); updateButtonsAndConfigPane(index); } PostProcessingShaderConfigWidget::PostProcessingShaderConfigWidget(QWidget* parent, - PostProcessingSettingsWidget* widget, - u32 stage_index, + PostProcessingChainConfigWidget* widget, + const char* section, u32 stage_index, std::vector options) - : QWidget(parent), m_widget(widget), m_stage_index(stage_index), m_options(std::move(options)) + : QWidget(parent), m_widget(widget), m_section(section), m_stage_index(stage_index), m_options(std::move(options)) { m_layout = new QGridLayout(this); @@ -272,7 +286,7 @@ void PostProcessingShaderConfigWidget::updateConfigForOption(const PostProcessin { const auto lock = Host::GetSettingsLock(); SettingsInterface& si = m_widget->getSettingsInterfaceToUpdate(); - PostProcessing::Config::SetStageOption(si, m_stage_index, option); + PostProcessing::Config::SetStageOption(si, m_section, m_stage_index, option); m_widget->commitSettingsUpdate(); } @@ -287,7 +301,7 @@ void PostProcessingShaderConfigWidget::onResetDefaultsClicked() continue; option.value = option.default_value; - PostProcessing::Config::UnsetStageOption(si, m_stage_index, option); + PostProcessing::Config::UnsetStageOption(si, m_section, m_stage_index, option); } m_widget->commitSettingsUpdate(); } diff --git a/src/duckstation-qt/postprocessingsettingswidget.h b/src/duckstation-qt/postprocessingsettingswidget.h index b188be0fe..d20ad48d2 100644 --- a/src/duckstation-qt/postprocessingsettingswidget.h +++ b/src/duckstation-qt/postprocessingsettingswidget.h @@ -1,26 +1,36 @@ -// SPDX-FileCopyrightText: 2019-2022 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once -#include "ui_postprocessingsettingswidget.h" +#include "ui_postprocessingchainconfigwidget.h" #include "util/postprocessing.h" +#include #include class SettingsWindow; class PostProcessingShaderConfigWidget; -class PostProcessingSettingsWidget : public QWidget +class PostProcessingSettingsWidget : public QTabWidget +{ + Q_OBJECT + +public: + PostProcessingSettingsWidget(SettingsWindow* dialog, QWidget* parent); + ~PostProcessingSettingsWidget(); +}; + +class PostProcessingChainConfigWidget : public QWidget { Q_OBJECT friend PostProcessingShaderConfigWidget; public: - PostProcessingSettingsWidget(SettingsWindow* dialog, QWidget* parent); - ~PostProcessingSettingsWidget(); + PostProcessingChainConfigWidget(SettingsWindow* dialog, QWidget* parent, const char* section); + ~PostProcessingChainConfigWidget(); private Q_SLOTS: void onAddButtonClicked(); @@ -44,7 +54,9 @@ private: SettingsWindow* m_dialog; - Ui::PostProcessingSettingsWidget m_ui; + Ui::PostProcessingChainConfigWidget m_ui; + + const char* m_section; PostProcessingShaderConfigWidget* m_shader_config = nullptr; }; @@ -54,8 +66,8 @@ class PostProcessingShaderConfigWidget : public QWidget Q_OBJECT public: - PostProcessingShaderConfigWidget(QWidget* parent, PostProcessingSettingsWidget* widget, u32 stage_index, - std::vector options); + PostProcessingShaderConfigWidget(QWidget* parent, PostProcessingChainConfigWidget* widget, const char* section, + u32 stage_index, std::vector options); ~PostProcessingShaderConfigWidget(); private Q_SLOTS: @@ -67,9 +79,10 @@ protected: QGridLayout* m_layout; - PostProcessingSettingsWidget* m_widget; + PostProcessingChainConfigWidget* m_widget; std::vector m_widgets; + const char* m_section; u32 m_stage_index; std::vector m_options; }; diff --git a/src/util/postprocessing.cpp b/src/util/postprocessing.cpp index 398a79621..2437a28d7 100644 --- a/src/util/postprocessing.cpp +++ b/src/util/postprocessing.cpp @@ -37,27 +37,24 @@ static u32 ParseVector(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 TinyString GetStageConfigSection(const char* section, u32 index); +static void CopyStageConfig(SettingsInterface& si, const char* section, u32 old_index, u32 new_index); +static void SwapStageConfig(SettingsInterface& si, const char* section, 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 SettingsInterface& GetLoadSettingsInterface(const char* section); -static std::vector> s_stages; -static bool s_enabled = false; +template +ALWAYS_INLINE void ForAllChains(const T& F) +{ + F(DisplayChain); + F(InternalChain); +} + +Chain DisplayChain(Config::DISPLAY_CHAIN_SECTION); +Chain InternalChain(Config::INTERNAL_CHAIN_SECTION); -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_output_texture; - static std::unordered_map> s_samplers; static std::unique_ptr s_dummy_texture; } // namespace PostProcessing @@ -213,15 +210,15 @@ std::vector> PostProcessing::GetAvailableSha return names; } -TinyString PostProcessing::GetStageConfigSection(u32 index) +TinyString PostProcessing::GetStageConfigSection(const char* section, u32 index) { - return TinyString::from_format("PostProcessing/Stage{}", index + 1); + return TinyString::from_format("{}/Stage{}", section, index + 1); } -void PostProcessing::CopyStageConfig(SettingsInterface& si, u32 old_index, u32 new_index) +void PostProcessing::CopyStageConfig(SettingsInterface& si, const char* section, u32 old_index, u32 new_index) { - const TinyString old_section = GetStageConfigSection(old_index); - const TinyString new_section = GetStageConfigSection(new_index); + const TinyString old_section = GetStageConfigSection(section, old_index); + const TinyString new_section = GetStageConfigSection(section, new_index); si.ClearSection(new_section); @@ -229,10 +226,10 @@ void PostProcessing::CopyStageConfig(SettingsInterface& si, u32 old_index, u32 n si.SetStringValue(new_section, key.c_str(), value.c_str()); } -void PostProcessing::SwapStageConfig(SettingsInterface& si, u32 lhs_index, u32 rhs_index) +void PostProcessing::SwapStageConfig(SettingsInterface& si, const char* section, u32 lhs_index, u32 rhs_index) { - const TinyString lhs_section = GetStageConfigSection(lhs_index); - const TinyString rhs_section = GetStageConfigSection(rhs_index); + const TinyString lhs_section = GetStageConfigSection(section, lhs_index); + const TinyString rhs_section = GetStageConfigSection(section, rhs_index); const std::vector> lhs_kvs = si.GetKeyValueList(lhs_section); si.ClearSection(lhs_section); @@ -247,23 +244,23 @@ void PostProcessing::SwapStageConfig(SettingsInterface& si, u32 lhs_index, u32 r si.SetStringValue(rhs_section, key.c_str(), value.c_str()); } -u32 PostProcessing::Config::GetStageCount(const SettingsInterface& si) +u32 PostProcessing::Config::GetStageCount(const SettingsInterface& si, const char* section) { - return si.GetUIntValue("PostProcessing", "StageCount", 0u); + return si.GetUIntValue(section, "StageCount", 0u); } -std::string PostProcessing::Config::GetStageShaderName(const SettingsInterface& si, u32 index) +std::string PostProcessing::Config::GetStageShaderName(const SettingsInterface& si, const char* section, u32 index) { - return si.GetStringValue(GetStageConfigSection(index), "ShaderName"); + return si.GetStringValue(GetStageConfigSection(section, index), "ShaderName"); } std::vector PostProcessing::Config::GetStageOptions(const SettingsInterface& si, - u32 index) + const char* section, u32 index) { std::vector ret; - const TinyString section = GetStageConfigSection(index); - const std::string shader_name = si.GetStringValue(section, "ShaderName"); + const TinyString stage_section = GetStageConfigSection(section, index); + const std::string shader_name = si.GetStringValue(stage_section, "ShaderName"); if (shader_name.empty()) return ret; @@ -271,7 +268,7 @@ std::vector PostProcessing::Config::GetStageOption if (!shader) return ret; - shader->LoadOptions(si, section); + shader->LoadOptions(si, stage_section); ret = shader->TakeOptions(); return ret; } @@ -288,17 +285,18 @@ std::vector PostProcessing::Config::GetShaderOptio return ret; } -bool PostProcessing::Config::AddStage(SettingsInterface& si, const std::string& shader_name, Error* error) +bool PostProcessing::Config::AddStage(SettingsInterface& si, const char* section, 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 u32 index = GetStageCount(si, section); + si.SetUIntValue(section, "StageCount", index + 1); - const TinyString section = GetStageConfigSection(index); - si.SetStringValue(section, "ShaderName", shader->GetName().c_str()); + const TinyString stage_section = GetStageConfigSection(section, index); + si.SetStringValue(stage_section, "ShaderName", shader->GetName().c_str()); #if 0 // Leave options unset for now. @@ -312,70 +310,367 @@ bool PostProcessing::Config::AddStage(SettingsInterface& si, const std::string& return true; } -void PostProcessing::Config::RemoveStage(SettingsInterface& si, u32 index) +void PostProcessing::Config::RemoveStage(SettingsInterface& si, const char* section, u32 index) { - const u32 stage_count = GetStageCount(si); + const u32 stage_count = GetStageCount(si, section); if (index >= stage_count) return; for (u32 i = index; i < (stage_count - 1); i++) - CopyStageConfig(si, i + 1, i); + CopyStageConfig(si, section, i + 1, i); - si.ClearSection(GetStageConfigSection(stage_count - 1)); - si.SetUIntValue("PostProcessing", "StageCount", stage_count - 1); + si.ClearSection(GetStageConfigSection(section, stage_count - 1)); + si.SetUIntValue(section, "StageCount", stage_count - 1); } -void PostProcessing::Config::MoveStageUp(SettingsInterface& si, u32 index) +void PostProcessing::Config::MoveStageUp(SettingsInterface& si, const char* section, u32 index) { - const u32 stage_count = GetStageCount(si); + const u32 stage_count = GetStageCount(si, section); if (index == 0 || index >= stage_count) return; - SwapStageConfig(si, index, index - 1); + SwapStageConfig(si, section, index, index - 1); } -void PostProcessing::Config::MoveStageDown(SettingsInterface& si, u32 index) +void PostProcessing::Config::MoveStageDown(SettingsInterface& si, const char* section, u32 index) { - const u32 stage_count = GetStageCount(si); + const u32 stage_count = GetStageCount(si, section); if ((index + 1) >= stage_count) return; - SwapStageConfig(si, index, index + 1); + SwapStageConfig(si, section, index, index + 1); } -void PostProcessing::Config::SetStageOption(SettingsInterface& si, u32 index, const ShaderOption& option) +void PostProcessing::Config::SetStageOption(SettingsInterface& si, const char* section, 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)); + const TinyString stage_section = GetStageConfigSection(section, index); + si.SetStringValue(stage_section, option.name.c_str(), ValueToString(option.type, option.vector_size, option.value)); } -void PostProcessing::Config::UnsetStageOption(SettingsInterface& si, u32 index, const ShaderOption& option) +void PostProcessing::Config::UnsetStageOption(SettingsInterface& si, const char* section, u32 index, + const ShaderOption& option) { - const TinyString section = GetStageConfigSection(index); - si.DeleteValue(section, option.name.c_str()); + const TinyString stage_section = GetStageConfigSection(section, index); + si.DeleteValue(stage_section, option.name.c_str()); } -void PostProcessing::Config::ClearStages(SettingsInterface& si) +void PostProcessing::Config::ClearStages(SettingsInterface& si, const char* section) { - const u32 count = GetStageCount(si); + const u32 count = GetStageCount(si, section); for (s32 i = static_cast(count - 1); i >= 0; i--) - si.ClearSection(GetStageConfigSection(static_cast(i))); - si.SetUIntValue("PostProcessing", "StageCount", 0); + si.ClearSection(GetStageConfigSection(section, static_cast(i))); + si.SetUIntValue(section, "StageCount", 0); } -bool PostProcessing::IsActive() +PostProcessing::Chain::Chain(const char* section) : m_section(section) { - return s_enabled && !s_stages.empty(); } -bool PostProcessing::IsEnabled() +PostProcessing::Chain::~Chain() = default; + +bool PostProcessing::Chain::IsActive() const { - return s_enabled; + return m_enabled && !m_stages.empty(); } -void PostProcessing::SetEnabled(bool enabled) +bool PostProcessing::Chain::IsInternalChain() const { - s_enabled = enabled; + return (this == &InternalChain); +} + +void PostProcessing::Chain::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); + m_stages.clear(); +} + +void PostProcessing::Chain::LoadStages() +{ + auto lock = Host::GetSettingsLock(); + SettingsInterface& si = GetLoadSettingsInterface(m_section); + + m_enabled = si.GetBoolValue(m_section, "Enabled", false); + + const u32 stage_count = Config::GetStageCount(si, m_section); + if (stage_count == 0) + return; + + Error error; + HostInterfaceProgressCallback progress; + progress.SetProgressRange(stage_count); + + for (u32 i = 0; i < stage_count; i++) + { + std::string stage_name = Config::GetStageShaderName(si, m_section, i); + if (stage_name.empty()) + { + error.SetString(fmt::format("No stage name in stage {}.", i + 1)); + ClearStagesWithError(error); + return; + } + + lock.unlock(); + progress.SetFormattedStatusText("Loading shader %s...", stage_name.c_str()); + + std::unique_ptr shader = TryLoadingShader(stage_name, false, &error); + if (!shader) + { + ClearStagesWithError(error); + return; + } + + lock.lock(); + shader->LoadOptions(si, GetStageConfigSection(m_section, i)); + m_stages.push_back(std::move(shader)); + + progress.IncrementProgressValue(); + } + + if (stage_count > 0) + DEV_LOG("Loaded {} post-processing stages.", stage_count); + + // precompile shaders + if (!IsInternalChain() && g_gpu_device && g_gpu_device->GetWindowFormat() != GPUTexture::Format::Unknown) + { + CheckTargets(g_gpu_device->GetWindowFormat(), g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), + &progress); + } +} + +void PostProcessing::Chain::ClearStages() +{ + decltype(m_stages)().swap(m_stages); +} + +void PostProcessing::Chain::UpdateSettings(std::unique_lock& settings_lock) +{ + SettingsInterface& si = GetLoadSettingsInterface(m_section); + + m_enabled = si.GetBoolValue(m_section, "Enabled", false); + + const u32 stage_count = Config::GetStageCount(si, m_section); + if (stage_count == 0) + { + m_stages.clear(); + return; + } + + Error error; + + m_stages.resize(stage_count); + + HostInterfaceProgressCallback progress; + progress.SetProgressRange(stage_count); + + const GPUTexture::Format prev_format = m_target_format; + + for (u32 i = 0; i < stage_count; i++) + { + std::string stage_name = Config::GetStageShaderName(si, m_section, i); + if (stage_name.empty()) + { + error.SetString(fmt::format("No stage name in stage {}.", i + 1)); + ClearStagesWithError(error); + return; + } + + if (!m_stages[i] || stage_name != m_stages[i]->GetName()) + { + if (i < m_stages.size()) + m_stages[i].reset(); + + // Force recompile. + m_target_format = GPUTexture::Format::Unknown; + + settings_lock.unlock(); + + std::unique_ptr shader = TryLoadingShader(stage_name, false, &error); + if (!shader) + { + ClearStagesWithError(error); + return; + } + + if (i < m_stages.size()) + m_stages[i] = std::move(shader); + else + m_stages.push_back(std::move(shader)); + + settings_lock.lock(); + } + + m_stages[i]->LoadOptions(si, GetStageConfigSection(m_section, i)); + } + + if (prev_format != GPUTexture::Format::Unknown) + CheckTargets(prev_format, m_target_width, m_target_height, &progress); + + if (stage_count > 0) + { + s_timer.Reset(); + DEV_LOG("Loaded {} post-processing stages.", stage_count); + } +} + +void PostProcessing::Chain::Toggle() +{ + if (m_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 = !m_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); + m_enabled = new_enabled; + if (m_enabled) + s_timer.Reset(); +} + +bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height, + ProgressCallback* progress) +{ + if (m_target_format == target_format && m_target_width == target_width && m_target_height == target_height) + return true; + + // In case any allocs fail. + DestroyTextures(); + + if (!(m_input_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, + GPUTexture::Type::RenderTarget, target_format)) || + !(m_output_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, + GPUTexture::Type::RenderTarget, target_format))) + { + DestroyTextures(); + return false; + } + + if (!progress) + progress = ProgressCallback::NullProgressCallback; + + progress->SetProgressRange(static_cast(m_stages.size())); + progress->SetProgressValue(0); + + for (size_t i = 0; i < m_stages.size(); i++) + { + Shader* const shader = m_stages[i].get(); + + progress->SetFormattedStatusText("Compiling %s...", shader->GetName().c_str()); + + if (!shader->CompilePipeline(target_format, target_width, target_height, progress) || + !shader->ResizeOutput(target_format, target_width, target_height)) + { + ERROR_LOG("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())); + m_enabled = false; + return false; + } + + progress->SetProgressValue(static_cast(i + 1)); + } + + m_target_format = target_format; + m_target_width = target_width; + m_target_height = target_height; + return true; +} + +void PostProcessing::Chain::DestroyTextures() +{ + m_target_format = GPUTexture::Format::Unknown; + m_target_width = 0; + m_target_height = 0; + + g_gpu_device->RecycleTexture(std::move(m_output_texture)); + g_gpu_device->RecycleTexture(std::move(m_input_texture)); +} + +bool PostProcessing::Chain::Apply(GPUTexture* input_color, GPUTexture* final_target, s32 final_left, s32 final_top, + s32 final_width, s32 final_height, s32 orig_width, s32 orig_height, s32 native_width, + s32 native_height) +{ + GL_SCOPE_FMT("{} Apply", m_section); + + GPUTexture* output = m_output_texture.get(); + input_color->MakeReadyForSampling(); + + for (const std::unique_ptr& stage : m_stages) + { + const bool is_final = (stage.get() == m_stages.back().get()); + + if (!stage->Apply(input_color, is_final ? final_target : output, final_left, final_top, final_width, final_height, + orig_width, orig_height, native_width, native_height, m_target_width, m_target_height)) + { + return false; + } + + if (!is_final) + { + output->MakeReadyForSampling(); + input_color = output; + output = (output == m_output_texture.get()) ? m_input_texture.get() : m_output_texture.get(); + } + } + + return true; +} + +void PostProcessing::Initialize() +{ + DisplayChain.LoadStages(); + InternalChain.LoadStages(); + s_timer.Reset(); +} + +void PostProcessing::UpdateSettings() +{ + auto lock = Host::GetSettingsLock(); + ForAllChains([&lock](Chain& chain) { chain.UpdateSettings(lock); }); +} + +void PostProcessing::Shutdown() +{ + g_gpu_device->RecycleTexture(std::move(s_dummy_texture)); + s_samplers.clear(); + ForAllChains([](Chain& chain) { + chain.ClearStages(); + chain.DestroyTextures(); + }); +} + +bool PostProcessing::ReloadShaders() +{ + if (!DisplayChain.HasStages() && !InternalChain.HasStages()) + { + Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER, + TRANSLATE_STR("OSDMessage", "No post-processing shaders are selected."), + Host::OSD_QUICK_DURATION); + return false; + } + + ForAllChains([](Chain& chain) { + chain.ClearStages(); + chain.DestroyTextures(); + chain.LoadStages(); + }); + s_timer.Reset(); + + Host::AddIconOSDMessage("PostProcessing", ICON_FA_PAINT_ROLLER, + TRANSLATE_STR("OSDMessage", "Post-processing shaders reloaded."), Host::OSD_QUICK_DURATION); + return true; } std::unique_ptr PostProcessing::TryLoadingShader(const std::string& shader_name, @@ -429,214 +724,18 @@ std::unique_ptr PostProcessing::TryLoadingShader(const s 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() +SettingsInterface& PostProcessing::GetLoadSettingsInterface(const char* section) { // 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")) + if (game_si && game_si->ContainsValue(section, "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; - HostInterfaceProgressCallback progress; - progress.SetProgressRange(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; - } - - lock.unlock(); - progress.SetFormattedStatusText("Loading shader %s...", stage_name.c_str()); - - 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)); - - progress.IncrementProgressValue(); - } - - if (stage_count > 0) - { - s_timer.Reset(); - DEV_LOG("Loaded {} post-processing stages.", stage_count); - } - - // precompile shaders - if (g_gpu_device && g_gpu_device->GetWindowFormat() != GPUTexture::Format::Unknown) - { - CheckTargets(g_gpu_device->GetWindowFormat(), g_gpu_device->GetWindowWidth(), g_gpu_device->GetWindowHeight(), - &progress); - } -} - -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); - - HostInterfaceProgressCallback progress; - progress.SetProgressRange(stage_count); - - const GPUTexture::Format prev_format = s_target_format; - - 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 (prev_format != GPUTexture::Format::Unknown) - CheckTargets(prev_format, s_target_width, s_target_height, &progress); - - if (stage_count > 0) - { - s_timer.Reset(); - DEV_LOG("Loaded {} 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() -{ - g_gpu_device->RecycleTexture(std::move(s_dummy_texture)); - s_samplers.clear(); - s_enabled = false; - decltype(s_stages)().swap(s_stages); - DestroyTextures(); -} - -GPUTexture* PostProcessing::GetInputTexture() -{ - return s_input_texture.get(); -} - const Common::Timer& PostProcessing::GetTimer() { return s_timer; @@ -669,92 +768,3 @@ GPUTexture* PostProcessing::GetDummyTexture() return s_dummy_texture.get(); } - -bool PostProcessing::CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height, - ProgressCallback* progress) -{ - 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->FetchTexture(target_width, target_height, 1, 1, 1, - GPUTexture::Type::RenderTarget, target_format)) || - !(s_output_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, - GPUTexture::Type::RenderTarget, target_format))) - { - DestroyTextures(); - return false; - } - - if (!progress) - progress = ProgressCallback::NullProgressCallback; - - progress->SetProgressRange(static_cast(s_stages.size())); - progress->SetProgressValue(0); - - for (size_t i = 0; i < s_stages.size(); i++) - { - Shader* const shader = s_stages[i].get(); - - progress->SetFormattedStatusText("Compiling %s...", shader->GetName().c_str()); - - if (!shader->CompilePipeline(target_format, target_width, target_height, progress) || - !shader->ResizeOutput(target_format, target_width, target_height)) - { - ERROR_LOG("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; - } - - progress->SetProgressValue(static_cast(i + 1)); - } - - 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; - - g_gpu_device->RecycleTexture(std::move(s_output_texture)); - g_gpu_device->RecycleTexture(std::move(s_input_texture)); -} - -bool PostProcessing::Apply(GPUTexture* final_target, s32 final_left, s32 final_top, s32 final_width, s32 final_height, - s32 orig_width, s32 orig_height, s32 native_width, s32 native_height) -{ - GL_SCOPE("PostProcessing Apply"); - - GPUTexture* input = s_input_texture.get(); - GPUTexture* output = s_output_texture.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, final_left, final_top, final_width, final_height, - orig_width, orig_height, native_width, native_height, s_target_width, s_target_height)) - { - return false; - } - - if (!is_final) - { - output->MakeReadyForSampling(); - std::swap(input, output); - } - } - - return true; -} diff --git a/src/util/postprocessing.h b/src/util/postprocessing.h index 20acecbe0..335f4c012 100644 --- a/src/util/postprocessing.h +++ b/src/util/postprocessing.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: 2019-2023 Connor McLaughlin +// SPDX-FileCopyrightText: 2019-2024 Connor McLaughlin // SPDX-License-Identifier: (GPL-3.0 OR CC-BY-NC-ND-4.0) #pragma once @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -22,6 +23,8 @@ class SettingsInterface; class ProgressCallback; namespace PostProcessing { +class Shader; + struct ShaderOption { enum : u32 @@ -87,51 +90,86 @@ struct ShaderOption } }; -// [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); +static constexpr const char* DISPLAY_CHAIN_SECTION = "PostProcessing"; +static constexpr const char* INTERNAL_CHAIN_SECTION = "InternalPostProcessing"; + +u32 GetStageCount(const SettingsInterface& si, const char* section); +std::string GetStageShaderName(const SettingsInterface& si, const char* section, u32 index); +std::vector GetStageOptions(const SettingsInterface& si, const char* section, u32 index); std::vector GetShaderOptions(const std::string& shader_name, Error* error); -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); +bool AddStage(SettingsInterface& si, const char* section, const std::string& shader_name, Error* error); +void RemoveStage(SettingsInterface& si, const char* section, u32 index); +void MoveStageUp(SettingsInterface& si, const char* section, u32 index); +void MoveStageDown(SettingsInterface& si, const char* section, u32 index); +void SetStageOption(SettingsInterface& si, const char* section, u32 index, const ShaderOption& option); +void UnsetStageOption(SettingsInterface& si, const char* section, u32 index, const ShaderOption& option); +void ClearStages(SettingsInterface& si, const char* section); } // namespace Config -bool IsActive(); -bool IsEnabled(); -void SetEnabled(bool enabled); +class Chain +{ +public: + Chain(const char* section); + ~Chain(); + + ALWAYS_INLINE bool HasStages() const { return m_stages.empty(); } + ALWAYS_INLINE GPUTexture* GetInputTexture() const { return m_input_texture.get(); } + ALWAYS_INLINE GPUTexture* GetOutputTexture() const { return m_output_texture.get(); } + + bool IsActive() const; + bool IsInternalChain() const; + + void UpdateSettings(std::unique_lock& settings_lock); + + void LoadStages(); + void ClearStages(); + void DestroyTextures(); + + /// Temporarily toggles post-processing on/off. + void Toggle(); + + bool CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height, + ProgressCallback* progress = nullptr); + + bool Apply(GPUTexture* input_color, GPUTexture* final_target, s32 final_left, s32 final_top, s32 final_width, + s32 final_height, s32 orig_width, s32 orig_height, s32 native_width, s32 native_height); + +private: + void ClearStagesWithError(const Error& error); + + const char* m_section; + + GPUTexture::Format m_target_format = GPUTexture::Format::Unknown; + u32 m_target_width = 0; + u32 m_target_height = 0; + bool m_enabled = false; + + std::vector> m_stages; + std::unique_ptr m_input_texture; + std::unique_ptr m_output_texture; +}; + +// [display_name, filename] +std::vector> GetAvailableShaderNames(); 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(); -const Common::Timer& GetTimer(); - -bool CheckTargets(GPUTexture::Format target_format, u32 target_width, u32 target_height, - ProgressCallback* progress = nullptr); - -bool Apply(GPUTexture* final_target, s32 final_left, s32 final_top, s32 final_width, s32 final_height, s32 orig_width, - s32 orig_height, s32 native_width, s32 native_height); - GPUSampler* GetSampler(const GPUSampler::Config& config); GPUTexture* GetDummyTexture(); +const Common::Timer& GetTimer(); + +extern Chain DisplayChain; +extern Chain InternalChain; + }; // namespace PostProcessing