diff --git a/Source/Core/Common/Timer.cpp b/Source/Core/Common/Timer.cpp index fb81164390..0a23080c6f 100644 --- a/Source/Core/Common/Timer.cpp +++ b/Source/Core/Common/Timer.cpp @@ -4,6 +4,7 @@ #include "Common/Timer.h" #include <chrono> +#include <thread> #ifdef _WIN32 #include <Windows.h> @@ -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,82 @@ 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<LONGLONG, std::ratio<100, std::nano::den>::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<TimerDuration>(std::chrono::milliseconds{TIMER_RESOLUTION_MS}) * 95 / 100; + + const auto wait_time = target - Clock::now() - SPIN_TIME; + const auto ticks = std::min(duration_cast<TimerDuration>(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) + { +#if defined(_WIN32) + YieldProcessor(); +#else + std::this_thread::yield(); +#endif + } +} + } // 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 <Windows.h> +#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 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<bool> MAIN_FPRF{{System::Main, "Core", "FPRF"}, false}; const Info<bool> MAIN_ACCURATE_NANS{{System::Main, "Core", "AccurateNaNs"}, false}; const Info<bool> MAIN_DISABLE_ICACHE{{System::Main, "Core", "DisableICache"}, false}; const Info<float> 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<bool> MAIN_PRECISION_FRAME_TIMING{{System::Main, "Core", "PrecisionFrameTiming"}, + DEFAULT_PRECISION_FRAME_TIMING}; const Info<float> MAIN_OVERCLOCK{{System::Main, "Core", "Overclock"}, 1.0f}; const Info<bool> MAIN_OVERCLOCK_ENABLE{{System::Main, "Core", "OverclockEnable"}, false}; const Info<bool> 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<bool> MAIN_FPRF; extern const Info<bool> MAIN_ACCURATE_NANS; extern const Info<bool> MAIN_DISABLE_ICACHE; extern const Info<float> MAIN_EMULATION_SPEED; +extern const Info<bool> MAIN_PRECISION_FRAME_TIMING; extern const Info<float> MAIN_OVERCLOCK; extern const Info<bool> MAIN_OVERCLOCK_ENABLE; extern const Info<bool> 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<std::pair<QString, QString>> 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." + "<br><br>This will marginally increase power usage." + "<br><br><dolphin_emphasis>If unsure, leave this checked.</dolphin_emphasis>")); + 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<int>(&QComboBox::currentIndexChanged), this, [&](int index) { const bool is_custom_aspect_ratio = (index == static_cast<int>(AspectMode::Custom)) || (index == static_cast<int>(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;