summaryrefslogtreecommitdiff
path: root/Source/Core/VideoCommon
diff options
context:
space:
mode:
authorJordan Woyak <jordan.woyak@gmail.com>2025-10-27 18:57:07 -0500
committerJordan Woyak <jordan.woyak@gmail.com>2025-11-11 20:01:52 -0600
commitc2a1dce246e27f1ac9dc421387e4d70402bb96f4 (patch)
treeb025e98168ed06ae7a9fba668510a215907bfeb3 /Source/Core/VideoCommon
parentcc331feb020d71f109d41b7f841bd69caa96bbb3 (diff)
VideoCommon: Add "Smooth Early Presentation" setting to improve frame pacing with ImmediateXFB and/or RushFramePresentation.
Diffstat (limited to 'Source/Core/VideoCommon')
-rw-r--r--Source/Core/VideoCommon/Present.cpp73
-rw-r--r--Source/Core/VideoCommon/Present.h9
-rw-r--r--Source/Core/VideoCommon/VideoEvents.h5
3 files changed, 64 insertions, 23 deletions
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<int>& target_rc,
}
}
-void Presenter::Present(std::optional<TimePoint> presentation_time)
+void Presenter::Present(PresentInfo* present_info)
{
m_present_count++;
@@ -888,8 +887,16 @@ void Presenter::Present(std::optional<TimePoint> presentation_time)
{
std::lock_guard<std::mutex> 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<TimePoint> 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 d6662f97f1..e9ad21dbd9 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<TimePoint> presentation_time = std::nullopt);
+ void Present(PresentInfo* present_info = nullptr);
void ClearLastXfbId() { m_last_xfb_id = std::numeric_limits<u64>::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 b7ecf0fca5..fe30aa251b 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
{