From 033df70d4c0f1e3acdbfe246a9133e2d1a721178 Mon Sep 17 00:00:00 2001
From: Sam Lantinga <[EMAIL REDACTED]>
Date: Mon, 15 Jul 2024 18:05:31 -0700
Subject: [PATCH] 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
---
include/SDL3/SDL_timer.h | 4 ++--
src/timer/SDL_timer.c | 23 ++++++++++++++++++++++-
src/timer/SDL_timer_c.h | 2 ++
src/timer/haiku/SDL_systimer.c | 2 +-
src/timer/n3ds/SDL_systimer.c | 2 +-
src/timer/ngage/SDL_systimer.cpp | 2 +-
src/timer/ps2/SDL_systimer.c | 2 +-
src/timer/psp/SDL_systimer.c | 2 +-
src/timer/unix/SDL_systimer.c | 2 +-
src/timer/vita/SDL_systimer.c | 2 +-
src/timer/windows/SDL_systimer.c | 2 +-
test/testtimer.c | 28 ++++++++++++++++++++++++++++
12 files changed, 62 insertions(+), 11 deletions(-)
diff --git a/include/SDL3/SDL_timer.h b/include/SDL3/SDL_timer.h
index c127da1fd1a26..fa40a6f82ca62 100644
--- a/include/SDL3/SDL_timer.h
+++ b/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.
*
diff --git a/src/timer/SDL_timer.c b/src/timer/SDL_timer.c
index ca98271d66de1..4c5bd018c614a 100644
--- a/src/timer/SDL_timer.c
+++ b/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();
+ }
}
diff --git a/src/timer/SDL_timer_c.h b/src/timer/SDL_timer_c.h
index 161fb91434c71..9c202a368908c 100644
--- a/src/timer/SDL_timer_c.h
+++ b/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_ */
diff --git a/src/timer/haiku/SDL_systimer.c b/src/timer/haiku/SDL_systimer.c
index 726e4150180fc..8f844b6d30a5e 100644
--- a/src/timer/haiku/SDL_systimer.c
+++ b/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));
}
diff --git a/src/timer/n3ds/SDL_systimer.c b/src/timer/n3ds/SDL_systimer.c
index 99389a4c7626d..e90b99dc7cd75 100644
--- a/src/timer/n3ds/SDL_systimer.c
+++ b/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);
}
diff --git a/src/timer/ngage/SDL_systimer.cpp b/src/timer/ngage/SDL_systimer.cpp
index f7f11ea3d3202..ce0082a40de88 100644
--- a/src/timer/ngage/SDL_systimer.cpp
+++ b/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) {
diff --git a/src/timer/ps2/SDL_systimer.c b/src/timer/ps2/SDL_systimer.c
index 7070e17af49ff..8d13c4f599ddd 100644
--- a/src/timer/ps2/SDL_systimer.c
+++ b/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);
diff --git a/src/timer/psp/SDL_systimer.c b/src/timer/psp/SDL_systimer.c
index b91ea244a0d3a..0a33f45822397 100644
--- a/src/timer/psp/SDL_systimer.c
+++ b/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) {
diff --git a/src/timer/unix/SDL_systimer.c b/src/timer/unix/SDL_systimer.c
index 0a0c5e807035a..6f9a5c8a4f3a4 100644
--- a/src/timer/unix/SDL_systimer.c
+++ b/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;
diff --git a/src/timer/vita/SDL_systimer.c b/src/timer/vita/SDL_systimer.c
index a99b8e1c2b359..d9e856ef6d81c 100644
--- a/src/timer/vita/SDL_systimer.c
+++ b/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) {
diff --git a/src/timer/windows/SDL_systimer.c b/src/timer/windows/SDL_systimer.c
index d82d6f8bc23bb..5f1ba26bf7f0e 100644
--- a/src/timer/windows/SDL_systimer.c
+++ b/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.
*
diff --git a/test/testtimer.c b/test/testtimer.c
index de38842928061..249b76a7ebcee 100644
--- a/test/testtimer.c
+++ b/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);