The problem that you have with reaching the target FPS, base on NESPowerGlove's answer, is that you've got to pick an integer value to call Thread.sleep()
for - if you pick 16
, you'll get about 63fps; if you pick 17
, you'll get around 58fps.
There are two solutions that I can think of to this:
Implement some sort of negative feedback loop to control the FPS. For example, I found that the following PI (proportional + integral) controller kept the FPS around 60:
final long sleepTime = (long) (1000.0 / TARGET_FPS);
System.out.println("Sleep time is " + sleepTime);
long time = System.nanoTime();
long delta = 0;
double cumulativeFpsError = 0.0;
for (int frameCount = 0; true; ++frameCount) {
Thread.sleep((long) (sleepTime + delta));
long elapsed = System.nanoTime() - time;
if (elapsed >= 1_000_000_000) {
double fps = (double) frameCount / elapsed * 1e9;
System.out.println(fps);
cumulativeFpsError += (fps - 60);
delta += (fps - TARGET_FPS) * 0.55 + 0.14 * cumulativeFpsError;
frameCount = 0;
time += elapsed;
}
}
The values of 0.55
and 0.14
were found by trial-and-error. Better values are probably available (but at least it appears to be roughly stable). fps output:
61.08042479827653
61.817816897275485
58.42717726642977
62.0654826347193
58.43759367657694
62.07263954479402
58.444556146850026
62.05489635777375
58.4438970272065
62.05784933619571
58.45590905841833
62.069491426232766
58.44381852435569
62.07438904528996
...
Actually, the integral term doesn't do much at all - presumably because the sleep value can only vary in steps of size 1.
Use a different method to sleep for a more precise amount of time. For example, How to suspend a java thread for a small period of time, like 100 nanoseconds? suggests some alternatives, like polling System.nanoTime()
until some deadline is exceeded. e.g.
long time = System.nanoTime();
for (int frameCount = 0; true; ++frameCount) {
long end = System.nanoTime() + (long) (1_000_000_000.0 / TARGET_FPS);
while (System.nanoTime() < end);
long elapsed = System.nanoTime() - time;
if (elapsed >= 1_000_000_000) {
double fps = (double) frameCount / elapsed * 1e9;
System.out.println(fps);
frameCount = 0;
time = System.nanoTime();
}
}
This seems to work better, with the FPS hovering just under 60:
58.99961555850502
59.99966304189236
59.99942898543434
59.99968068169941
59.99968770162551
59.99919595077507
59.99945862488483
59.999679241714766
59.99753134157542
59.99963898217224
59.999265728986
...
The disadvantage of this might be that while
loop is going to be quite hot.
Of course, you could do some sort of hybrid approach, using Thread.sleep
for most of the wait, then using the hot while
loop to get a more precise delay.