summaryrefslogtreecommitdiff
path: root/Source/Core
diff options
context:
space:
mode:
authorJMC47 <JMC4789@gmail.com>2025-04-04 15:34:00 -0400
committerGitHub <noreply@github.com>2025-04-04 15:34:00 -0400
commitbee70353226a78071ea70f160c85bafc9b1f60cd (patch)
tree7c0adb77c6b6ccecbab369a6e07348d07660d0b2 /Source/Core
parentdadbd2f9fb3fd032672781c23c7466231e19346b (diff)
parente0e53f3235605c7d402a2c415a9154b3f48a8f97 (diff)
Merge pull request #13426 from jordan-woyak/sleepy-windows
Common/CoreTiming: Create a PrecisionTimer class for better frame pacing.
Diffstat (limited to 'Source/Core')
-rw-r--r--Source/Core/Common/Timer.cpp77
-rw-r--r--Source/Core/Common/Timer.h21
-rw-r--r--Source/Core/Core/Config/MainSettings.cpp9
-rw-r--r--Source/Core/Core/Config/MainSettings.h1
-rw-r--r--Source/Core/Core/CoreTiming.cpp20
-rw-r--r--Source/Core/Core/CoreTiming.h4
-rw-r--r--Source/Core/DolphinQt/Config/Graphics/GeneralWidget.cpp56
-rw-r--r--Source/Core/DolphinQt/Config/Graphics/GeneralWidget.h2
8 files changed, 142 insertions, 48 deletions
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;