From 44a766772f399c3134c50bbf4f8273f149f2e218 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 24 Nov 2025 15:31:50 -0600 Subject: [PATCH 1/3] VideoCommon: Add a hidden setting to cap immediate XFB swaps to one per VI. --- Source/Core/Core/Config/GraphicsSettings.cpp | 1 + Source/Core/Core/Config/GraphicsSettings.h | 1 + Source/Core/VideoCommon/Present.cpp | 17 ++++++++++++++++- Source/Core/VideoCommon/Present.h | 3 +++ 4 files changed, 21 insertions(+), 1 deletion(-) diff --git a/Source/Core/Core/Config/GraphicsSettings.cpp b/Source/Core/Core/Config/GraphicsSettings.cpp index 7062a724cb..53090bec8a 100644 --- a/Source/Core/Core/Config/GraphicsSettings.cpp +++ b/Source/Core/Core/Config/GraphicsSettings.cpp @@ -194,6 +194,7 @@ const Info GFX_HACK_SKIP_XFB_COPY_TO_RAM{{System::GFX, "Hacks", "XFBToText const Info GFX_HACK_DISABLE_COPY_TO_VRAM{{System::GFX, "Hacks", "DisableCopyToVRAM"}, false}; const Info GFX_HACK_DEFER_EFB_COPIES{{System::GFX, "Hacks", "DeferEFBCopies"}, true}; const Info GFX_HACK_IMMEDIATE_XFB{{System::GFX, "Hacks", "ImmediateXFBEnable"}, false}; +const Info GFX_HACK_CAP_IMMEDIATE_XFB{{System::GFX, "Hacks", "CapImmediateXFB"}, false}; const Info GFX_HACK_SKIP_DUPLICATE_XFBS{{System::GFX, "Hacks", "SkipDuplicateXFBs"}, true}; const Info GFX_HACK_EARLY_XFB_OUTPUT{{System::GFX, "Hacks", "EarlyXFBOutput"}, true}; const Info GFX_HACK_COPY_EFB_SCALED{{System::GFX, "Hacks", "EFBScaledCopy"}, true}; diff --git a/Source/Core/Core/Config/GraphicsSettings.h b/Source/Core/Core/Config/GraphicsSettings.h index 99dbfb5e64..5c22f588ea 100644 --- a/Source/Core/Core/Config/GraphicsSettings.h +++ b/Source/Core/Core/Config/GraphicsSettings.h @@ -168,6 +168,7 @@ extern const Info GFX_HACK_SKIP_XFB_COPY_TO_RAM; extern const Info GFX_HACK_DISABLE_COPY_TO_VRAM; extern const Info GFX_HACK_DEFER_EFB_COPIES; extern const Info GFX_HACK_IMMEDIATE_XFB; +extern const Info GFX_HACK_CAP_IMMEDIATE_XFB; extern const Info GFX_HACK_SKIP_DUPLICATE_XFBS; extern const Info GFX_HACK_EARLY_XFB_OUTPUT; extern const Info GFX_HACK_COPY_EFB_SCALED; diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index ea7cb65f95..ea7f2d6d74 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -95,8 +95,13 @@ static void TryToSnapToXFBSize(int& width, int& height, int xfb_width, int xfb_h Presenter::Presenter() { + auto& video_events = GetVideoEvents(); + m_config_changed = - GetVideoEvents().config_changed_event.Register([this](u32 bits) { ConfigChanged(bits); }); + video_events.config_changed_event.Register([this](u32 bits) { ConfigChanged(bits); }); + + m_end_field_hook = video_events.vi_end_field_event.Register( + [this] { m_immediate_swap_happened_this_field.store(false, std::memory_order_relaxed); }); } Presenter::~Presenter() @@ -109,6 +114,8 @@ bool Presenter::Initialize() { UpdateDrawRectangle(); + m_immediate_swap_happened_this_field.store(false, std::memory_order_relaxed); + if (!g_gfx->IsHeadless()) { SetBackbuffer(g_gfx->GetSurfaceInfo()); @@ -214,6 +221,12 @@ void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height) { + if (m_immediate_swap_happened_this_field.exchange(true, std::memory_order_relaxed) && + Config::Get(Config::GFX_HACK_CAP_IMMEDIATE_XFB)) + { + return; + } + const u64 ticks = m_next_swap_estimated_ticks; FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); @@ -984,6 +997,8 @@ void Presenter::DoState(PointerWrap& p) m_next_swap_estimated_ticks = m_last_xfb_ticks; m_next_swap_estimated_time = Clock::now(); + m_immediate_swap_happened_this_field.store(false, std::memory_order_relaxed); + ImmediateSwap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height); } } diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index e9ad21dbd9..7e5ce651e3 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -169,6 +169,7 @@ private: u32 m_last_xfb_height = MAX_XFB_HEIGHT; Common::EventHook m_config_changed; + Common::EventHook m_end_field_hook; // Updates state for the SmoothEarlyPresentation setting if enabled. // Returns the desired presentation time regardless. @@ -181,6 +182,8 @@ private: // Can be used for presentation of ImmediateXFB swaps which don't have timing information. u64 m_next_swap_estimated_ticks = 0; TimePoint m_next_swap_estimated_time{Clock::now()}; + + std::atomic_bool m_immediate_swap_happened_this_field{}; }; } // namespace VideoCommon From a358636234003df1a4d9d49eb30c4de51d83f1a2 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 24 Nov 2025 15:34:39 -0600 Subject: [PATCH 2/3] GameSettings: Enable CapImmediateXFB in Xenoblade instead of disabling ImmediateXFB to handle the uncapped "Reading Disc" screen. --- Data/Sys/GameSettings/SX4.ini | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Data/Sys/GameSettings/SX4.ini b/Data/Sys/GameSettings/SX4.ini index 1d937ab49c..24d31349e1 100644 --- a/Data/Sys/GameSettings/SX4.ini +++ b/Data/Sys/GameSettings/SX4.ini @@ -10,4 +10,5 @@ # Add action replay cheats here. [Video_Hacks] -ImmediateXFBEnable = False +# Makes the uncapped "Reading Disc" screen not unbearably slow. +CapImmediateXFB = True From 43104036749251ca75454b266e2f49910080c130 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 24 Nov 2025 22:54:10 -0600 Subject: [PATCH 3/3] GameSettings: Enable CapImmediateXFB in Lost Kingdoms II to allow ImmediateXFB without consistent extraneous swaps that cause terrible pacing. --- Data/Sys/GameSettings/GR2.ini | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 Data/Sys/GameSettings/GR2.ini diff --git a/Data/Sys/GameSettings/GR2.ini b/Data/Sys/GameSettings/GR2.ini new file mode 100644 index 0000000000..a44ecb6d98 --- /dev/null +++ b/Data/Sys/GameSettings/GR2.ini @@ -0,0 +1,5 @@ +# GR2P52, GR2JCQ, GR2E52 - Lost Kingdoms II + +[Video_Hacks] +# Avoids consistent extraneous swaps that cause terrible pacing. +CapImmediateXFB = True