diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index 2a9bad56e03..70838b0fb69 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -47,6 +47,8 @@ const Info MAIN_DSP_HLE{{System::Main, "Core", "DSPHLE"}, true}; const Info MAIN_MAX_FALLBACK{{System::Main, "Core", "MaxFallback"}, 100}; const Info MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 40}; const Info MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false}; +const Info MAIN_RUSH_FRAME_PRESENTATION{{System::Main, "Core", "RushFramePresentation"}, + 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 2355508f9f4..bbe56556ac9 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -65,6 +65,7 @@ extern const Info MAIN_DSP_HLE; 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_CPU_THREAD; extern const Info MAIN_SYNC_ON_SKIP_IDLE; extern const Info MAIN_DEFAULT_ISO; diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index e16db6a7ee1..af5bc4731ce 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -29,6 +29,7 @@ #include "VideoCommon/PerformanceMetrics.h" #include "VideoCommon/VideoBackendBase.h" #include "VideoCommon/VideoConfig.h" +#include "VideoCommon/VideoEvents.h" namespace CoreTiming { @@ -113,6 +114,11 @@ void CoreTimingManager::Init() ResetThrottle(GetTicks()); } }); + + m_throttled_after_presentation = false; + m_frame_hook = m_system.GetVideoEvents().after_present_event.Register([this](const PresentInfo&) { + m_throttled_after_presentation.store(false, std::memory_order_relaxed); + }); } void CoreTimingManager::Shutdown() @@ -124,6 +130,7 @@ void CoreTimingManager::Shutdown() ClearPendingEvents(); UnregisterAllEvents(); CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id); + m_frame_hook.reset(); } void CoreTimingManager::RefreshConfig() @@ -134,6 +141,11 @@ void CoreTimingManager::RefreshConfig() 1.0f); m_config_oc_inv_factor = 1.0f / m_config_oc_factor; m_config_sync_on_skip_idle = Config::Get(Config::MAIN_SYNC_ON_SKIP_IDLE); + m_config_rush_frame_presentation = Config::Get(Config::MAIN_RUSH_FRAME_PRESENTATION); + + // We don't want to skip so much throttling that the audio buffer overfills. + m_max_throttle_skip_time = + std::chrono::milliseconds{Config::Get(Config::MAIN_AUDIO_BUFFER_SIZE)} / 2; // A maximum fallback is used to prevent the system from sleeping for // too long or going full speed in an attempt to catch up to timings. @@ -422,6 +434,21 @@ void CoreTimingManager::SleepUntil(TimePoint time_point) void CoreTimingManager::Throttle(const s64 target_cycle) { + const TimePoint time = Clock::now(); + + const bool already_throttled = + m_throttled_after_presentation.exchange(true, std::memory_order_relaxed); + + // If RushFramePresentation is enabled, try to Throttle just once after each presentation. + // This lowers input latency by speeding through to presentation after grabbing input. + // Make sure we don't get too far ahead of proper timing though, + // otherwise the emulator unreasonably speeds through loading screens that don't have XFB copies, + // making audio sound terrible. + const bool skip_throttle = already_throttled && m_config_rush_frame_presentation && + ((GetTargetHostTime(target_cycle) - time) < m_max_throttle_skip_time); + if (skip_throttle) + return; + if (IsSpeedUnlimited()) { ResetThrottle(target_cycle); @@ -441,8 +468,6 @@ void CoreTimingManager::Throttle(const s64 target_cycle) TimePoint target_time = CalculateTargetHostTimeInternal(target_cycle); - const TimePoint time = Clock::now(); - const TimePoint min_target = time - m_max_fallback; // "Correct Time Drift" setting prevents timing relaxing. diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index 3a522b8deaa..68d1fd4fd29 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -207,6 +207,7 @@ private: float m_config_oc_factor = 1.0f; float m_config_oc_inv_factor = 1.0f; bool m_config_sync_on_skip_idle = false; + bool m_config_rush_frame_presentation = false; s64 m_throttle_reference_cycle = 0; TimePoint m_throttle_reference_time = Clock::now(); @@ -232,6 +233,11 @@ private: Common::PrecisionTimer m_precision_gpu_timer; Common::EventHook m_core_state_changed_hook; + Common::EventHook m_frame_hook; + + // Used to optionally minimize throttling for improving input latency. + std::atomic_bool m_throttled_after_presentation = false; + DT m_max_throttle_skip_time{}; }; } // namespace CoreTiming diff --git a/Source/Core/DolphinQt/Settings/AdvancedPane.cpp b/Source/Core/DolphinQt/Settings/AdvancedPane.cpp index 399c46c486a..347310c0c7f 100644 --- a/Source/Core/DolphinQt/Settings/AdvancedPane.cpp +++ b/Source/Core/DolphinQt/Settings/AdvancedPane.cpp @@ -114,6 +114,19 @@ void AdvancedPane::CreateLayout() "

If unsure, leave this unchecked.")); timing_group_layout->addWidget(correct_time_drift); + auto* const rush_frame_presentation = + // i18n: "Rush" is a verb + new ConfigBool{tr("Rush Frame Presentation"), Config::MAIN_RUSH_FRAME_PRESENTATION}; + rush_frame_presentation->SetDescription( + tr("Limits throttling between input and frame output," + " speeding through emulation to reach presentation," + " displaying sooner, and thus reducing input latency." + "

This will generally make frame pacing worse." + "
This setting can work either with or without Immediately Present XFB." + "
An Audio Buffer Size of at least 80 ms is recommended to ensure full effect." + "

If unsure, leave this unchecked.")); + timing_group_layout->addWidget(rush_frame_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 d2ba5f22118..ce4b62a85b7 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);