Skip to content

Commit 24946e0

Browse files
Calibrate SDL sleep and refine frame timing
Add calibrated sleep logic and fractional-frame stepping to improve timing accuracy and avoid overshooting frame deadlines. Introduces calibration for SDL_Delay(1) (s_minSleepCostMs / s_delay1CostMs) and helper functions (CalibrateMinSleep/CalibrateDelay1, SleepMinStep/SleepShortest) to decide whether to sleep 1ms or yield (SDL_Delay(0)). Adds NextFrameStep with fractional accumulation to advance nextUpdate using integer+fractional frame periods, and replaces unconditional SDL_Delay(1) with conditional calibrated delay/yield in the main loop. These changes reduce jitter and drift caused by coarse sleep quantization.
1 parent 964ad6d commit 24946e0

File tree

1 file changed

+109
-7
lines changed

1 file changed

+109
-7
lines changed

project/src/backend/sdl/SDLApplication.cpp

Lines changed: 109 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,96 @@ namespace lime {
2222
std::map<int, std::map<int, int> > gamepadsAxisMap;
2323
bool inBackground = false;
2424

25+
static double nextFrac = 0.0;
26+
static double s_minSleepCostMs = 2.0; // initial estimate
27+
static bool s_minSleepCalibrated = false;
28+
29+
static inline bool CanUseDelay1(Uint32 remainingMs)
30+
{
31+
return remainingMs > (Uint32)(s_minSleepCostMs + 1.0);
32+
}
33+
34+
static inline void CalibrateMinSleep()
35+
{
36+
Uint32 t0 = SDL_GetTicks();
37+
SDL_Delay(1);
38+
Uint32 t1 = SDL_GetTicks();
39+
40+
Uint32 observed = t1 - t0;
41+
if (observed < 1) observed = 1;
42+
43+
double observedD = (double)observed;
44+
45+
s_minSleepCostMs = (s_minSleepCostMs + observedD) * 0.5;
46+
s_minSleepCalibrated = true;
47+
}
48+
49+
static inline Uint32 SleepMinStep()
50+
{
51+
if (!s_minSleepCalibrated)
52+
CalibrateMinSleep();
53+
else
54+
CalibrateMinSleep();
55+
56+
return (Uint32)s_minSleepCostMs;
57+
}
58+
59+
static inline Uint32 NextFrameStep(double framePeriod) {
60+
Uint32 base = (Uint32)framePeriod;
61+
nextFrac += framePeriod - (double)base;
62+
if (nextFrac >= 1.0) { base++; nextFrac -= 1.0; }
63+
return base;
64+
}
65+
66+
static inline Sint32 GetFrameTimeRemaining(Uint32 nextUpdate)
67+
{
68+
Uint32 now = SDL_GetTicks();
69+
Sint32 remaining = (Sint32)(nextUpdate - now);
70+
return remaining > 0 ? remaining : 0;
71+
}
72+
73+
static double s_delay1CostMs = 2.0;
74+
static bool s_delay1Calibrated = false;
75+
76+
static inline void CalibrateDelay1()
77+
{
78+
Uint32 t0 = SDL_GetTicks();
79+
SDL_Delay(1);
80+
Uint32 t1 = SDL_GetTicks();
81+
82+
double observed = (double)(t1 - t0);
83+
if (observed < 1.0) observed = 1.0;
84+
85+
s_delay1CostMs = (s_delay1CostMs + observed) * 0.5;
86+
s_delay1Calibrated = true;
87+
}
88+
89+
static inline Uint32 SleepShortest(Uint32 timeRemainingMs)
90+
{
91+
if (!s_delay1Calibrated) {
92+
CalibrateDelay1();
93+
return 0;
94+
}
95+
96+
97+
// The "+1" is a small guard because observed delay is quantized and jittery
98+
if (timeRemainingMs > (Uint32)(s_delay1CostMs + 1.0)) {
99+
Uint32 t0 = SDL_GetTicks();
100+
SDL_Delay(1);
101+
Uint32 t1 = SDL_GetTicks();
102+
103+
double observed = (double)(t1 - t0);
104+
if (observed < 1.0) observed = 1.0;
105+
106+
s_delay1CostMs = (s_delay1CostMs + observed) * 0.5;
107+
return (Uint32)observed;
108+
}
109+
110+
// Otherwise, we're too close: yield instead of risking a multi-ms overshoot
111+
// Yield cost is scheduler-dependent so we don't try to model it here!
112+
SDL_Delay(0);
113+
return 0;
114+
}
25115

26116
SDLApplication::SDLApplication () {
27117

@@ -41,8 +131,8 @@ namespace lime {
41131
currentApplication = this;
42132

43133
framePeriod = 1000.0 / 60.0;
44-
45134
currentUpdate = 0;
135+
46136
lastUpdate = 0;
47137
nextUpdate = 0;
48138

@@ -135,12 +225,10 @@ namespace lime {
135225
applicationEvent.deltaTime = currentUpdate - lastUpdate;
136226
lastUpdate = currentUpdate;
137227

138-
nextUpdate += framePeriod;
228+
nextUpdate += NextFrameStep(framePeriod);
139229

140230
while (nextUpdate <= currentUpdate) {
141-
142-
nextUpdate += framePeriod;
143-
231+
nextUpdate += NextFrameStep(framePeriod);
144232
}
145233

146234
ApplicationEvent::Dispatch (&applicationEvent);
@@ -989,9 +1077,23 @@ namespace lime {
9891077

9901078
if (!isBlocking) System::GCEnterBlocking ();
9911079
isBlocking = true;
992-
SDL_Delay (1);
993-
break;
9941080

1081+
Uint32 now = SDL_GetTicks();
1082+
Sint32 remaining = (Sint32)(nextUpdate - now);
1083+
1084+
if (remaining > 0)
1085+
{
1086+
if (lime::CanUseDelay1((Uint32)remaining))
1087+
{
1088+
lime::SleepMinStep();
1089+
1090+
}
1091+
else
1092+
{
1093+
SDL_Delay(0);
1094+
}
1095+
}
1096+
break;
9951097
}
9961098

9971099
}

0 commit comments

Comments
 (0)