diff --git a/include/SDL3/SDL_timer.h b/include/SDL3/SDL_timer.h index c127da1fd1..fa40a6f82c 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 ca98271d66..4c5bd018c6 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 161fb91434..9c202a3689 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 726e415018..8f844b6d30 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 99389a4c76..e90b99dc7c 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 f7f11ea3d3..ce0082a40d 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 7070e17af4..8d13c4f599 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 b91ea244a0..0a33f45822 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 0a0c5e8070..6f9a5c8a4f 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 a99b8e1c2b..d9e856ef6d 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 d82d6f8bc2..5f1ba26bf7 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 de38842928..249b76a7eb 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);