mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2025-12-16 04:09:39 +00:00
VideoCommon: Add "Smooth Early Presentation" setting to improve frame pacing with ImmediateXFB and/or RushFramePresentation.
This commit is contained in:
parent
cc331feb02
commit
c2a1dce246
@ -49,6 +49,8 @@ const Info<int> MAIN_TIMING_VARIANCE{{System::Main, "Core", "TimingVariance"}, 4
|
||||
const Info<bool> MAIN_CORRECT_TIME_DRIFT{{System::Main, "Core", "CorrectTimeDrift"}, false};
|
||||
const Info<bool> MAIN_RUSH_FRAME_PRESENTATION{{System::Main, "Core", "RushFramePresentation"},
|
||||
false};
|
||||
const Info<bool> 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;
|
||||
|
||||
@ -66,6 +66,7 @@ extern const Info<int> MAIN_MAX_FALLBACK;
|
||||
extern const Info<int> MAIN_TIMING_VARIANCE;
|
||||
extern const Info<bool> MAIN_CORRECT_TIME_DRIFT;
|
||||
extern const Info<bool> MAIN_RUSH_FRAME_PRESENTATION;
|
||||
extern const Info<bool> MAIN_SMOOTH_EARLY_PRESENTATION;
|
||||
extern const Info<bool> MAIN_CPU_THREAD;
|
||||
extern const Info<bool> MAIN_SYNC_ON_SKIP_IDLE;
|
||||
extern const Info<std::string> MAIN_DEFAULT_ISO;
|
||||
|
||||
@ -127,6 +127,17 @@ void AdvancedPane::CreateLayout()
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
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."
|
||||
"<br><br>This can improve frame pacing with Immediately Present XFB"
|
||||
" and/or Rush Frame Presentation,"
|
||||
" while still maintaining most of the input latency benefits."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
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();
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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
|
||||
{
|
||||
|
||||
Loading…
Reference in New Issue
Block a user