Search code examples
javagraphics2dframe-rate

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:

enter image description here

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.


Solution

  • 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.