16.66ms frames times. How do you get a perfect 60 fps when sleep() only goes by whole milliseconds?

戏子无情 提交于 2020-01-15 13:35:32

问题


I enjoy making little games in C++ and Java, but something has always bothered me, and I've never quite understood how to remedy it.

Sleep in C++ and Java only works in milliseconds. Meaning that if you do

startTime=clock();
-------Execute everything in that frame-----
endTime=clock(); 
sleep(x-(endTime-startTime));

if x is 16 you get 62.5 frames per second if x is 17 you get 58.8 frames per second

Neither of which is that perfect 60 to fit a monitor's refresh rate.

But I've noticed some games like Warframe will say "16.66 ms frame time" meaning that their engine was able to somehow sleep with greater precision.

So how do you get that perfect 60?

Preferably in C++ as that's what i'm working with right now, but answering for Java too would also be helpful


回答1:


Relying on sleep alone is wrong anyway: you need scheduling at a fixed rate, and specified by you at the nanosecond precision. Use

final ExecutorService scheduler = Executors.newScheduledThreadPool(1);
scheduler.scheduleAtFixedRate(task, 
    TimeUnit.SECONDS.toNanos(1/60), 
    TimeUnit.SECONDS.toNanos(1/60), 
    TimeUnit.NANOSECONDS);



回答2:


You have an oversimplified understanding of how timing works. You don't do precision timing in user-space software. The precision timing is done elsewhere.

Software composes the next frame. When it's done, it passes the frame to a driver which displays the frame at the right time and at the right rate. Typically, the driver can trigger software to render the next frame.

Software just doesn't work by sleeping for amount of time. When it needs to wait for something, it actually waits for the thing it needs to wait for.




回答3:


I don't know if this is the right way to build a render loop, but there are more ways to sleep than sleep. Check out nanosleep.




回答4:


You should not have a timer which sleeps a specific time and runs the update and rendering method in a different Thread

Instead messure the time you needed to render your stuff on the last iteration. you give the time (or a multiplicator for your calculation based on the time) into the update method.

e.g.

final int stepsize = 10;

public void update(final Scenegraph scene, final float tpf) {
        // move all elements in the scene to +x with a speed of 10 distance-units per second 
        scene.getRootNode().move(tpf * stepsize, 0 ,0);
}

Now you don't have to hurry to manipulate your scenegraph and render the new view within a specific timer interval.

In games which are displaying a fps value, they use something like this:

private boolean runGame;
private double tpf;

public void stop() {
    runGame = false;
}

public void run() {
    rungame = true;

    // repeat until the game should stop
    while(runGame) {
        // calculate the time per frame of the last frame
        tpf = (System.nanoTime() / 1000 / 1000) - tpf;
        // calculate the frames per second to show it in a debug window or else
        double fps = (tpf > 0 ? (1.0 / tpf) : 0);

        // handle all actions on the scenegraph
        update(scenegraph, tpf);
        // render the new view for each camera (e.g. each players view)
        renderViews(cameras, scenegraph);
    }
}

if you display the fps to a debugconsole you will not be able to read it, cause it changes too fast :) save the fps of the last minute and display the average.




回答5:


For Windows, most game physics engines that have a thread that runs at a fixed frequency use something similar to this code. The delay's are based on an original read of a high frequency clock, to prevent any drifting over a long period of time. This example is Windows XP compatible, where a Sleep(1) can take up to almost 2ms (for later versions of windows, Sleep(1) takes up to 1ms). dwLateStep is incremented if any delay was too long (a optional diagnostic aid).

/* code for a thread to run at fixed frequency */
typedef unsigned long long UI64;        /* unsigned 64 bit int */
#define FREQ    400                     /* frequency */
static DWORD    dwLateStep;             /* late step count */

LARGE_INTEGER liPerfTemp;               /* used for query */
UI64 uFreq = FREQ;                      /* process frequency */
UI64 uOrig;                             /* original tick */
UI64 uWait;                             /* tick rate / freq */
UI64 uRem = 0;                          /* tick rate % freq */
UI64 uPrev;                             /* previous tick based on original tick */
UI64 uDelta;                            /* current tick - previous */
UI64 u2ms;                              /* 2ms of ticks */
UI64 i;

    /* ... */ /* wait for some event to start thread */
    timeBeginPeriod(1);                 /* set period to 1ms */
    Sleep(128);                         /* wait for it to stabilize */

    u2ms = ((UI64)(liPerfFreq.QuadPart)+499) / ((UI64)500);

    QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
    uOrig = uPrev = liPerfTemp.QuadPart;

    for(i = 0; i < (uFreq*30); i++){
        /* update uWait and uRem based on uRem */
        uWait = ((UI64)(liPerfFreq.QuadPart) + uRem) / uFreq;
        uRem  = ((UI64)(liPerfFreq.QuadPart) + uRem) % uFreq;
        /* wait for uWait ticks */
        while(1){
            QueryPerformanceCounter((PLARGE_INTEGER)&liPerfTemp);
            uDelta = (UI64)(liPerfTemp.QuadPart - uPrev);
            if(uDelta >= uWait)
                break;
            if((uWait - uDelta) > u2ms)
                Sleep(1);
        }
        if(uDelta >= (uWait*2))
            dwLateStep += 1;
        uPrev += uWait;
        /* fixed frequency code goes here */
        /*  along with some type of break when done */
    }

    timeEndPeriod(1);                   /* restore period */



回答6:


The frame itself does not necessarily have to be executed exactly every 16.6666ms, as long as the simulation is advanced by a fixed amount, you will have determinism in your engine. However, to match the simulation time with real time, it's necessary for each frame to be executed every 16.6666ms ON AVERAGE as compared to every time.

If your sleep function is consistently over 16.6666ms, you can save up these differences and if they add up to over 16.6666ms, you simulate an extra frame. If your sleep function goes under this, you can skip a frame from time to time.




回答7:


The best approach is to avoid sleep altogether, and use some API that call your code right after the screen has been refreshed. This greatly depends on the drawing libraries you are using, but you are looking for something similar to the famous requestAnimationFrame() function in JavaScript.

If you don't have access tu such API, then as other answer states, in Java you can make use of System.nanoTime() and Thread.sleep(long millis, int nanos).




回答8:


You have no guarentees as to how long the process will sleep for. In fact your program can be paused for any length of time in between sleeping and will do so thousands of times a second. there is tools for measuring the jitter in your system based on how wrong this timing is. http://www.azulsystems.com/jHiccup

Instead you should estimate in nanoTime when the process should have woken up next and sleep for the difference, this avoid cumulative error.

long intervalNanos = ....
long nextNanos = System.nanoTime() + intervalNanos;
while(running) {
    doSomething();
    long now = System.nanoTime();
    if (now > nextNanos) {
       // we are not keeping up.
       nextNanos = now + intervalNanos;
       continue;
    }
    long delay = (nextNanos - now) / 1000000;
    Thread.sleep(delay);
    nextNanos += intervalNanos;
}


来源:https://stackoverflow.com/questions/27543478/16-66ms-frames-times-how-do-you-get-a-perfect-60-fps-when-sleep-only-goes-by

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!