Browse Source

SDL_DelayNS() will attempt to sleep exactly the requested amount of time

This provides a highly accurate sleep function for your application, although you are still subject to being switched out occasionally.

Fixes https://github.com/libsdl-org/SDL/issues/10210
Sam Lantinga 9 months ago
parent
commit
033df70d4c

+ 2 - 2
include/SDL3/SDL_timer.h

@@ -115,8 +115,8 @@ extern SDL_DECLSPEC void SDLCALL SDL_Delay(Uint32 ms);
  * Wait a specified number of nanoseconds before returning.
  *
  * This function waits a specified number of nanoseconds before returning. It
- * waits at least the specified time, but possibly longer due to OS
- * scheduling.
+ * will attempt to wait as close to the requested time as possible, busy waiting
+ * if necessary, but could return later due to OS scheduling.
  *
  * \param ns the number of nanoseconds to delay.
  *

+ 22 - 1
src/timer/SDL_timer.c

@@ -643,5 +643,26 @@ Uint64 SDL_GetTicks(void)
 
 void SDL_Delay(Uint32 ms)
 {
-    SDL_DelayNS(SDL_MS_TO_NS(ms));
+    SDL_SYS_DelayNS(SDL_MS_TO_NS(ms));
+}
+
+void SDL_DelayNS(Uint64 ns)
+{
+    Uint64 current_value = SDL_GetTicksNS();
+    Uint64 target_value = current_value + ns;
+
+    // Sleep for a short number of cycles
+    // We'll use 1 ms as a scheduling timeslice, it's a good value for modern operating systems
+    const int SCHEDULING_TIMESLICE_NS = 1 * SDL_NS_PER_MS;
+    while (current_value < target_value) {
+        Uint64 remaining_ns = (target_value - current_value);
+        if (remaining_ns > (SCHEDULING_TIMESLICE_NS + SDL_NS_PER_US)) {
+            // Sleep for a short time, less than the scheduling timeslice
+            SDL_SYS_DelayNS(SCHEDULING_TIMESLICE_NS - SDL_NS_PER_US);
+        } else {
+            // Spin for any remaining time
+            SDL_CPUPauseInstruction();
+        }
+        current_value = SDL_GetTicksNS();
+    }
 }

+ 2 - 0
src/timer/SDL_timer_c.h

@@ -34,4 +34,6 @@ extern void SDL_QuitTicks(void);
 extern int SDL_InitTimers(void);
 extern void SDL_QuitTimers(void);
 
+extern void SDL_SYS_DelayNS(Uint64 ns);
+
 #endif /* SDL_timer_c_h_ */

+ 1 - 1
src/timer/haiku/SDL_systimer.c

@@ -35,7 +35,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return SDL_US_PER_SECOND;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     snooze((bigtime_t)SDL_NS_TO_US(ns));
 }

+ 1 - 1
src/timer/n3ds/SDL_systimer.c

@@ -35,7 +35,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return SYSCLOCK_ARM11;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     svcSleepThread(ns);
 }

+ 1 - 1
src/timer/ngage/SDL_systimer.cpp

@@ -43,7 +43,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return SDL_US_PER_SECOND;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     const Uint64 max_delay = 0x7fffffffLL * SDL_NS_PER_US;
     if (ns > max_delay) {

+ 1 - 1
src/timer/ps2/SDL_systimer.c

@@ -39,7 +39,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return kBUSCLK;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     struct timespec tv;
     tv.tv_sec = (ns / SDL_NS_PER_SECOND);

+ 1 - 1
src/timer/psp/SDL_systimer.c

@@ -42,7 +42,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return sceRtcGetTickResolution();
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     const Uint64 max_delay = 0xffffffffLL * SDL_NS_PER_US;
     if (ns > max_delay) {

+ 1 - 1
src/timer/unix/SDL_systimer.c

@@ -135,7 +135,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return SDL_US_PER_SECOND;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     int was_error;
 

+ 1 - 1
src/timer/vita/SDL_systimer.c

@@ -39,7 +39,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return SDL_US_PER_SECOND;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     const Uint64 max_delay = 0xffffffffLL * SDL_NS_PER_US;
     if (ns > max_delay) {

+ 1 - 1
src/timer/windows/SDL_systimer.c

@@ -66,7 +66,7 @@ Uint64 SDL_GetPerformanceFrequency(void)
     return (Uint64)frequency.QuadPart;
 }
 
-void SDL_DelayNS(Uint64 ns)
+void SDL_SYS_DelayNS(Uint64 ns)
 {
     /* CREATE_WAITABLE_TIMER_HIGH_RESOLUTION flag was added in Windows 10 version 1803.
      *

+ 28 - 0
test/testtimer.c

@@ -186,6 +186,34 @@ int main(int argc, char *argv[])
     /* Wait for the results to be seen */
     SDL_Delay(1 * 1000);
 
+    /* Check accuracy of precise delay */
+    {
+        Uint64 desired_delay = SDL_NS_PER_SECOND / 60;
+        Uint64 actual_delay;
+        Uint64 total_overslept = 0;
+
+        start = SDL_GetTicksNS();
+        SDL_DelayNS(1);
+        now = SDL_GetTicksNS();
+        actual_delay = (now - start);
+        SDL_Log("Minimum precise delay: %" SDL_PRIu64 " ns\n", actual_delay);
+
+        SDL_Log("Timing 100 frames at 60 FPS\n");
+        for (i = 0; i < 100; ++i) {
+            start = SDL_GetTicksNS();
+            SDL_DelayNS(desired_delay);
+            now = SDL_GetTicksNS();
+            actual_delay = (now - start);
+            if (actual_delay > desired_delay) {
+                total_overslept += (actual_delay - desired_delay);
+            }
+        }
+        SDL_Log("Overslept %.2f ms\n", (double)total_overslept / SDL_NS_PER_MS);
+    }
+
+    /* Wait for the results to be seen */
+    SDL_Delay(1 * 1000);
+
     /* Test multiple timers */
     SDL_Log("Testing multiple timers...\n");
     t1 = SDL_AddTimer(100, callback, (void *)1);