timer: Added SDL_GetTicks64(), for a timer that doesn't wrap every ~49 days. Note that this removes the timeGetTime() fallback on Windows; it is a 32-bit counter and SDL2 should never choose to use it, as it only is needed if QueryPerformanceCounter() isn't available, and QPC is _always_ available on Windows XP and later. OS/2 has a similar situation, but since it isn't clear to me that similar promises can be made about DosTmrQueryTime() even in modern times, I decided to leave the fallback in, with some heroic measures added to try to provide a true 64-bit tick counter despite the 49-day wraparound. That approach can migrate to Windows too, if we discover some truly broken install that doesn't have QPC and still depends on timeGetTime(). Fixes #4870.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452 453 454 455 456 457 458 459 460 461 462 463 464 465 466 467 468 469 470 471 472 473 474 475 476 477 478 479 480 481 482 483 484 485 486 487 488 489 490 491
diff --git a/include/SDL_timer.h b/include/SDL_timer.h
index dca71f3..ed6be78 100644
--- a/include/SDL_timer.h
+++ b/include/SDL_timer.h
@@ -42,6 +42,10 @@ extern "C" {
*
* This value wraps if the program runs for more than ~49 days.
*
+ * \deprecated This function is deprecated as of SDL 2.0.18; use
+ * SDL_GetTicks64() instead, where the value doesn't wrap
+ * every ~49 days.
+ *
* \returns an unsigned 32-bit value representing the number of milliseconds
* since the SDL library initialized.
*
@@ -49,15 +53,42 @@ extern "C" {
*
* \sa SDL_TICKS_PASSED
*/
-extern DECLSPEC Uint32 SDLCALL SDL_GetTicks(void);
+extern SDL_DEPRECATED DECLSPEC Uint32 SDLCALL SDL_GetTicks(void);
/**
- * Compare SDL ticks values, and return true if `A` has passed `B`.
+ * Get the number of milliseconds since SDL library initialization.
+ *
+ * Note that you should not use the SDL_TICKS_PASSED macro with values
+ * returned by this function, as that macro does clever math to compensate
+ * for the 32-bit overflow every ~49 days. 64-bit values can just be safely
+ * compared directly.
+ *
+ * For example, if you want to wait 100 ms, you could do this:
+ *
+ * ```c
+ * const Uint64 timeout = SDL_GetTicks64() + 100;
+ * while (SDL_GetTicks64() < timeout) {
+ * // ... do work until timeout has elapsed
+ * }
+ * ```
+ *
+ * \returns an unsigned 64-bit value representing the number of milliseconds
+ * since the SDL library initialized.
+ */
+extern DECLSPEC Uint64 SDLCALL SDL_GetTicks64(void);
+
+/**
+ * Compare 32-bit SDL ticks values, and return true if `A` has passed `B`.
+ *
+ * This should be used with results from SDL_GetTicks(), as this macro
+ * attempts to deal with the 32-bit counter wrapping back to zero every ~49
+ * days, but should _not_ be used with SDL_GetTicks64(), which does not have
+ * that problem.
*
* For example, if you want to wait 100 ms, you could do this:
*
- * ```c++
- * Uint32 timeout = SDL_GetTicks() + 100;
+ * ```c
+ * const Uint32 timeout = SDL_GetTicks() + 100;
* while (!SDL_TICKS_PASSED(SDL_GetTicks(), timeout)) {
* // ... do work until timeout has elapsed
* }
diff --git a/src/dynapi/SDL_dynapi_overrides.h b/src/dynapi/SDL_dynapi_overrides.h
index 005f7a7..761d20f 100644
--- a/src/dynapi/SDL_dynapi_overrides.h
+++ b/src/dynapi/SDL_dynapi_overrides.h
@@ -823,3 +823,4 @@
#define SDL_asprintf SDL_asprintf_REAL
#define SDL_vasprintf SDL_vasprintf_REAL
#define SDL_GetWindowICCProfile SDL_GetWindowICCProfile_REAL
+#define SDL_GetTicks64 SDL_GetTicks64_REAL
diff --git a/src/dynapi/SDL_dynapi_procs.h b/src/dynapi/SDL_dynapi_procs.h
index f30eb41..bafb80b 100644
--- a/src/dynapi/SDL_dynapi_procs.h
+++ b/src/dynapi/SDL_dynapi_procs.h
@@ -890,3 +890,4 @@ SDL_DYNAPI_PROC(int,SDL_asprintf,(char **a, SDL_PRINTF_FORMAT_STRING const char
#endif
SDL_DYNAPI_PROC(int,SDL_vasprintf,(char **a, const char *b, va_list c),(a,b,c),return)
SDL_DYNAPI_PROC(void*,SDL_GetWindowICCProfile,(SDL_Window *a, size_t *b),(a,b),return)
+SDL_DYNAPI_PROC(Uint64,SDL_GetTicks64,(void),(),return)
diff --git a/src/timer/SDL_timer.c b/src/timer/SDL_timer.c
index 3ef7b0a..559fdf5 100644
--- a/src/timer/SDL_timer.c
+++ b/src/timer/SDL_timer.c
@@ -370,4 +370,14 @@ SDL_RemoveTimer(SDL_TimerID id)
return canceled;
}
+/* This is a legacy support function; SDL_GetTicks() returns a Uint32,
+ which wraps back to zero every ~49 days. The newer SDL_GetTicks64()
+ doesn't have this problem, so we just wrap that function and clamp to
+ the low 32-bits for binary compatibility. */
+Uint32
+SDL_GetTicks(void)
+{
+ return (Uint32) (SDL_GetTicks64() & 0xFFFFFFFF);
+}
+
/* vi: set ts=4 sw=4 expandtab: */
diff --git a/src/timer/dummy/SDL_systimer.c b/src/timer/dummy/SDL_systimer.c
index 4c759a0..a3d31ea 100644
--- a/src/timer/dummy/SDL_systimer.c
+++ b/src/timer/dummy/SDL_systimer.c
@@ -41,8 +41,8 @@ SDL_TicksQuit(void)
ticks_started = SDL_FALSE;
}
-Uint32
-SDL_GetTicks(void)
+Uint64
+SDL_GetTicks64(void)
{
if (!ticks_started) {
SDL_TicksInit();
diff --git a/src/timer/haiku/SDL_systimer.c b/src/timer/haiku/SDL_systimer.c
index 6a07862..38898a9 100644
--- a/src/timer/haiku/SDL_systimer.c
+++ b/src/timer/haiku/SDL_systimer.c
@@ -47,14 +47,14 @@ SDL_TicksQuit(void)
ticks_started = SDL_FALSE;
}
-Uint32
-SDL_GetTicks(void)
+Uint64
+SDL_GetTicks64(void)
{
if (!ticks_started) {
SDL_TicksInit();
}
- return ((system_time() - start) / 1000);
+ return (Uint64) ((system_time() - start) / 1000);
}
Uint64
diff --git a/src/timer/os2/SDL_systimer.c b/src/timer/os2/SDL_systimer.c
index 0ea2218..ae71b81 100644
--- a/src/timer/os2/SDL_systimer.c
+++ b/src/timer/os2/SDL_systimer.c
@@ -40,25 +40,31 @@
typedef unsigned long long ULLONG;
static ULONG ulTmrFreq = 0;
-static ULLONG ullTmrStart;
+static ULLONG ullTmrStart = 0;
+
+/* 32-bit counter fallback...not used if DosTmrQuery* is functioning. */
+static ULONG ulPrevTmr = 0;
+static Uint64 ui64TmrStartOffset = 0; /* Used if 32-bit counter overflows. */
void
SDL_TicksInit(void)
{
- ULONG ulRC;
-
- ulRC = DosTmrQueryFreq(&ulTmrFreq);
+ ULONG ulTmrStart; /* for 32-bit fallback. */
+ ULONG ulRC = DosTmrQueryFreq(&ulTmrFreq);
if (ulRC != NO_ERROR) {
debug_os2("DosTmrQueryFreq() failed, rc = %u", ulRC);
} else {
ulRC = DosTmrQueryTime((PQWORD)&ullTmrStart);
- if (ulRC == NO_ERROR)
+ if (ulRC == NO_ERROR) {
return;
+ }
debug_os2("DosTmrQueryTime() failed, rc = %u", ulRC);
}
ulTmrFreq = 0; /* Error - use DosQuerySysInfo() for timer. */
- DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, (PULONG)&ullTmrStart, sizeof(ULONG));
+ DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &ulTmrStart, sizeof (ULONG));
+ ullTmrStart = (ULLONG) ulTmrStart;
+ ulPrevTmr = ulTmrStart;
}
void
@@ -66,24 +72,32 @@ SDL_TicksQuit(void)
{
}
-Uint32
-SDL_GetTicks(void)
+Uint64
+SDL_GetTicks64(void)
{
- ULONG ulResult;
- ULLONG ullTmrNow;
+ Uint64 ui64Result;
+ ULLONG ullTmrNow;
- if (ulTmrFreq == 0) /* Was not initialized. */
+ if (ulTmrFreq == 0) { /* Was not initialized. */
SDL_TicksInit();
+ }
if (ulTmrFreq != 0) {
DosTmrQueryTime((PQWORD)&ullTmrNow);
- ulResult = (ullTmrNow - ullTmrStart) * 1000 / ulTmrFreq;
+ ui64Result = (ullTmrNow - ullTmrStart) * 1000 / ulTmrFreq;
} else {
- DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, (PULONG)&ullTmrNow, sizeof(ULONG));
- ulResult = (ULONG)ullTmrNow - (ULONG)ullTmrStart;
+ ULONG ulTmrNow;
+ DosQuerySysInfo(QSV_MS_COUNT, QSV_MS_COUNT, &ulTmrNow, sizeof (ULONG));
+ if ( ((ULLONG) ulTmrNow) < ulPrevTmr ) { /* have we overflowed the 32-bit counter since last check? */
+ /* Note that this is incorrect if you went more than ~98 days between calls to SDL_GetTicks64(). */
+ /* One could query QSV_TIME_HIGH and QSV_TIME_LOW here to verify, but it's probably not worth it. */
+ ui64TmrStartOffset += 0xFFFFFFFF;
+ }
+ ui64Result = (((Uint64) ulTmrNow) - ullTmrStart) + ui64TmrStartOffset;
+ ulPrevTmr = ulTmrNow;
}
- return ulResult;
+ return ui64Result;
}
Uint64
@@ -92,7 +106,7 @@ SDL_GetPerformanceCounter(void)
QWORD qwTmrNow;
if (ulTmrFreq == 0 || (DosTmrQueryTime(&qwTmrNow) != NO_ERROR))
- return SDL_GetTicks();
+ return SDL_GetTicks64();
return *((Uint64 *)&qwTmrNow);
}
diff --git a/src/timer/psp/SDL_systimer.c b/src/timer/psp/SDL_systimer.c
index 2829d58..15d29e4 100644
--- a/src/timer/psp/SDL_systimer.c
+++ b/src/timer/psp/SDL_systimer.c
@@ -51,24 +51,23 @@ SDL_TicksQuit(void)
ticks_started = SDL_FALSE;
}
-Uint32 SDL_GetTicks(void)
+Uint64
+SDL_GetTicks64(void)
{
+ struct timeval now;
+
if (!ticks_started) {
SDL_TicksInit();
}
- struct timeval now;
- Uint32 ticks;
-
gettimeofday(&now, NULL);
- ticks=(now.tv_sec-start.tv_sec)*1000+(now.tv_usec-start.tv_usec)/1000;
- return(ticks);
+ return (((Uint64)(now.tv_sec-start.tv_sec)) * 1000) + (((Uint64) (now.tv_usec-start.tv_usec)) / 1000);
}
Uint64
SDL_GetPerformanceCounter(void)
{
- return SDL_GetTicks();
+ return SDL_GetTicks64();
}
Uint64
diff --git a/src/timer/unix/SDL_systimer.c b/src/timer/unix/SDL_systimer.c
index 05db3a9..067d207 100644
--- a/src/timer/unix/SDL_systimer.c
+++ b/src/timer/unix/SDL_systimer.c
@@ -104,10 +104,11 @@ SDL_TicksQuit(void)
ticks_started = SDL_FALSE;
}
-Uint32
+Uint64
SDL_GetTicks(void)
{
- Uint32 ticks;
+ struct timeval now;
+
if (!ticks_started) {
SDL_TicksInit();
}
@@ -116,21 +117,18 @@ SDL_GetTicks(void)
#if HAVE_CLOCK_GETTIME
struct timespec now;
clock_gettime(SDL_MONOTONIC_CLOCK, &now);
- ticks = (Uint32)((now.tv_sec - start_ts.tv_sec) * 1000 + (now.tv_nsec - start_ts.tv_nsec) / 1000000);
+ ticks = (((Uint64) (now.tv_sec - start_ts.tv_sec)) * 1000) + (((Uint64) (now.tv_nsec - start_ts.tv_nsec)) / 1000000);
#elif defined(__APPLE__)
- uint64_t now = mach_absolute_time();
- ticks = (Uint32)((((now - start_mach) * mach_base_info.numer) / mach_base_info.denom) / 1000000);
+ const uint64_t now = mach_absolute_time();
+ return (Uint64) ((((now - start_mach) * mach_base_info.numer) / mach_base_info.denom) / 1000000);
#else
SDL_assert(SDL_FALSE);
- ticks = 0;
+ return 0;
#endif
- } else {
- struct timeval now;
-
- gettimeofday(&now, NULL);
- ticks = (Uint32)((now.tv_sec - start_tv.tv_sec) * 1000 + (now.tv_usec - start_tv.tv_usec) / 1000);
}
- return (ticks);
+
+ gettimeofday(&now, NULL);
+ return (((Uint64) (now.tv_sec - start_tv.tv_sec)) * 1000) + (((Uint64) (now.tv_usec - start_tv.tv_usec)) / 1000);
}
Uint64
@@ -203,7 +201,7 @@ SDL_Delay(Uint32 ms)
struct timespec elapsed, tv;
#else
struct timeval tv;
- Uint32 then, now, elapsed;
+ Uint64 then, now, elapsed;
#endif
/* Set the timeout interval */
@@ -211,7 +209,7 @@ SDL_Delay(Uint32 ms)
elapsed.tv_sec = ms / 1000;
elapsed.tv_nsec = (ms % 1000) * 1000000;
#else
- then = SDL_GetTicks();
+ then = SDL_GetTicks64();
#endif
do {
errno = 0;
@@ -222,13 +220,13 @@ SDL_Delay(Uint32 ms)
was_error = nanosleep(&tv, &elapsed);
#else
/* Calculate the time interval left (in case of interrupt) */
- now = SDL_GetTicks();
+ now = SDL_GetTicks64();
elapsed = (now - then);
then = now;
- if (elapsed >= ms) {
+ if (elapsed >= ((Uint64) ms)) {
break;
}
- ms -= elapsed;
+ ms -= (Uint32) elapsed;
tv.tv_sec = ms / 1000;
tv.tv_usec = (ms % 1000) * 1000;
diff --git a/src/timer/vita/SDL_systimer.c b/src/timer/vita/SDL_systimer.c
index b7cbb22..5ae02a0 100644
--- a/src/timer/vita/SDL_systimer.c
+++ b/src/timer/vita/SDL_systimer.c
@@ -51,18 +51,17 @@ SDL_TicksQuit(void)
ticks_started = SDL_FALSE;
}
-Uint32 SDL_GetTicks(void)
+Uint64
+SDL_GetTicks64(void)
{
uint64_t now;
- Uint32 ticks;
if (!ticks_started) {
SDL_TicksInit();
}
now = sceKernelGetProcessTimeWide();
- ticks = (now - start)/1000;
- return (ticks);
+ return (Uint64) ((now - start) / 1000);
}
Uint64
diff --git a/src/timer/windows/SDL_systimer.c b/src/timer/windows/SDL_systimer.c
index 12c3736..b3e79d6 100644
--- a/src/timer/windows/SDL_systimer.c
+++ b/src/timer/windows/SDL_systimer.c
@@ -33,12 +33,10 @@
static DWORD start = 0;
static BOOL ticks_started = FALSE;
-/* Store if a high-resolution performance counter exists on the system */
-static BOOL hires_timer_available;
/* The first high-resolution ticks value of the application */
-static LARGE_INTEGER hires_start_ticks;
+static LARGE_INTEGER start_ticks;
/* The number of ticks per second of the high-resolution performance counter */
-static LARGE_INTEGER hires_ticks_per_second;
+static LARGE_INTEGER ticks_per_second;
static void
SDL_SetSystemTimerResolution(const UINT uPeriod)
@@ -79,6 +77,8 @@ SDL_TimerResolutionChanged(void *userdata, const char *name, const char *oldValu
void
SDL_TicksInit(void)
{
+ BOOL rc;
+
if (ticks_started) {
return;
}
@@ -90,18 +90,12 @@ SDL_TicksInit(void)
SDL_TimerResolutionChanged, NULL);
/* Set first ticks value */
- /* QueryPerformanceCounter has had problems in the past, but lots of games
- use it, so we'll rely on it here.
+ /* QueryPerformanceCounter allegedly is always available and reliable as of WinXP,
+ so we'll rely on it here.
*/
- if (QueryPerformanceFrequency(&hires_ticks_per_second) == TRUE) {
- hires_timer_available = TRUE;
- QueryPerformanceCounter(&hires_start_ticks);
- } else {
- hires_timer_available = FALSE;
-#ifndef __WINRT__
- start = timeGetTime();
-#endif /* __WINRT__ */
- }
+ rc = QueryPerformanceFrequency(&ticks_per_second);
+ SDL_assert(rc != 0); /* this should _never_ fail if you're on XP or later. */
+ QueryPerformanceCounter(&start_ticks);
}
void
@@ -116,53 +110,37 @@ SDL_TicksQuit(void)
ticks_started = SDL_FALSE;
}
-Uint32
-SDL_GetTicks(void)
+Uint64
+SDL_GetTicks64(void)
{
- DWORD now = 0;
- LARGE_INTEGER hires_now;
+ LARGE_INTEGER now;
+ BOOL rc;
if (!ticks_started) {
SDL_TicksInit();
}
- if (hires_timer_available) {
- QueryPerformanceCounter(&hires_now);
-
- hires_now.QuadPart -= hires_start_ticks.QuadPart;
- hires_now.QuadPart *= 1000;
- hires_now.QuadPart /= hires_ticks_per_second.QuadPart;
-
- return (DWORD) hires_now.QuadPart;
- } else {
-#ifndef __WINRT__
- now = timeGetTime();
-#endif /* __WINRT__ */
- }
-
- return (now - start);
+ rc = QueryPerformanceCounter(&now);
+ SDL_assert(rc != 0); /* this should _never_ fail if you're on XP or later. */
+ return (Uint64) (((now.QuadPart - start_ticks.QuadPart) * 1000) / ticks_per_second.QuadPart);
}
Uint64
SDL_GetPerformanceCounter(void)
{
LARGE_INTEGER counter;
-
- if (!QueryPerformanceCounter(&counter)) {
- return SDL_GetTicks();
- }
- return counter.QuadPart;
+ const BOOL rc = QueryPerformanceCounter(&counter);
+ SDL_assert(rc != 0); /* this should _never_ fail if you're on XP or later. */
+ return (Uint64) counter.QuadPart;
}
Uint64
SDL_GetPerformanceFrequency(void)
{
LARGE_INTEGER frequency;
-
- if (!QueryPerformanceFrequency(&frequency)) {
- return 1000;
- }
- return frequency.QuadPart;
+ const BOOL rc = QueryPerformanceFrequency(&frequency);
+ SDL_assert(rc != 0); /* this should _never_ fail if you're on XP or later. */
+ return (Uint64) frequency.QuadPart;
}
void