mirror of
https://github.com/dolphin-emu/dolphin.git
synced 2026-03-19 03:42:36 +00:00
CoreTiming/VideoCommon: Add "Sync to Host Refresh Rate" setting.
This commit is contained in:
parent
7a0bceecb0
commit
a078950d14
@ -339,6 +339,8 @@ const Info<int> MAIN_ISO_PATH_COUNT{{System::Main, "General", "ISOPaths"}, 0};
|
||||
const Info<std::string> MAIN_SKYLANDERS_PATH{{System::Main, "General", "SkylandersCollectionPath"},
|
||||
""};
|
||||
const Info<bool> MAIN_TIME_TRACKING{{System::Main, "General", "EnablePlayTimeTracking"}, true};
|
||||
const Info<bool> MAIN_SYNC_TO_HOST_REFRESH_RATE{{System::Main, "General", "SyncToHostRefreshRate"},
|
||||
true};
|
||||
|
||||
static Info<std::string> MakeISOPathConfigInfo(size_t idx)
|
||||
{
|
||||
|
||||
@ -197,6 +197,7 @@ extern const Info<bool> MAIN_RENDER_WINDOW_AUTOSIZE;
|
||||
extern const Info<bool> MAIN_KEEP_WINDOW_ON_TOP;
|
||||
extern const Info<bool> MAIN_DISABLE_SCREENSAVER;
|
||||
extern const Info<bool> MAIN_TIME_TRACKING;
|
||||
extern const Info<bool> MAIN_SYNC_TO_HOST_REFRESH_RATE;
|
||||
|
||||
// Main.General
|
||||
|
||||
|
||||
@ -164,7 +164,7 @@ void CoreTimingManager::RefreshConfig()
|
||||
OSD::AddMessage("Minimum speed is 100% in Hardcore Mode");
|
||||
}
|
||||
|
||||
UpdateSpeedLimit(GetTicks(), Config::Get(Config::MAIN_EMULATION_SPEED));
|
||||
UpdateSpeedLimit(GetTicks());
|
||||
|
||||
m_use_precision_timer = Config::Get(Config::MAIN_PRECISION_FRAME_TIMING);
|
||||
}
|
||||
@ -433,8 +433,16 @@ void CoreTimingManager::SleepUntil(TimePoint time_point)
|
||||
}
|
||||
}
|
||||
|
||||
void CoreTimingManager::AdjustThrottleReferenceTime(DT adjustment)
|
||||
{
|
||||
m_throttle_reference_time_adjustment.fetch_add(adjustment.count());
|
||||
}
|
||||
|
||||
void CoreTimingManager::Throttle(const s64 target_cycle)
|
||||
{
|
||||
m_throttle_reference_time +=
|
||||
DT{m_throttle_reference_time_adjustment.exchange(0, std::memory_order_relaxed)};
|
||||
|
||||
const TimePoint time = Clock::now();
|
||||
|
||||
const bool already_throttled =
|
||||
@ -494,10 +502,9 @@ void CoreTimingManager::Throttle(const s64 target_cycle)
|
||||
SleepUntil(target_time);
|
||||
}
|
||||
|
||||
void CoreTimingManager::UpdateSpeedLimit(s64 cycle, double new_speed)
|
||||
void CoreTimingManager::UpdateSpeedLimit(s64 cycle)
|
||||
{
|
||||
m_emulation_speed = new_speed;
|
||||
|
||||
const auto new_speed = Config::Get(Config::MAIN_EMULATION_SPEED);
|
||||
const u32 new_clock_per_sec =
|
||||
std::lround(m_system.GetSystemTimers().GetTicksPerSecond() * new_speed);
|
||||
|
||||
@ -517,6 +524,7 @@ void CoreTimingManager::ResetThrottle(s64 cycle)
|
||||
{
|
||||
m_throttle_reference_cycle = cycle;
|
||||
m_throttle_reference_time = Clock::now();
|
||||
m_throttle_reference_time_adjustment.store(0, std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void CoreTimingManager::UpdateVISkip(TimePoint current_time, TimePoint target_time)
|
||||
@ -560,7 +568,7 @@ void CoreTimingManager::AdjustEventQueueTimes(u32 new_ppc_clock, u32 old_ppc_clo
|
||||
{
|
||||
const s64 ticks = m_globals.global_timer;
|
||||
|
||||
UpdateSpeedLimit(ticks, m_emulation_speed);
|
||||
UpdateSpeedLimit(ticks);
|
||||
|
||||
g_perf_metrics.AdjustClockSpeed(ticks, new_ppc_clock, old_ppc_clock);
|
||||
|
||||
|
||||
@ -157,8 +157,11 @@ public:
|
||||
// Directly accessed by the JIT.
|
||||
Globals& GetGlobals() { return m_globals; }
|
||||
|
||||
// Callable from any thread. Takes effect on the next Throttle call.
|
||||
void AdjustThrottleReferenceTime(DT adjustment);
|
||||
|
||||
// Throttle the CPU to the specified target cycle.
|
||||
void Throttle(const s64 target_cycle);
|
||||
void Throttle(s64 target_cycle);
|
||||
|
||||
// May be used from CPU or GPU thread.
|
||||
void SleepUntil(TimePoint time_point);
|
||||
@ -211,16 +214,17 @@ private:
|
||||
|
||||
s64 m_throttle_reference_cycle = 0;
|
||||
TimePoint m_throttle_reference_time = Clock::now();
|
||||
std::atomic<DT::rep> m_throttle_reference_time_adjustment{};
|
||||
|
||||
u32 m_throttle_adj_clock_per_sec = 0;
|
||||
bool m_throttle_disable_vi_int = false;
|
||||
|
||||
DT m_max_fallback = {};
|
||||
DT m_max_variance = {};
|
||||
bool m_correct_time_drift = false;
|
||||
double m_emulation_speed = 1.0;
|
||||
|
||||
bool IsSpeedUnlimited() const;
|
||||
void UpdateSpeedLimit(s64 cycle, double new_speed);
|
||||
void UpdateSpeedLimit(s64 cycle);
|
||||
void ResetThrottle(s64 cycle);
|
||||
TimePoint CalculateTargetHostTimeInternal(s64 target_cycle);
|
||||
void UpdateVISkip(TimePoint current_time, TimePoint target_time);
|
||||
|
||||
@ -138,6 +138,17 @@ void AdvancedPane::CreateLayout()
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
timing_group_layout->addWidget(smooth_early_presentation);
|
||||
|
||||
auto* const sync_to_host_refresh =
|
||||
new ConfigBool{tr("Sync to Host Refresh Rate"), Config::MAIN_SYNC_TO_HOST_REFRESH_RATE};
|
||||
sync_to_host_refresh->SetDescription(
|
||||
tr("Adjusts emulation speed to match host refresh rate when V-Sync is enabled."
|
||||
"<br>This can make 59.94 FPS games run at 60 FPS."
|
||||
"<br><br>Not needed or recommended for users with variable refresh rate displays."
|
||||
"<br><br>For best results, turn off Immediately Present XFB"
|
||||
" and Skip Presenting Duplicate Frames."
|
||||
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
|
||||
timing_group_layout->addWidget(sync_to_host_refresh);
|
||||
|
||||
// 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();
|
||||
|
||||
@ -11,6 +11,8 @@
|
||||
#include "Common/Logging/Log.h"
|
||||
#include "Common/MsgHandler.h"
|
||||
|
||||
#include "Core/Config/MainSettings.h"
|
||||
|
||||
#include "VideoBackends/Vulkan/CommandBufferManager.h"
|
||||
#include "VideoBackends/Vulkan/ObjectCache.h"
|
||||
#include "VideoBackends/Vulkan/StateTracker.h"
|
||||
@ -311,6 +313,10 @@ void VKGfx::PresentBackbuffer()
|
||||
// End drawing to backbuffer
|
||||
StateTracker::GetInstance()->EndRenderPass();
|
||||
|
||||
// If "Sync to Host Refresh Rate" is active, wait for completion for a usable timestamp.
|
||||
const bool wait_for_completion =
|
||||
g_ActiveConfig.bVSyncActive && Config::Get(Config::MAIN_SYNC_TO_HOST_REFRESH_RATE);
|
||||
|
||||
if (m_swap_chain->IsCurrentImageValid())
|
||||
{
|
||||
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
|
||||
@ -322,12 +328,13 @@ void VKGfx::PresentBackbuffer()
|
||||
// Because this final command buffer is rendering to the swap chain, we need to wait for
|
||||
// the available semaphore to be signaled before executing the buffer. This final submission
|
||||
// can happen off-thread in the background while we're preparing the next frame.
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, false, true, m_swap_chain->GetSwapChain(),
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, wait_for_completion, true,
|
||||
m_swap_chain->GetSwapChain(),
|
||||
m_swap_chain->GetCurrentImageIndex());
|
||||
}
|
||||
else
|
||||
{
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, false, true);
|
||||
g_command_buffer_mgr->SubmitCommandBuffer(true, wait_for_completion, true);
|
||||
}
|
||||
|
||||
// New cmdbuffer, so invalidate state.
|
||||
|
||||
@ -902,9 +902,22 @@ void Presenter::Present(PresentInfo* present_info)
|
||||
|
||||
if (present_info != nullptr)
|
||||
{
|
||||
auto& core_timing = Core::System::GetInstance().GetCoreTiming();
|
||||
|
||||
const auto present_time = GetUpdatedPresentationTime(present_info->intended_present_time);
|
||||
|
||||
Core::System::GetInstance().GetCoreTiming().SleepUntil(present_time);
|
||||
// "Sync to Host Refresh Rate" throttle adjustment.
|
||||
if (Config::Get(Config::MAIN_SYNC_TO_HOST_REFRESH_RATE) && Config::Get(Config::GFX_VSYNC))
|
||||
{
|
||||
constexpr DT MAX_ADJUSTMENT = std::chrono::microseconds{100};
|
||||
const auto adjustment =
|
||||
std::clamp(m_ideal_present_time - present_time, -MAX_ADJUSTMENT, MAX_ADJUSTMENT);
|
||||
|
||||
DEBUG_LOG_FMT(VIDEO, "Adjusting throttle by {:.2f} ms.", DT_ms(adjustment).count());
|
||||
core_timing.AdjustThrottleReferenceTime(adjustment);
|
||||
}
|
||||
|
||||
core_timing.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();
|
||||
@ -914,6 +927,12 @@ void Presenter::Present(PresentInfo* present_info)
|
||||
g_gfx->PresentBackbuffer();
|
||||
}
|
||||
|
||||
// "Sync to Host Refresh Rate" targets the timing immediately between the return from two swaps.
|
||||
// Maybe there is a better way to determine a proper vsync deadline ?
|
||||
const auto now = Clock::now();
|
||||
const auto time_since_last_present = now - std::exchange(m_last_after_present_time, now);
|
||||
m_ideal_present_time = now + time_since_last_present / 2;
|
||||
|
||||
if (m_xfb_entry)
|
||||
{
|
||||
// Update the window size based on the frame that was just rendered.
|
||||
|
||||
@ -184,6 +184,10 @@ private:
|
||||
TimePoint m_next_swap_estimated_time{Clock::now()};
|
||||
|
||||
std::atomic_bool m_immediate_swap_happened_this_field{};
|
||||
|
||||
// Used for "Sync to Host Refresh Rate".
|
||||
TimePoint m_last_after_present_time{};
|
||||
TimePoint m_ideal_present_time{};
|
||||
};
|
||||
|
||||
} // namespace VideoCommon
|
||||
|
||||
Loading…
Reference in New Issue
Block a user