Commit 99c9727dc0bbc9be96f9e023d8b6e7e0fe120b11

Ryan C. Gordon 2021-10-23T15:00:31

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