From 16260040e0d585f529abcde3820e2a203d1710eb Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 27 Oct 2025 18:18:38 -0500 Subject: CoreTiming: Add "Rush Frame Presentation" setting to throttle only once after each presentation for lower input latency. --- Source/Core/VideoCommon/Present.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) (limited to 'Source/Core/VideoCommon/Present.cpp') diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index d2ba5f2211..ce4b62a85b 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -5,6 +5,7 @@ #include "Common/ChunkFile.h" #include "Core/Config/GraphicsSettings.h" +#include "Core/Config/MainSettings.h" #include "Core/CoreTiming.h" #include "Core/HW/VideoInterface.h" #include "Core/Host.h" @@ -201,7 +202,13 @@ void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs) { - Present(presentation_time); + // If RushFramePresentation is enabled, ignore the proper time to present as soon as possible. + // The goal is to achieve the lowest possible input latency. + if (Config::Get(Config::MAIN_RUSH_FRAME_PRESENTATION)) + Present(); + else + Present(presentation_time); + ProcessFrameDumping(ticks); video_events.after_present_event.Trigger(present_info); -- cgit v1.2.3 From cc331feb020d71f109d41b7f841bd69caa96bbb3 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 27 Oct 2025 18:14:57 -0500 Subject: VideoCommon: Make Presenter aware of the next swap time to eliminate unsafe usage of GetTicks() with ImmediateXFB + DualCore. --- Source/Core/VideoCommon/Present.cpp | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) (limited to 'Source/Core/VideoCommon/Present.cpp') diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index ce4b62a85b..bedc1c128f 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -215,12 +215,14 @@ 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, u64 ticks) +void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height) { + const u64 ticks = m_next_swap_estimated_ticks; + FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); PresentInfo present_info; - present_info.emulated_timestamp = ticks; // TODO: This should be the time of the next VI field + present_info.emulated_timestamp = ticks; present_info.frame_count = m_frame_count++; present_info.reason = PresentInfo::PresentReason::Immediate; present_info.present_count = m_present_count++; @@ -235,6 +237,12 @@ void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_ video_events.after_present_event.Trigger(present_info); } +void Presenter::SetNextSwapEstimatedTime(u64 ticks, TimePoint host_time) +{ + m_next_swap_estimated_ticks = ticks; + m_next_swap_estimated_time = host_time; +} + void Presenter::ProcessFrameDumping(u64 ticks) const { if (g_frame_dumper->IsFrameDumping() && m_xfb_entry) @@ -938,8 +946,10 @@ void Presenter::DoState(PointerWrap& p) // This technically counts as the end of the frame GetVideoEvents().after_frame_event.Trigger(Core::System::GetInstance()); - ImmediateSwap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height, - m_last_xfb_ticks); + m_next_swap_estimated_ticks = m_last_xfb_ticks; + m_next_swap_estimated_time = Clock::now(); + + ImmediateSwap(m_last_xfb_addr, m_last_xfb_width, m_last_xfb_stride, m_last_xfb_height); } } -- cgit v1.2.3 From c2a1dce246e27f1ac9dc421387e4d70402bb96f4 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Mon, 27 Oct 2025 18:57:07 -0500 Subject: VideoCommon: Add "Smooth Early Presentation" setting to improve frame pacing with ImmediateXFB and/or RushFramePresentation. --- Source/Core/VideoCommon/Present.cpp | 73 +++++++++++++++++++++++++++---------- 1 file changed, 54 insertions(+), 19 deletions(-) (limited to 'Source/Core/VideoCommon/Present.cpp') diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index bedc1c128f..ea7cb65f95 100644 --- a/Source/Core/VideoCommon/Present.cpp +++ b/Source/Core/VideoCommon/Present.cpp @@ -163,9 +163,12 @@ void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, { bool is_duplicate = FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); - PresentInfo present_info; - present_info.emulated_timestamp = ticks; - present_info.present_count = m_present_count++; + PresentInfo present_info{ + .present_count = m_present_count++, + .emulated_timestamp = ticks, + .intended_present_time = presentation_time, + }; + if (is_duplicate) { present_info.frame_count = m_frame_count - 1; // Previous frame @@ -202,13 +205,7 @@ void Presenter::ViSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_height, if (!is_duplicate || !g_ActiveConfig.bSkipPresentingDuplicateXFBs) { - // If RushFramePresentation is enabled, ignore the proper time to present as soon as possible. - // The goal is to achieve the lowest possible input latency. - if (Config::Get(Config::MAIN_RUSH_FRAME_PRESENTATION)) - Present(); - else - Present(presentation_time); - + Present(&present_info); ProcessFrameDumping(ticks); video_events.after_present_event.Trigger(present_info); @@ -221,17 +218,19 @@ void Presenter::ImmediateSwap(u32 xfb_addr, u32 fb_width, u32 fb_stride, u32 fb_ FetchXFB(xfb_addr, fb_width, fb_stride, fb_height, ticks); - PresentInfo present_info; - present_info.emulated_timestamp = ticks; - present_info.frame_count = m_frame_count++; - present_info.reason = PresentInfo::PresentReason::Immediate; - present_info.present_count = m_present_count++; + PresentInfo present_info{ + .frame_count = m_frame_count++, + .present_count = m_present_count++, + .reason = PresentInfo::PresentReason::Immediate, + .emulated_timestamp = ticks, + .intended_present_time = m_next_swap_estimated_time, + }; auto& video_events = GetVideoEvents(); video_events.before_present_event.Trigger(present_info); - Present(); + Present(&present_info); ProcessFrameDumping(ticks); video_events.after_present_event.Trigger(present_info); @@ -834,7 +833,7 @@ void Presenter::RenderXFBToScreen(const MathUtil::Rectangle& target_rc, } } -void Presenter::Present(std::optional presentation_time) +void Presenter::Present(PresentInfo* present_info) { m_present_count++; @@ -888,8 +887,16 @@ void Presenter::Present(std::optional presentation_time) { std::lock_guard guard(m_swap_mutex); - if (presentation_time.has_value()) - Core::System::GetInstance().GetCoreTiming().SleepUntil(*presentation_time); + if (present_info != nullptr) + { + const auto present_time = GetUpdatedPresentationTime(present_info->intended_present_time); + + Core::System::GetInstance().GetCoreTiming().SleepUntil(present_time); + + // Perhaps in the future a more accurate time can be acquired from the various backends. + present_info->actual_present_time = Clock::now(); + present_info->present_time_accuracy = PresentInfo::PresentTimeAccuracy::PresentInProgress; + } g_gfx->PresentBackbuffer(); } @@ -907,6 +914,34 @@ void Presenter::Present(std::optional presentation_time) g_gfx->EndUtilityDrawing(); } +TimePoint Presenter::GetUpdatedPresentationTime(TimePoint intended_presentation_time) +{ + const auto now = Clock::now(); + const auto arrival_offset = std::min(now - intended_presentation_time, DT{}); + + if (!Config::Get(Config::MAIN_SMOOTH_EARLY_PRESENTATION)) + { + m_presentation_time_offset = arrival_offset; + + // When SmoothEarlyPresentation is off and ImmediateXFB or RushFramePresentation are on, + // present as soon as possible as the goal is to achieve low input latency. + if (g_ActiveConfig.bImmediateXFB || Config::Get(Config::MAIN_RUSH_FRAME_PRESENTATION)) + return now; + + return intended_presentation_time; + } + + // Adjust slowly backward in time but quickly forward in time. + // This keeps the pacing moderately smooth even if games produce regular sporadic bumps. + // This was tuned to handle the terrible pacing in Brawl with "Immediate XFB". + // Super Mario Galaxy 1 + 2 still perform poorly here in SingleCore mode. + const auto adjustment_divisor = (arrival_offset < m_presentation_time_offset) ? 100 : 2; + + m_presentation_time_offset += (arrival_offset - m_presentation_time_offset) / adjustment_divisor; + + return intended_presentation_time + m_presentation_time_offset; +} + void Presenter::SetKeyMap(const DolphinKeyMap& key_map) { if (m_onscreen_ui) -- cgit v1.2.3