CoreTiming/VideoCommon: Add "Sync to Host Refresh Rate" setting.

This commit is contained in:
Jordan Woyak 2025-04-08 22:23:25 -05:00
parent 92c816faad
commit e82fdc778c
7 changed files with 65 additions and 3 deletions

View File

@ -324,6 +324,7 @@ 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_REFRESH_RATE{{System::Main, "General", "SyncToHostRefreshRate"}, true};
static Info<std::string> MakeISOPathConfigInfo(size_t idx)
{

View File

@ -190,6 +190,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_REFRESH_RATE;
// Main.General

View File

@ -28,6 +28,7 @@
#include "VideoCommon/PerformanceMetrics.h"
#include "VideoCommon/VideoBackendBase.h"
#include "VideoCommon/VideoConfig.h"
#include "VideoCommon/VideoEvents.h"
namespace CoreTiming
{
@ -103,6 +104,19 @@ void CoreTimingManager::Init()
m_event_fifo_id = 0;
m_ev_lost = RegisterEvent("_lost_event", &EmptyTimedCallback);
m_after_frame_hook = AfterPresentEvent::Register(
[this](const PresentInfo& info) {
const bool sync_to_host_active =
Config::Get(Config::MAIN_SYNC_REFRESH_RATE) && g_ActiveConfig.bVSyncActive;
const auto presentation_time = PresentationTime{.ticks = info.emulated_timestamp,
.time = Clock::now(),
.sync_to_host_active = sync_to_host_active};
m_last_presentation.Store(std::make_unique<PresentationTime>(presentation_time));
},
"CoreTiming AfterPresentEvent");
}
void CoreTimingManager::Shutdown()
@ -112,6 +126,8 @@ void CoreTimingManager::Shutdown()
ClearPendingEvents();
UnregisterAllEvents();
CPUThreadConfigCallback::RemoveConfigChangedCallback(m_registered_config_callback_id);
m_after_frame_hook.reset();
m_last_presentation = nullptr;
}
void CoreTimingManager::RefreshConfig()
@ -201,6 +217,7 @@ void CoreTimingManager::DoState(PointerWrap& p)
// The stave state has changed the time, so our previous Throttle targets are invalid.
// Especially when global_time goes down; So we create a fake throttle update.
ResetThrottle(m_globals.global_timer);
m_last_presentation = nullptr;
}
}
@ -399,6 +416,14 @@ void CoreTimingManager::SleepUntil(TimePoint time_point)
void CoreTimingManager::Throttle(const s64 target_cycle)
{
// Adjust throttle based on last presentation if "Sync to Host Refresh Rate" was active.
const auto last_presentation = m_last_presentation.Exchange(nullptr);
if (last_presentation && last_presentation->sync_to_host_active)
{
m_throttle_last_cycle = last_presentation->ticks;
m_throttle_deadline = last_presentation->time;
}
// Based on number of cycles and emulation speed, increase the target deadline
const s64 cycles = target_cycle - m_throttle_last_cycle;
m_throttle_last_cycle = target_cycle;

View File

@ -22,7 +22,9 @@
#include <unordered_map>
#include <vector>
#include "Common/AtomicUniquePtr.h"
#include "Common/CommonTypes.h"
#include "Common/HookableEvent.h"
#include "Common/SPSCQueue.h"
#include "Common/Timer.h"
#include "Core/CPUThreadConfigCallback.h"
@ -219,6 +221,16 @@ private:
std::atomic_bool m_use_precision_timer = false;
Common::PrecisionTimer m_precision_cpu_timer;
Common::PrecisionTimer m_precision_gpu_timer;
struct PresentationTime
{
u64 ticks;
Clock::time_point time;
bool sync_to_host_active;
};
Common::AtomicUniquePtr<PresentationTime> m_last_presentation;
Common::EventHook m_after_frame_hook;
};
} // namespace CoreTiming

View File

@ -88,6 +88,19 @@ void AdvancedPane::CreateLayout()
"needed.<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
cpu_options_group_layout->addWidget(m_accurate_cpu_cache_checkbox);
auto* const timing_group = new QGroupBox(tr("Timing"));
main_layout->addWidget(timing_group);
auto* timing_group_layout = new QVBoxLayout{timing_group};
auto* const sync_to_host_refresh =
new ConfigBool{tr("Sync to Host Refresh Rate"), Config::MAIN_SYNC_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>Has no effect when Immediate XFB is in use."
"<br><br><dolphin_emphasis>If unsure, leave this unchecked.</dolphin_emphasis>"));
timing_group_layout->addWidget(sync_to_host_refresh);
auto* clock_override = new QGroupBox(tr("Clock Override"));
auto* clock_override_layout = new QVBoxLayout();
clock_override->setLayout(clock_override_layout);

View File

@ -16,6 +16,7 @@
#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"
@ -316,6 +317,9 @@ void VKGfx::PresentBackbuffer()
// End drawing to backbuffer
StateTracker::GetInstance()->EndRenderPass();
const bool wait_for_completion =
Config::Get(Config::MAIN_SYNC_REFRESH_RATE) && g_ActiveConfig.bVSyncActive;
if (m_swap_chain->IsCurrentImageValid())
{
// Transition the backbuffer to PRESENT_SRC to ensure all commands drawing
@ -327,12 +331,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.

View File

@ -97,7 +97,12 @@ void VideoBackendBase::Video_OutputXFB(u32 xfb_addr, u32 fb_width, u32 fb_stride
auto& system = Core::System::GetInstance();
system.GetFifo().SyncGPU(Fifo::SyncGPUReason::Swap);
const TimePoint presentation_time = system.GetCoreTiming().GetTargetHostTime(ticks);
const bool sync_to_host_refresh =
Config::Get(Config::MAIN_SYNC_REFRESH_RATE) && g_ActiveConfig.bVSyncActive;
const TimePoint presentation_time =
sync_to_host_refresh ? Clock::now() : system.GetCoreTiming().GetTargetHostTime(ticks);
AsyncRequests::GetInstance()->PushEvent([=] {
g_presenter->ViSwap(xfb_addr, fb_width, fb_stride, fb_height, ticks, presentation_time);
});