From 96c4ae4457e34fb353ce40bac52acd92b85daace Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Fri, 26 Jul 2024 11:16:41 +0200 Subject: [PATCH] TimeUtil: Minor cleanup, add precise_sleep() --- Common/TimeUtil.cpp | 88 +++++++++++++++++++++++++++++++------ Common/TimeUtil.h | 22 ++++------ Core/MIPS/IR/IRJit.cpp | 4 +- Core/PSPLoaders.cpp | 12 ++--- Qt/QtMain.cpp | 4 ++ SDL/SDLMain.cpp | 2 + Windows/main.cpp | 2 + android/jni/app-android.cpp | 2 + libretro/libretro.cpp | 2 + 9 files changed, 103 insertions(+), 35 deletions(-) diff --git a/Common/TimeUtil.cpp b/Common/TimeUtil.cpp index fdb6817723..10f29e061a 100644 --- a/Common/TimeUtil.cpp +++ b/Common/TimeUtil.cpp @@ -41,20 +41,27 @@ static LARGE_INTEGER frequency; static double frequencyMult; static LARGE_INTEGER startTime; -static inline void InitTime() { - if (frequency.QuadPart == 0) { - QueryPerformanceFrequency(&frequency); - QueryPerformanceCounter(&startTime); - frequencyMult = 1.0 / static_cast(frequency.QuadPart); - } +HANDLE Timer; +int SchedulerPeriodMs; +INT64 QpcPerSecond; + +void TimeInit() { + QueryPerformanceFrequency(&frequency); + QueryPerformanceCounter(&startTime); + QpcPerSecond = frequency.QuadPart; + frequencyMult = 1.0 / static_cast(frequency.QuadPart); + + Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS); + TIMECAPS caps; + timeGetDevCaps(&caps, sizeof caps); + timeBeginPeriod(caps.wPeriodMin); + SchedulerPeriodMs = (int)caps.wPeriodMin; } double time_now_d() { - InitTime(); LARGE_INTEGER time; QueryPerformanceCounter(&time); - double elapsed = static_cast(time.QuadPart - startTime.QuadPart); - return elapsed * frequencyMult; + return static_cast(time.QuadPart - startTime.QuadPart) * frequencyMult; } // Fake, but usable in a pinch. Don't, though. @@ -91,24 +98,28 @@ void yield() { YieldProcessor(); } -TimeSpan::TimeSpan() { +Instant::Instant() { _dbg_assert_(frequencyMult != 0.0); QueryPerformanceCounter(reinterpret_cast(&nativeStart_)); } -double TimeSpan::ElapsedSeconds() const { +double Instant::ElapsedSeconds() const { LARGE_INTEGER time; QueryPerformanceCounter(&time); double elapsed = static_cast(time.QuadPart - nativeStart_); return elapsed * frequencyMult; } -int64_t TimeSpan::ElapsedNanos() const { +int64_t Instant::ElapsedNanos() const { return (int64_t)(ElapsedSeconds() * 1000000000.0); } #elif PPSSPP_PLATFORM(ANDROID) || PPSSPP_PLATFORM(LINUX) || PPSSPP_PLATFORM(MAC) || PPSSPP_PLATFORM(IOS) +void TimeInit() { + // Nothing to do. +} + // The only intended use is to match the timings in VK_GOOGLE_display_timing uint64_t time_now_raw() { struct timespec tp; @@ -174,6 +185,10 @@ double TimeSpan::ElapsedSeconds() const { #else +void TimeInit() { + // Nothing to do. +} + static time_t start; double time_now_d() { @@ -229,7 +244,7 @@ int64_t TimeSpan::ElapsedNanos() const { } double TimeSpan::ElapsedSeconds() const { - return (double)ElapsedNanos() * (1.0 / 1000000000); + return (double)ElapsedNanos() * (1.0 / 1000000000.0); } #endif @@ -246,6 +261,53 @@ void sleep_ms(int ms) { #endif } +// Precise Windows sleep function from: https://github.com/blat-blatnik/Snippets/blob/main/precise_sleep.c +// Described in: https://blog.bearcats.nl/perfect-sleep-function/ + +void sleep_precise(double seconds) { +#ifdef _WIN32 + LARGE_INTEGER qpc; + QueryPerformanceCounter(&qpc); + INT64 targetQpc = (INT64)(qpc.QuadPart + seconds * QpcPerSecond); + if (Timer) { // Try using a high resolution timer first. + const double TOLERANCE = 0.001'02; + INT64 maxTicks = (INT64)SchedulerPeriodMs * 9'500; + for (;;) // Break sleep up into parts that are lower than scheduler period. + { + double remainingSeconds = (targetQpc - qpc.QuadPart) / (double)QpcPerSecond; + INT64 sleepTicks = (INT64)((remainingSeconds - TOLERANCE) * 10'000'000); + if (sleepTicks <= 0) + break; + LARGE_INTEGER due; + due.QuadPart = -(sleepTicks > maxTicks ? maxTicks : sleepTicks); + SetWaitableTimerEx(Timer, &due, 0, NULL, NULL, NULL, 0); + WaitForSingleObject(Timer, INFINITE); + QueryPerformanceCounter(&qpc); + } + } else { // Fallback to Sleep. + const double TOLERANCE = 0.000'02; + double sleepMs = (seconds - TOLERANCE) * 1000 - SchedulerPeriodMs; // Sleep for 1 scheduler period less than requested. + int sleepSlices = (int)(sleepMs / SchedulerPeriodMs); + if (sleepSlices > 0) + Sleep((DWORD)sleepSlices * SchedulerPeriodMs); + QueryPerformanceCounter(&qpc); + } + while (qpc.QuadPart < targetQpc) // Spin for any remaining time. + { + YieldProcessor(); + QueryPerformanceCounter(&qpc); + } +#else +#if defined(HAVE_LIBNX) + svcSleepThread((int64_t)(seconds * 1000000000.0)); +#elif defined(__EMSCRIPTEN__) + emscripten_sleep(seconds * 1000.0); +#else + usleep(seconds * 1000000.0); +#endif +#endif +} + // Return the current time formatted as Minutes:Seconds:Milliseconds // in the form 00:00:000. void GetCurrentTimeFormatted(char formattedTime[13]) { diff --git a/Common/TimeUtil.h b/Common/TimeUtil.h index 578886f3f7..1e89a4a24f 100644 --- a/Common/TimeUtil.h +++ b/Common/TimeUtil.h @@ -1,6 +1,8 @@ #pragma once #include +void TimeInit(); + // Seconds. double time_now_d(); @@ -18,32 +20,24 @@ double time_now_unix_utc(); // Sleep. Does not necessarily have millisecond granularity, especially on Windows. void sleep_ms(int ms); +// Precise sleep. Can consume a little bit of CPU on Windows at least. +void sleep_precise(double seconds); + // Yield. Signals that this thread is busy-waiting but wants to allow other hyperthreads to run. void yield(); void GetCurrentTimeFormatted(char formattedTime[13]); -// Rust-style Instant for clear and easy timing. +// Most accurate timer possible - no extra double conversions. Only for spans. class Instant { public: static Instant Now() { - return Instant(time_now_d()); + return Instant(); } - double ElapsedSeconds() const { - return time_now_d() - instantTime_; - } -private: - explicit Instant(double initTime) : instantTime_(initTime) {} - double instantTime_; -}; - -// Most accurate timer possible - no extra double conversions. Only for spans. -class TimeSpan { -public: - TimeSpan(); double ElapsedSeconds() const; int64_t ElapsedNanos() const; private: + Instant(); uint64_t nativeStart_; #ifndef _WIN32 int64_t nsecs_; diff --git a/Core/MIPS/IR/IRJit.cpp b/Core/MIPS/IR/IRJit.cpp index 474f7be3cb..8de1656d86 100644 --- a/Core/MIPS/IR/IRJit.cpp +++ b/Core/MIPS/IR/IRJit.cpp @@ -285,9 +285,9 @@ void IRJit::RunLoopUntil(u64 globalticks) { instPtr++; #ifdef IR_PROFILING IRBlock *block = blocks_.GetBlock(blocks_.GetBlockNumFromOffset(offset)); - TimeSpan span; + Instant start = Instant::Now(); mips->pc = IRInterpret(mips, instPtr); - int64_t elapsedNanos = span.ElapsedNanos(); + int64_t elapsedNanos = start.ElapsedNanos(); block->profileStats_.executions += 1; block->profileStats_.totalNanos += elapsedNanos; #else diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 48c6ff5835..e115558167 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -56,7 +56,7 @@ #include "Core/HLE/sceKernelModule.h" #include "Core/HLE/sceKernelMemory.h" -static std::thread loadingThread; +static std::thread g_loadingThread; static void UseLargeMem(int memsize) { if (memsize != 1) { @@ -313,7 +313,7 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { // Note: this thread reads the game binary, loads caches, and links HLE while UI spins. // To do something deterministically when the game starts, disabling this thread won't be enough. // Instead: Use Core_ListenLifecycle() or watch coreState. - loadingThread = std::thread([bootpath] { + g_loadingThread = std::thread([bootpath] { SetCurrentThreadName("ExecLoader"); PSP_LoadingLock guard; if (coreState != CORE_POWERUP) @@ -474,7 +474,7 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { PSPLoaders_Shutdown(); // Note: See Load_PSP_ISO for notes about this thread. - loadingThread = std::thread([finalName] { + g_loadingThread = std::thread([finalName] { SetCurrentThreadName("ExecLoader"); PSP_LoadingLock guard; if (coreState != CORE_POWERUP) @@ -500,7 +500,7 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { PSPLoaders_Shutdown(); // Note: See Load_PSP_ISO for notes about this thread. - loadingThread = std::thread([] { + g_loadingThread = std::thread([] { SetCurrentThreadName("ExecLoader"); PSP_LoadingLock guard; if (coreState != CORE_POWERUP) @@ -521,6 +521,6 @@ bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { } void PSPLoaders_Shutdown() { - if (loadingThread.joinable()) - loadingThread.join(); + if (g_loadingThread.joinable()) + g_loadingThread.join(); } diff --git a/Qt/QtMain.cpp b/Qt/QtMain.cpp index caaaf9f31c..9e6e759df1 100644 --- a/Qt/QtMain.cpp +++ b/Qt/QtMain.cpp @@ -45,6 +45,8 @@ #include "Common/Thread/ThreadUtil.h" #include "Common/Data/Encoding/Utf8.h" #include "Common/StringUtils.h" +#include "Common/TimeUtil.h" + #include "Core/Config.h" #include "Core/ConfigValues.h" #include "Core/HW/Camera.h" @@ -799,6 +801,8 @@ Q_DECL_EXPORT #endif int main(int argc, char *argv[]) { + TimeInit(); + for (int i = 1; i < argc; i++) { if (!strcmp(argv[i], "--version")) { printf("%s\n", PPSSPP_GIT_VERSION); diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index b8a19e2b46..716ebde8a9 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -1108,6 +1108,8 @@ int main(int argc, char *argv[]) { } } + TimeInit(); + #ifdef HAVE_LIBNX socketInitializeDefault(); nxlinkStdio(); diff --git a/Windows/main.cpp b/Windows/main.cpp index e70aebfe9e..06ac3fbb65 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -887,6 +887,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin SetCurrentThreadName("Main"); + TimeInit(); + WinMainInit(); #ifndef _DEBUG diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index fe19a10c40..6bf54fa364 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -294,6 +294,8 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *pjvm, void *reserved) { "(Ljava/lang/String;)Ljava/lang/Class;"); RegisterAttachDetach(&Android_AttachThreadToJNI, &Android_DetachThreadFromJNI); + + TimeInit(); return JNI_VERSION_1_6; } diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index f3bcdfa624..11007d5c5d 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -1195,6 +1195,8 @@ static const struct retro_controller_info ports[] = void retro_init(void) { + TimeInit(); + struct retro_log_callback log; if (environ_cb(RETRO_ENVIRONMENT_GET_LOG_INTERFACE, &log)) {