From e5c8935acc7fe72fe1d3331caf16c49354afc003 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 14 Mar 2025 18:16:39 -0500 Subject: [PATCH 1/3] Common: Create a PrecisionTimer class. --- Source/Core/Common/Timer.cpp | 71 +++++++++++++++++++++++++++++++++++- Source/Core/Common/Timer.h | 21 +++++++++++ 2 files changed, 90 insertions(+), 2 deletions(-) diff --git a/Source/Core/Common/Timer.cpp b/Source/Core/Common/Timer.cpp index fb81164390..29febef6c5 100644 --- a/Source/Core/Common/Timer.cpp +++ b/Source/Core/Common/Timer.cpp @@ -4,6 +4,7 @@ #include "Common/Timer.h" #include +#include #ifdef _WIN32 #include @@ -13,6 +14,7 @@ #endif #include "Common/CommonTypes.h" +#include "Common/Logging/Log.h" namespace Common { @@ -91,6 +93,10 @@ u64 Timer::GetLocalTimeSinceJan1970() #endif } +// This is requested by Timer::IncreaseResolution on Windows. +// On Linux/other we hope/assume we already have 1ms scheduling granularity. +static constexpr int TIMER_RESOLUTION_MS = 1; + void Timer::IncreaseResolution() { #ifdef _WIN32 @@ -110,15 +116,76 @@ void Timer::IncreaseResolution() sizeof(PowerThrottling)); // Not actually sure how useful this is these days.. :') - timeBeginPeriod(1); + timeBeginPeriod(TIMER_RESOLUTION_MS); #endif } void Timer::RestoreResolution() { #ifdef _WIN32 - timeEndPeriod(1); + timeEndPeriod(TIMER_RESOLUTION_MS); #endif } +PrecisionTimer::PrecisionTimer() +{ +#if defined(_WIN32) + // "TIMER_HIGH_RESOLUTION" requires Windows 10, version 1803, and later. + m_timer_handle = + CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + + if (m_timer_handle == NULL) + { + ERROR_LOG_FMT(COMMON, "CREATE_WAITABLE_TIMER_HIGH_RESOLUTION: Error:{}", GetLastError()); + + // Create a normal timer if "HIGH_RESOLUTION" isn't available. + m_timer_handle = CreateWaitableTimerExW(NULL, NULL, 0, TIMER_ALL_ACCESS); + if (m_timer_handle == NULL) + ERROR_LOG_FMT(COMMON, "CreateWaitableTimerExW: Error:{}", GetLastError()); + } +#endif +} + +PrecisionTimer::~PrecisionTimer() +{ +#if defined(_WIN32) + CloseHandle(m_timer_handle); +#endif +} + +void PrecisionTimer::SleepUntil(Clock::time_point target) +{ + constexpr auto SPIN_TIME = + std::chrono::milliseconds{TIMER_RESOLUTION_MS} + std::chrono::microseconds{20}; + +#if defined(_WIN32) + while (true) + { + // SetWaitableTimerEx takes time in "100 nanosecond intervals". + using TimerDuration = std::chrono::duration::type>; + + // Apparently waiting longer than the timer resolution gives terrible accuracy. + // We'll wait in steps of 95% of the time period. + constexpr auto MAX_TICKS = + duration_cast(std::chrono::milliseconds{TIMER_RESOLUTION_MS}) * 95 / 100; + + const auto wait_time = target - Clock::now() - SPIN_TIME; + const auto ticks = std::min(duration_cast(wait_time), MAX_TICKS).count(); + if (ticks <= 0) + break; + + const LARGE_INTEGER due_time{.QuadPart = -ticks}; + SetWaitableTimerEx(m_timer_handle, &due_time, 0, NULL, NULL, NULL, 0); + WaitForSingleObject(m_timer_handle, INFINITE); + } +#else + // Sleeping on Linux generally isn't as terrible as it is on Windows. + std::this_thread::sleep_until(target - SPIN_TIME); +#endif + + // Spin for the remaining time. + while (Clock::now() < target) + std::this_thread::yield(); +} + } // Namespace Common diff --git a/Source/Core/Common/Timer.h b/Source/Core/Common/Timer.h index 7298f4a368..7f36308e78 100644 --- a/Source/Core/Common/Timer.h +++ b/Source/Core/Common/Timer.h @@ -5,6 +5,10 @@ #include "Common/CommonTypes.h" +#ifdef _WIN32 +#include +#endif + namespace Common { class Timer @@ -32,4 +36,21 @@ private: bool m_running{false}; }; +class PrecisionTimer +{ +public: + PrecisionTimer(); + ~PrecisionTimer(); + + PrecisionTimer(const PrecisionTimer&) = delete; + PrecisionTimer& operator=(const PrecisionTimer&) = delete; + + void SleepUntil(Clock::time_point); + +private: +#ifdef _WIN32 + HANDLE m_timer_handle; +#endif +}; + } // Namespace Common From 7dc27753e2703bd979e32862b765bba475af291c Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Fri, 14 Mar 2025 18:16:24 -0500 Subject: [PATCH 2/3] CoreTiming: Add a setting to use Common::PrecisionTimer. --- Source/Core/Core/Config/MainSettings.cpp | 9 +++ Source/Core/Core/Config/MainSettings.h | 1 + Source/Core/Core/CoreTiming.cpp | 20 +++---- Source/Core/Core/CoreTiming.h | 4 ++ .../Config/Graphics/GeneralWidget.cpp | 56 ++++++++----------- .../DolphinQt/Config/Graphics/GeneralWidget.h | 2 - 6 files changed, 46 insertions(+), 46 deletions(-) diff --git a/Source/Core/Core/Config/MainSettings.cpp b/Source/Core/Core/Config/MainSettings.cpp index bd43c721c8..37f2a6054e 100644 --- a/Source/Core/Core/Config/MainSettings.cpp +++ b/Source/Core/Core/Config/MainSettings.cpp @@ -211,6 +211,15 @@ const Info MAIN_FPRF{{System::Main, "Core", "FPRF"}, false}; const Info MAIN_ACCURATE_NANS{{System::Main, "Core", "AccurateNaNs"}, false}; const Info MAIN_DISABLE_ICACHE{{System::Main, "Core", "DisableICache"}, false}; const Info MAIN_EMULATION_SPEED{{System::Main, "Core", "EmulationSpeed"}, 1.0f}; +#if defined(ANDROID) +// Currently disabled by default on Android for concern of increased power usage while on battery. +// It is also not yet exposed in the UI on Android. +constexpr bool DEFAULT_PRECISION_FRAME_TIMING = false; +#else +constexpr bool DEFAULT_PRECISION_FRAME_TIMING = true; +#endif +const Info MAIN_PRECISION_FRAME_TIMING{{System::Main, "Core", "PrecisionFrameTiming"}, + DEFAULT_PRECISION_FRAME_TIMING}; const Info MAIN_OVERCLOCK{{System::Main, "Core", "Overclock"}, 1.0f}; const Info MAIN_OVERCLOCK_ENABLE{{System::Main, "Core", "OverclockEnable"}, false}; const Info MAIN_RAM_OVERRIDE_ENABLE{{System::Main, "Core", "RAMOverrideEnable"}, false}; diff --git a/Source/Core/Core/Config/MainSettings.h b/Source/Core/Core/Config/MainSettings.h index 68fb75e6ef..51aa7ec8aa 100644 --- a/Source/Core/Core/Config/MainSettings.h +++ b/Source/Core/Core/Config/MainSettings.h @@ -125,6 +125,7 @@ extern const Info MAIN_FPRF; extern const Info MAIN_ACCURATE_NANS; extern const Info MAIN_DISABLE_ICACHE; extern const Info MAIN_EMULATION_SPEED; +extern const Info MAIN_PRECISION_FRAME_TIMING; extern const Info MAIN_OVERCLOCK; extern const Info MAIN_OVERCLOCK_ENABLE; extern const Info MAIN_RAM_OVERRIDE_ENABLE; diff --git a/Source/Core/Core/CoreTiming.cpp b/Source/Core/Core/CoreTiming.cpp index 6893bc2b91..b0aca0168a 100644 --- a/Source/Core/Core/CoreTiming.cpp +++ b/Source/Core/Core/CoreTiming.cpp @@ -137,6 +137,8 @@ void CoreTimingManager::RefreshConfig() } m_emulation_speed = Config::Get(Config::MAIN_EMULATION_SPEED); + + m_use_precision_timer = Config::Get(Config::MAIN_PRECISION_FRAME_TIMING); } void CoreTimingManager::DoState(PointerWrap& p) @@ -373,7 +375,13 @@ void CoreTimingManager::SleepUntil(TimePoint time_point) { const TimePoint time = Clock::now(); - std::this_thread::sleep_until(time_point); + if (time >= time_point) + return; + + if (m_use_precision_timer) + m_precision_timer.SleepUntil(time_point); + else + std::this_thread::sleep_until(time_point); if (Core::IsCPUThread()) { @@ -416,15 +424,7 @@ void CoreTimingManager::Throttle(const s64 target_cycle) // It doesn't matter what amount of lag we skip VI at, as long as it's constant. m_throttle_disable_vi_int = 0.0 < speed && m_throttle_deadline < vi_deadline; - // Only sleep if we are behind the deadline - if (time < m_throttle_deadline) - { - std::this_thread::sleep_until(m_throttle_deadline); - - // Count amount of time sleeping for analytics - const TimePoint time_after_sleep = Clock::now(); - g_perf_metrics.CountThrottleSleep(time_after_sleep - time); - } + SleepUntil(m_throttle_deadline); } void CoreTimingManager::ResetThrottle(s64 cycle) diff --git a/Source/Core/Core/CoreTiming.h b/Source/Core/Core/CoreTiming.h index 88635e47ae..8200da3ded 100644 --- a/Source/Core/Core/CoreTiming.h +++ b/Source/Core/Core/CoreTiming.h @@ -24,6 +24,7 @@ #include "Common/CommonTypes.h" #include "Common/SPSCQueue.h" +#include "Common/Timer.h" #include "Core/CPUThreadConfigCallback.h" class PointerWrap; @@ -214,6 +215,9 @@ private: int DowncountToCycles(int downcount) const; int CyclesToDowncount(int cycles) const; + + bool m_use_precision_timer = false; + Common::PrecisionTimer m_precision_timer; }; } // namespace CoreTiming diff --git a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp index 19af3a47f6..b698fbdd77 100644 --- a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp +++ b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp @@ -36,7 +36,6 @@ GeneralWidget::GeneralWidget(GraphicsWindow* parent) { CreateWidgets(); - LoadSettings(); ConnectWidgets(); AddDescriptions(); @@ -50,7 +49,6 @@ GeneralWidget::GeneralWidget(GraphicsWindow* parent) GeneralWidget::GeneralWidget(GameConfigWidget* parent, Config::Layer* layer) : m_game_layer(layer) { CreateWidgets(); - LoadSettings(); ConnectWidgets(); AddDescriptions(); } @@ -61,7 +59,7 @@ void GeneralWidget::CreateWidgets() // Basic Section auto* m_video_box = new QGroupBox(tr("Basic")); - m_video_layout = new QGridLayout(); + auto* const video_layout = new QGridLayout{m_video_box}; std::vector> options; for (auto& backend : VideoBackendBase::GetAvailableBackends()) @@ -76,38 +74,41 @@ void GeneralWidget::CreateWidgets() tr("Stretch to Window"), tr("Custom"), tr("Custom (Stretch)")}, Config::GFX_ASPECT_RATIO, m_game_layer); m_custom_aspect_label = new QLabel(tr("Custom Aspect Ratio:")); - m_custom_aspect_label->setHidden(true); constexpr int MAX_CUSTOM_ASPECT_RATIO_RESOLUTION = 10000; m_custom_aspect_width = new ConfigInteger(1, MAX_CUSTOM_ASPECT_RATIO_RESOLUTION, Config::GFX_CUSTOM_ASPECT_RATIO_WIDTH, m_game_layer); - m_custom_aspect_width->setEnabled(false); - m_custom_aspect_width->setHidden(true); m_custom_aspect_height = new ConfigInteger(1, MAX_CUSTOM_ASPECT_RATIO_RESOLUTION, Config::GFX_CUSTOM_ASPECT_RATIO_HEIGHT, m_game_layer); - m_custom_aspect_height->setEnabled(false); - m_custom_aspect_height->setHidden(true); m_adapter_combo = new ToolTipComboBox; m_enable_vsync = new ConfigBool(tr("V-Sync"), Config::GFX_VSYNC, m_game_layer); m_enable_fullscreen = new ConfigBool(tr("Start in Fullscreen"), Config::MAIN_FULLSCREEN, m_game_layer); - m_video_box->setLayout(m_video_layout); + video_layout->addWidget(new QLabel(tr("Backend:")), 0, 0); + video_layout->addWidget(m_backend_combo, 0, 1, 1, -1); - m_video_layout->addWidget(new QLabel(tr("Backend:")), 0, 0); - m_video_layout->addWidget(m_backend_combo, 0, 1, 1, -1); + video_layout->addWidget(new QLabel(tr("Adapter:")), 1, 0); + video_layout->addWidget(m_adapter_combo, 1, 1, 1, -1); - m_video_layout->addWidget(new QLabel(tr("Adapter:")), 1, 0); - m_video_layout->addWidget(m_adapter_combo, 1, 1, 1, -1); + video_layout->addWidget(new QLabel(tr("Aspect Ratio:")), 2, 0); + video_layout->addWidget(m_aspect_combo, 2, 1, 1, -1); - m_video_layout->addWidget(new QLabel(tr("Aspect Ratio:")), 3, 0); - m_video_layout->addWidget(m_aspect_combo, 3, 1, 1, -1); + video_layout->addWidget(m_custom_aspect_label, 3, 0); + video_layout->addWidget(m_custom_aspect_width, 3, 1); + video_layout->addWidget(m_custom_aspect_height, 3, 2); - m_video_layout->addWidget(m_custom_aspect_label, 4, 0); - m_video_layout->addWidget(m_custom_aspect_width, 4, 1); - m_video_layout->addWidget(m_custom_aspect_height, 4, 2); + auto* const basic_grid = new QGridLayout; + video_layout->addLayout(basic_grid, video_layout->rowCount(), 0, 1, -1); + basic_grid->addWidget(m_enable_vsync, 0, 0); + basic_grid->addWidget(m_enable_fullscreen, 0, 1); - m_video_layout->addWidget(m_enable_vsync, 5, 0); - m_video_layout->addWidget(m_enable_fullscreen, 5, 1, 1, -1); + auto* const precision_timing = + new ConfigBool(tr("Precision Frame Timing"), Config::MAIN_PRECISION_FRAME_TIMING); + precision_timing->SetDescription( + tr("Uses high resolution timers and \"busy waiting\" for improved frame pacing." + "

This will marginally increase power usage." + "

If unsure, leave this checked.")); + basic_grid->addWidget(precision_timing, 1, 0); // Other auto* m_options_box = new QGroupBox(tr("Other")); @@ -171,24 +172,11 @@ void GeneralWidget::ConnectWidgets() connect(m_aspect_combo, qOverload(&QComboBox::currentIndexChanged), this, [&](int index) { const bool is_custom_aspect_ratio = (index == static_cast(AspectMode::Custom)) || (index == static_cast(AspectMode::CustomStretch)); - m_custom_aspect_width->setEnabled(is_custom_aspect_ratio); - m_custom_aspect_height->setEnabled(is_custom_aspect_ratio); m_custom_aspect_label->setHidden(!is_custom_aspect_ratio); m_custom_aspect_width->setHidden(!is_custom_aspect_ratio); m_custom_aspect_height->setHidden(!is_custom_aspect_ratio); }); -} - -void GeneralWidget::LoadSettings() -{ - const bool is_custom_aspect_ratio = - (Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::Custom) || - (Config::Get(Config::GFX_ASPECT_RATIO) == AspectMode::CustomStretch); - m_custom_aspect_width->setEnabled(is_custom_aspect_ratio); - m_custom_aspect_height->setEnabled(is_custom_aspect_ratio); - m_custom_aspect_label->setHidden(!is_custom_aspect_ratio); - m_custom_aspect_width->setHidden(!is_custom_aspect_ratio); - m_custom_aspect_height->setHidden(!is_custom_aspect_ratio); + m_aspect_combo->currentIndexChanged(m_aspect_combo->currentIndex()); } void GeneralWidget::BackendWarning() diff --git a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h index 5b4bf36441..42f74229d8 100644 --- a/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h +++ b/Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h @@ -37,7 +37,6 @@ signals: void BackendChanged(const QString& backend); private: - void LoadSettings(); void BackendWarning(); void CreateWidgets(); @@ -48,7 +47,6 @@ private: void OnEmulationStateChanged(bool running); // Video - QGridLayout* m_video_layout; ConfigStringChoice* m_backend_combo; ToolTipComboBox* m_adapter_combo; ConfigChoice* m_aspect_combo; From e0e53f3235605c7d402a2c415a9154b3f48a8f97 Mon Sep 17 00:00:00 2001 From: Jordan Woyak Date: Thu, 3 Apr 2025 15:45:30 -0500 Subject: [PATCH 3/3] Common/Timer: Use YieldProcessor on Windows. --- Source/Core/Common/Timer.cpp | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Source/Core/Common/Timer.cpp b/Source/Core/Common/Timer.cpp index 29febef6c5..0a23080c6f 100644 --- a/Source/Core/Common/Timer.cpp +++ b/Source/Core/Common/Timer.cpp @@ -185,7 +185,13 @@ void PrecisionTimer::SleepUntil(Clock::time_point target) // Spin for the remaining time. while (Clock::now() < target) + { +#if defined(_WIN32) + YieldProcessor(); +#else std::this_thread::yield(); +#endif + } } } // Namespace Common