diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 70838b0fb69..b06fb5b4963 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -49,6 +49,8 @@ const Info MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 4 const Info MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false}; const Info MAIN_RUSH_FRAME_PRESENTATION{{System::Main, "Core", "RushFramePresentation"}, false}; +const Info MAIN_SMOOTH_EARLY_PRESENTATION{{System::Main, "Core", "SmoothEarlyPresentation"}, + false}; #if defined(ANDROID) // Currently enabled by default on Android because the performance boost is really needed. constexpr bool DEFAULT_CPU_THREAD = true; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index bbe56556ac9..cc64c80d69c 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -66,6 +66,7 @@ extern const Info MAIN_MAX_FALLBACK; extern const Info MAIN_TIMING_VARIANCE; extern const Info MAIN_CORRECT_TIME_DRIFT; extern const Info MAIN_RUSH_FRAME_PRESENTATION; +extern const Info MAIN_SMOOTH_EARLY_PRESENTATION; extern const Info MAIN_CPU_THREAD; extern const Info MAIN_SYNC_ON_SKIP_IDLE; extern const Info MAIN_DEFAULT_ISO; diff --git a/Source/Core/DolphinQt/Settings/AdvancedPane.cpp b/Source/Core/DolphinQt/Settings/AdvancedPane.cpp index 347310c0c7f..b96d2b24eaa 100644 --- a/Source/Core/DolphinQt/Settings/AdvancedPane.cpp +++ b/Source/Core/DolphinQt/Settings/AdvancedPane.cpp @@ -127,6 +127,17 @@ void AdvancedPane::CreateLayout() "

If unsure, leave this unchecked.")); timing_group_layout->addWidget(rush_frame_presentation); + auto* const smooth_early_presentation = + // i18n: "Smooth" is a verb + new ConfigBool{tr("Smooth Early Presentation"), Config::MAIN_SMOOTH_EARLY_PRESENTATION}; + smooth_early_presentation->SetDescription( + tr("Adaptively adjusts the timing of early frame presentation." + "

This can improve frame pacing with Immediately Present XFB" + " and/or Rush Frame Presentation," + " while still maintaining most of the input latency benefits." + "

If unsure, leave this unchecked.")); + timing_group_layout->addWidget(smooth_early_presentation); + // Make all labels the same width, so that the sliders are aligned. const QFontMetrics font_metrics{font()}; const int label_width = font_metrics.boundingRect(QStringLiteral(" 500% (000.00 VPS)")).width(); diff --git a/Source/Core/VideoCommon/Present.cpp b/Source/Core/VideoCommon/Present.cpp index bedc1c128f6..ea7cb65f95d 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) diff --git a/Source/Core/VideoCommon/Present.h b/Source/Core/VideoCommon/Present.h index d6662f97f13..e9ad21dbd93 100644 --- a/Source/Core/VideoCommon/Present.h +++ b/Source/Core/VideoCommon/Present.h @@ -41,7 +41,7 @@ public: void SetNextSwapEstimatedTime(u64 ticks, TimePoint host_time); - void Present(std::optional presentation_time = std::nullopt); + void Present(PresentInfo* present_info = nullptr); void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits::max(); } bool Initialize(); @@ -170,6 +170,13 @@ private: Common::EventHook m_config_changed; + // Updates state for the SmoothEarlyPresentation setting if enabled. + // Returns the desired presentation time regardless. + TimePoint GetUpdatedPresentationTime(TimePoint intended_presentation_time); + + // Used by the SmoothEarlyPresentation setting. + DT m_presentation_time_offset{}; + // Calculated from the previous swap time and current refresh rate. // Can be used for presentation of ImmediateXFB swaps which don't have timing information. u64 m_next_swap_estimated_ticks = 0; diff --git a/Source/Core/VideoCommon/VideoEvents.h b/Source/Core/VideoCommon/VideoEvents.h index b7ecf0fca58..fe30aa251b8 100644 --- a/Source/Core/VideoCommon/VideoEvents.h +++ b/Source/Core/VideoCommon/VideoEvents.h @@ -36,11 +36,10 @@ struct PresentInfo // The exact emulated time of the when real hardware would have presented this frame u64 emulated_timestamp = 0; - // TODO: - // u64 intended_present_time = 0; + TimePoint intended_present_time{}; // AfterPresent only: The actual time the frame was presented - u64 actual_present_time = 0; + TimePoint actual_present_time{}; enum class PresentTimeAccuracy {