Merge pull request #13426 from jordan-woyak/sleepy-windows
Common/CoreTiming: Create a PrecisionTimer class for better frame pacing.
This commit is contained in:
commit
bee7035322
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue