FPS lock not precise
I'm implementing an FPS cap for my game, but it's not very precise.
public static volatile int FPS_CAP = 60;
@Override
public void run() {
long lastTime = System.nanoTime();
double amountOfTicks = 60.0;
double ns = 1000000000 / amountOfTicks;
double delta = 0;
long timer = System.currentTimeMillis(), lastRender;
while (running) {
long now = System.nanoTime();
delta += (now - lastTime) / ns;
lastTime = now;
while (delta >= 1) {
tick();
delta--;
}
lastRender = System.currentTimeMillis();
draw.render();
draw.fps++;
if (FPS_CAP != -1) {
try {
int nsToSleep = (int) ((1000 / FPS_CAP) - (System.currentTimeMillis() - lastRender));
if (nsToSleep > 1 / FPS_CAP) {
Thread.sleep(nsToSleep);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (System.currentTimeMillis() - timer > 1000) {
timer += 1000;
draw.lastFPS = draw.fps;
draw.fps = 0;
// updates = 0;
}
}
}
The result is:
As you can see it's really not accurate, sometimes it's a lot lower than 60 and sometimes even higher!
I want it to be as precise as possible.
Thanks in advance.
Java is not precise enough to achieve such precision mechanisms. All rendering libraries actually rely on a C or C++ layer to manage real-time precision.
In your case, the best workaround would be to avoid the use of Thread.sleep()
.
You can rely on Timer events instead to run the updates during the TimerTask. Your game will have a heartbeat approximately 60 times per second.
Another solution, if 60fps is good for you, is to wait for a VSync before rendering your screen. Most LCD screens are 60 or 120fps. The VSync should be managed by your graphical library (swing, JavaFX or other).
A last solution, you could look into the code of a specialized engine (like JMonkey) as a reference.
First of all I see you mixed System.currentTimeMilis()
and System.nanoTime()
not really a good idea, use only either one of them. Better only use System.nanoTime()
since you are working on a high precision.
What's causing your issue is Thread.sleep()
is not precise enough. So you need to avoid sleeping. Change your sleeping code to this;
lastRender = System.nanoTime(); //change it to nano instead milli
draw.render();
draw.fps++;
if (FPS_CAP > 0) {
while ( now - lastRender < (1000000000 / FPS_CAP))
{
Thread.yield();
//This stops the app from consuming all your CPU. It makes this slightly less accurate, but is worth it.
//You can remove this line and it will still work (better), your CPU just climbs on certain OSes.
//FYI on some OS's this can cause pretty bad stuttering.
try {Thread.sleep(1);} catch(Exception e) {}
now = System.nanoTime();
}
}
About how to enable VSYNC
, your application need to be full screen and you should call Toolkit.sync()
after every render.
JavaFX is based upon a pulse mechanism.
A pulse is an event that indicates to the JavaFX scene graph that it is time to synchronize the state of the elements on the scene graph with Prism. A pulse is throttled at 60 frames per second (fps) maximum and is fired whenever animations are running on the scene graph. Even when animation is not running, a pulse is scheduled when something in the scene graph is changed. For example, if a position of a button is changed, a pulse is scheduled.
When a pulse is fired, the state of the elements on the scene graph is synchronized down to the rendering layer. A pulse enables application developers a way to handle events asynchronously. This important feature allows the system to batch and execute events on the pulse.
. . .
The Glass Windowing Toolkit is responsible for executing the pulse events. It uses the high-resolution native timers to make the execution.
To understand the implementation of the pulse mechanism, it is best to study the JavaFX source code. Some key classes are:
Timer.java WinTimer.java win/Timer.h win/Timer.cpp
The above links are for the generic Timer class and the window specific timer implementations. The JavaFX source code includes implementations for other platforms, such as OS X, GTK, iOS, Android, etc.
Some implementations (such as OS X it would seem) allow for vsync synchronization of the Timer implementation, others (such as Windows it would seem) do not allow for vsync synchronization. Though, these systems can get complicated, so I guess it is possible that, in some cases, vsync synchronization might be achieved via the hardware graphics pipeline rather than via the timer.
The windows native code is based upon a timeSetEvent call.
By default, the JavaFX framerate is capped at 60fps, though it is adjustable via undocumented properties.
If you are not using JavaFX (and it doesn't seem you are), you could still examine the JavaFX source code and learn about its implementation there in case you wanted to port any of the concepts or code for use in your application. You might also be able to shoehorn the JavaFX timer mechanism into a non-JavaFX application by making your application subclass the JavaFX Application class or creating a JFXPanel to initiate the JavaFX toolkit, then implementing your timer callback based upon AnimationTimer.