VideoCommon: Add "Smooth Early Presentation" setting to improve frame pacing with ImmediateXFB and/or RushFramePresentation.

This commit is contained in:
Jordan Woyak
2025-10-27 18:57:07 -05:00
parent cc331feb02
commit c2a1dce246
6 changed files with 78 additions and 23 deletions
+54 -19
View File
@@ -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)