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 */ /** The current display mode, if created */
private static DisplayMode current_mode; private static DisplayMode current_mode;
/** Timer for sync() */ /** time at last sync() */
private static long timeThen; 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 */ /** X coordinate of the window */
private static int x = -1; 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 * @param fps The desired frame rate, in frames per second
*/ */
public static void sync(int fps) { public static void sync(int fps) {
long timeNow; if (fps <= 0) return;
long gapTo; if (!syncInitiated) initiateSyncTimer();
long savedTimeLate;
synchronized ( GlobalLock.lock ) { long sleepTime = 1000000000 / fps; // nanoseconds to sleep this frame
gapTo = Sys.getTimerResolution() / fps + timeThen; // adaptiveYieldTime + remainder micro & nano seconds if smaller than sleepTime
timeNow = Sys.getTime(); long yieldTime = Math.min(sleepTime, adaptiveYieldTime + sleepTime % (1000*1000));
savedTimeLate = timeLate; long overSleep = 0; // time the sync goes over by
}
try { try {
while ( gapTo > timeNow + savedTimeLate ) { while (true) {
Thread.sleep(1); long t = getTime() - lastTime;
timeNow = Sys.getTime();
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) { } catch (InterruptedException e) {}
Thread.currentThread().interrupt();
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);
} }
else if (overSleep < adaptiveYieldTime - 1000*1000) {
synchronized ( GlobalLock.lock ) { // decrease by 5 microseconds
if ( gapTo < timeNow ) adaptiveYieldTime = Math.max(adaptiveYieldTime - 5*1000, 1000*1000);
timeLate = timeNow - gapTo;
else
timeLate = 0;
timeThen = timeNow;
} }
} }
/**
* 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 */ /** @return the title of the window */
public static String getTitle() { public static String getTitle() {