Files
ppsspp/Common/TimeUtil.cpp
T
Henrik RydgårdandClaude Opus 5 c6965cebdb Common: Establish the time origin in TimeInit(), and call it on iOS
time_now_d() lazily initialized g_startTime on its first call, which is a data
race between threads, and left from_time_raw() subtracting zero (returning
seconds since boot) if it happened to run before any time_now_d(). Set it in
TimeInit() instead, matching what the Windows path already does with
frequencyMult.

That only works if TimeInit() is actually called, and iOS was the one entry
point that never did - Windows, UWP, SDL, Qt, Android, libretro, headless and
the unit tests all do it as the first thing in main(). Added it there too.

Timing risk: anything calling time_now_d() before TimeInit() now gets seconds
since boot rather than a value near zero. All entry points call TimeInit()
first, so this only bites code running from a static initializer; deltas
between two timestamps are unaffected either way.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JZk5y4Fzw811WJoNWZb8Sc
2026-08-07 12:47:55 +02:00

341 lines
9.1 KiB
C++

#include "ppsspp_config.h"
#include <cstdio>
#include <cstdint>
#include "Common/TimeUtil.h"
#include "Common/Data/Random/Rng.h"
#include "Common/Log.h"
#ifdef HAVE_LIBNX
#include <switch.h>
#endif // HAVE_LIBNX
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif // __EMSCRIPTEN__
#ifdef _WIN32
#include "CommonWindows.h"
#include <mmsystem.h>
#include <sys/timeb.h>
#else
#include <sys/time.h>
#include <unistd.h>
#endif
// for _mm_pause
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
#include <emmintrin.h>
#endif
#include <ctime>
// TODO: https://github.com/floooh/sokol/blob/9a6237fcdf213e6da48e4f9201f144bcb2dcb46f/sokol_time.h#L229-L248
constexpr double nanos = 1000000000.0;
#if PPSSPP_PLATFORM(WINDOWS)
constexpr int64_t UNIX_TIME_START = 0x019DB1DED53E8000; //January 1, 1970 (start of Unix epoch) in "ticks"
constexpr double TICKS_PER_SECOND = 10000000; //a tick is 100ns
static LARGE_INTEGER frequency;
static double frequencyMult;
static LARGE_INTEGER startTime;
static LARGE_INTEGER startFileTime;
HANDLE Timer;
int SchedulerPeriodMs = 10;
INT64 QpcPerSecond;
void TimeInit() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft); //returns ticks in UTC
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
startFileTime.LowPart = ft.dwLowDateTime;
startFileTime.HighPart = ft.dwHighDateTime;
QueryPerformanceFrequency(&frequency);
QueryPerformanceCounter(&startTime);
QpcPerSecond = frequency.QuadPart;
frequencyMult = 1.0 / frequency.QuadPart;
// The timer will be automatically deleted on process destruction. Don't need to CloseHandle.
Timer = CreateWaitableTimerExW(NULL, NULL, CREATE_WAITABLE_TIMER_HIGH_RESOLUTION, TIMER_ALL_ACCESS);
// TODO: We probably don't need this anymore if we are using the high res waitable timers?
#if !PPSSPP_PLATFORM(UWP)
TIMECAPS caps;
timeGetDevCaps(&caps, sizeof caps);
timeBeginPeriod(caps.wPeriodMin);
SchedulerPeriodMs = (int)caps.wPeriodMin;
#endif
}
void TimeShutdown() {
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
timeEndPeriod(1);
#endif
}
double time_now_d() {
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
return static_cast<double>(time.QuadPart - startTime.QuadPart) * frequencyMult;
}
// Fake, but usable in a pinch. Don't, though.
uint64_t time_now_raw() {
return (uint64_t)(time_now_d() * nanos);
}
double from_time_raw(uint64_t raw_time) {
if (raw_time == 0) {
return 0.0; // invalid time
}
return (double)raw_time * (1.0 / nanos);
}
double from_time_raw_relative(uint64_t raw_time) {
return from_time_raw(raw_time);
}
double time_now_unix_utc() {
FILETIME ft;
GetSystemTimeAsFileTime(&ft); //returns ticks in UTC
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
LARGE_INTEGER li;
li.LowPart = ft.dwLowDateTime;
li.HighPart = ft.dwHighDateTime;
//Convert ticks since 1/1/1970 into seconds
return (double)(li.QuadPart - UNIX_TIME_START) / TICKS_PER_SECOND;
}
// Adds the timestamp to startTime, and converts to seconds from the unix epoch.
double time_to_unix_utc(double timestamp) {
// Copy the low and high parts of FILETIME into a LARGE_INTEGER
LARGE_INTEGER li;
li.LowPart = startFileTime.LowPart;
li.HighPart = startFileTime.HighPart;
return (double)(li.QuadPart - UNIX_TIME_START + static_cast<int64_t>(timestamp * TICKS_PER_SECOND)) / TICKS_PER_SECOND;
}
void yield() {
YieldProcessor();
}
Instant::Instant() {
_dbg_assert_(frequencyMult != 0.0);
QueryPerformanceCounter(reinterpret_cast<LARGE_INTEGER *>(&nativeStart_));
}
double Instant::ElapsedSeconds() const {
LARGE_INTEGER time;
QueryPerformanceCounter(&time);
double elapsed = static_cast<double>(time.QuadPart - nativeStart_);
return elapsed * frequencyMult;
}
int64_t Instant::ElapsedNanos() const {
return (int64_t)(ElapsedSeconds() * 1000000000.0);
}
#else // Everything that isn't Windows. They all have POSIX clock_gettime.
// The only intended use is to match the timings in VK_GOOGLE_display_timing
uint64_t time_now_raw() {
struct timespec tp;
clock_gettime(CLOCK_MONOTONIC, &tp);
return tp.tv_sec * 1000000000ULL + tp.tv_nsec;
}
// Subtracted from the raw time so that the doubles we hand out stay small and precise.
static uint64_t g_startTime;
void TimeInit() {
g_startTime = time_now_raw();
}
void TimeShutdown() {
// Nothing to do.
}
double from_time_raw(uint64_t raw_time) {
return (double)(raw_time - g_startTime) * (1.0 / nanos);
}
double time_now_d() {
return from_time_raw(time_now_raw());
}
double from_time_raw_relative(uint64_t raw_time) {
return (double)raw_time * (1.0 / nanos);
}
double time_now_unix_utc() {
struct timespec tp;
clock_gettime(CLOCK_REALTIME, &tp);
return (double)tp.tv_sec + (double)tp.tv_nsec / 1000000000.0;
}
// Converts a timestamp from time_now_d() to seconds since the unix epoch.
// Both clocks tick at the same rate, so we can just measure the offset between them right now.
double time_to_unix_utc(double timestamp) {
return time_now_unix_utc() - time_now_d() + timestamp;
}
void yield() {
#if PPSSPP_ARCH(X86) || PPSSPP_ARCH(AMD64)
_mm_pause();
#elif PPSSPP_ARCH(ARM64)
// Took this out for now. See issue #17877
// __builtin_arm_isb(15);
#endif
}
Instant::Instant() {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
nativeStart_ = ts.tv_sec;
nsecs_ = ts.tv_nsec;
}
int64_t Instant::ElapsedNanos() const {
struct timespec ts;
clock_gettime(CLOCK_MONOTONIC, &ts);
int64_t secs = ts.tv_sec - nativeStart_;
int64_t nsecs = ts.tv_nsec - nsecs_;
if (nsecs < 0) {
secs--;
nsecs += 1000000000;
}
return secs * 1000000000ULL + nsecs;
}
double Instant::ElapsedSeconds() const {
return (double)ElapsedNanos() * (1.0 / nanos);
}
#endif
#define SLEEP_LOG_ENABLED 0
void sleep_ms(int ms, const char *reason) {
if (ms <= 0) {
return;
}
#if SLEEP_LOG_ENABLED
INFO_LOG(Log::System, "Sleep %d ms: %s", ms, reason);
#endif
#ifdef _WIN32
Sleep(ms);
#elif defined(HAVE_LIBNX)
svcSleepThread(ms * 1000000);
#elif defined(__EMSCRIPTEN__)
emscripten_sleep(ms);
#else
usleep(ms * 1000);
#endif
}
void sleep_us(int us, const char *reason) {
if (us <= 0) {
return;
}
#if SLEEP_LOG_ENABLED
INFO_LOG(Log::System, "Sleep %d us: %s", us, reason);
#endif
#ifdef _WIN32
Sleep(us / 1000);
#elif defined(HAVE_LIBNX)
svcSleepThread(us * 1000);
#elif defined(__EMSCRIPTEN__)
emscripten_sleep(us / 1000);
#else
usleep(us);
#endif
}
// This can be a little more expensive in some circumstances, so only use when necessary.
void sleep_precise(double seconds, const char *reason) {
if (seconds <= 0.0) {
return;
}
#if SLEEP_LOG_ENABLED
INFO_LOG(Log::System, "Sleep precise %f s: %s", seconds, reason);
#endif
#ifdef _WIN32
// 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/
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);
// Note: SetWaitableTimerEx is not available on Vista.
if (!SetWaitableTimer(Timer, &due, 0, NULL, NULL, FALSE)) {
_dbg_assert_(false);
break;
}
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);
}
// On other platforms, we just do a conversion with more input precision than in sleep_ms which is restricted to whole milliseconds.
#elif defined(HAVE_LIBNX)
svcSleepThread((int64_t)(seconds * 1000000000.0));
#elif defined(__EMSCRIPTEN__)
emscripten_sleep(seconds * 1000.0);
#else
usleep(seconds * 1000000.0);
#endif
}
// Return the current time formatted as Minutes:Seconds:Milliseconds
// in the form 00:00:000.
void GetCurrentTimeFormatted(char formattedTime[13]) {
#ifdef _WIN32
SYSTEMTIME st;
GetLocalTime(&st);
snprintf(formattedTime, 13, "%02d:%02d:%03d", st.wMinute, st.wSecond, st.wMilliseconds);
#else
struct timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
struct tm tm;
localtime_r(&ts.tv_sec, &tm);
snprintf(formattedTime, 13, "%02d:%02d:%03d", tm.tm_min, tm.tm_sec, (int)(ts.tv_nsec / 1000000));
#endif
}
// We don't even bother synchronizing this, it's fine if threads stomp a bit.
static GMRng g_sleepRandom;
void sleep_random(double minSeconds, double maxSeconds, const char *reason) {
const double waitSeconds = minSeconds + (maxSeconds - minSeconds) * g_sleepRandom.F();
sleep_precise(waitSeconds, reason);
}