Implement a much more accurate Display.sync() method that auto adapts to the systems timer resolution and load.

This commit is contained in:
kappa1 2012-02-29 23:18:35 +00:00
parent 57ec5db726
commit 4293976506
1 changed files with 77 additions and 28 deletions

View File

@ -79,8 +79,14 @@ public final class Display {
/** The current display mode, if created */
private static DisplayMode current_mode;
/** Timer for sync() */
private static long timeThen;
/** time at last sync() */
private static long lastTime;
/** Whether the sync() method has been initiated */
private static boolean syncInitiated;
/** adaptive time to yield instead of sleeping in sync()*/
private static long adaptiveYieldTime = 1000*1000;
/** X coordinate of the window */
private static int x = -1;
@ -401,41 +407,84 @@ public final class Display {
}
}
private static long timeLate;
/**
* Best sync method that works reliably.
*
* An accurate sync method that adapts automatically
* to the system it runs on to provide reliable results.
*
* @param fps The desired frame rate, in frames per second
*/
public static void sync(int fps) {
long timeNow;
long gapTo;
long savedTimeLate;
synchronized ( GlobalLock.lock ) {
gapTo = Sys.getTimerResolution() / fps + timeThen;
timeNow = Sys.getTime();
savedTimeLate = timeLate;
}
if (fps <= 0) return;
if (!syncInitiated) initiateSyncTimer();
long sleepTime = 1000000000 / fps; // nanoseconds to sleep this frame
// adaptiveYieldTime + remainder micro & nano seconds if smaller than sleepTime
long yieldTime = Math.min(sleepTime, adaptiveYieldTime + sleepTime % (1000*1000));
long overSleep = 0; // time the sync goes over by
try {
while ( gapTo > timeNow + savedTimeLate ) {
Thread.sleep(1);
timeNow = Sys.getTime();
while (true) {
long t = getTime() - lastTime;
if (t < sleepTime - yieldTime) {
Thread.sleep(1);
}
else if (t < sleepTime) {
// burn the last few CPU cycles to ensure accuracy
Thread.yield();
}
else {
overSleep = t - sleepTime;
break; // exit while loop
}
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} catch (InterruptedException e) {}
lastTime = getTime() - Math.min(overSleep, sleepTime);
// auto tune the amount of time to yield
if (overSleep > adaptiveYieldTime) {
// increase by 500 microseconds (half a ms)
adaptiveYieldTime = Math.min(adaptiveYieldTime + 500*1000, sleepTime);
}
synchronized ( GlobalLock.lock ) {
if ( gapTo < timeNow )
timeLate = timeNow - gapTo;
else
timeLate = 0;
timeThen = timeNow;
else if (overSleep < adaptiveYieldTime - 1000*1000) {
// decrease by 5 microseconds
adaptiveYieldTime = Math.max(adaptiveYieldTime - 5*1000, 1000*1000);
}
}
/**
* Get System Nano Time
* @return will return the current time in nano's
*/
private static long getTime() {
return (Sys.getTime() * 1000000000) / Sys.getTimerResolution();
}
/**
* On windows the sleep functions can be highly inaccurate by
* over 10ms making in unusable. However it can be forced to
* be a bit more accurate by running a separate sleeping daemon
* thread.
*/
private static void initiateSyncTimer() {
syncInitiated = true;
if (!System.getProperty("os.name").startsWith("Win")) {
return;
}
Thread timerAccuracyThread = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(Long.MAX_VALUE);
} catch (Exception e) {}
}
});
timerAccuracyThread.setDaemon(true);
timerAccuracyThread.start();
}
/** @return the title of the window */
public static String getTitle() {