From: <ka...@us...> - 2012-03-24 00:05:05
|
Revision: 3755 http://java-game-lib.svn.sourceforge.net/java-game-lib/?rev=3755&view=rev Author: kappa1 Date: 2012-03-24 00:04:52 +0000 (Sat, 24 Mar 2012) Log Message: ----------- Replace Display.sync(int fps) with an even better implementation, special thanks to Riven. Modified Paths: -------------- trunk/LWJGL/src/java/org/lwjgl/opengl/Display.java Added Paths: ----------- trunk/LWJGL/src/java/org/lwjgl/opengl/Sync.java Modified: trunk/LWJGL/src/java/org/lwjgl/opengl/Display.java =================================================================== --- trunk/LWJGL/src/java/org/lwjgl/opengl/Display.java 2012-03-21 19:34:51 UTC (rev 3754) +++ trunk/LWJGL/src/java/org/lwjgl/opengl/Display.java 2012-03-24 00:04:52 UTC (rev 3755) @@ -79,18 +79,6 @@ /** The current display mode, if created */ private static DisplayMode current_mode; - /** time at last sync() */ - private static long lastTime; - - /** Whether the sync() method has been initiated */ - private static boolean syncInitiated; - - /** whether to disable adaptive yield time in sync() method */ - private static boolean adaptiveTimeDisabled; - - /** adaptive time to yield instead of sleeping in sync()*/ - private static long adaptiveYieldTime; - /** X coordinate of the window */ private static int x = -1; @@ -411,98 +399,16 @@ } /** - * An accurate sync method that adapts automatically - * to the system it runs on to provide reliable results. + * An accurate sync method that will attempt to run an application loop + * at a constant frame rate. * - * @param fps The desired frame rate, in frames per second + * It should be called once every frame. + * + * @param fps - the desired frame rate, in frames per second */ public static void sync(int fps) { - 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 = adaptiveTimeDisabled ? 0 : Math.min(sleepTime, adaptiveYieldTime + sleepTime % (1000*1000)); - long overSleep = 0; // time the sync goes over by - - try { - 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) {} - - lastTime = getTime() - Math.min(overSleep, sleepTime); - - if (!adaptiveTimeDisabled) { - // auto tune the amount of time to yield - if (overSleep > adaptiveYieldTime) { - // increase by 200 microseconds (1/5 a ms) - adaptiveYieldTime = Math.min(adaptiveYieldTime + 200*1000, sleepTime); - } - else if (overSleep < adaptiveYieldTime - 2*1000*1000) { - // fast decrease by 50 microseconds for large under sleeps - adaptiveYieldTime = Math.max(adaptiveYieldTime - 50*1000, 0); - } - else if (overSleep < adaptiveYieldTime - 200*1000) { - // slower but finer decrease by 2 microseconds - adaptiveYieldTime = Math.max(adaptiveYieldTime - 2*1000, 0); - } - } + Sync.sync(fps); } - - /** - * Get System Nano Time - * @return will return the current time in nano's - */ - private static long getTime() { - return (Sys.getTime() * 1000000000) / Sys.getTimerResolution(); - } - - /** - * Initialise the sync(fps) method. - */ - private static void initiateSyncTimer() { - syncInitiated = true; - lastTime = getTime(); - - String osName = System.getProperty("os.name"); - - if (osName.startsWith("Mac") || osName.startsWith("Darwin")) { - // disabled on mac as it uses too much cpu, besides - // Thread.sleep is pretty accurate on mac by default - adaptiveTimeDisabled = true; - return; - } - - if (osName.startsWith("Win")) { - // 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. - 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() { Added: trunk/LWJGL/src/java/org/lwjgl/opengl/Sync.java =================================================================== --- trunk/LWJGL/src/java/org/lwjgl/opengl/Sync.java (rev 0) +++ trunk/LWJGL/src/java/org/lwjgl/opengl/Sync.java 2012-03-24 00:04:52 UTC (rev 3755) @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2002-2012 LWJGL Project + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are + * met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * * Neither the name of 'LWJGL' nor the names of + * its contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED + * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +package org.lwjgl.opengl; + +import org.lwjgl.Sys; + +/** +* A highly accurate sync method that continually adapts to the system +* it runs on to provide reliable results. +* +* @author Riven +* @author kappaOne +*/ +class Sync { + + /** number of nano seconds in a second */ + private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L; + + /** The time to sleep/yield until the next frame */ + private static long nextFrame = 0; + + /** whether the initialisation code has run */ + private static boolean initialised = false; + + /** stored results of how long sleep/yields took to calculate averages */ + private static RunningAvg sleepDurations = new RunningAvg(10); + private static RunningAvg yieldDurations = new RunningAvg(10); + + + /** + * An accurate sync method that will attempt to run an application loop + * at a constant frame rate. + * + * It should be called once every frame. + * + * @param fps - the desired frame rate, in frames per second + */ + public static void sync(int fps) { + if (fps <= 0) return; + if (!initialised) initialise(); + + try { + // sleep until the average sleep time is greater than the time remaining till nextFrame + for (long t0 = getTime(), t1; (nextFrame - t0) > sleepDurations.avg(); t0 = t1) { + Thread.sleep(1); + sleepDurations.add((t1 = getTime()) - t0); // update average sleep time + } + + // slowly dampen sleep average if too high to avoid over yielding + sleepDurations.dampenForLowResTicker(); + + // yield until the average yield time is greater than the time remaining till nextFrame + for (long t0 = getTime(), t1; (nextFrame - t0) > yieldDurations.avg(); t0 = t1) { + Thread.yield(); + yieldDurations.add((t1 = getTime()) - t0); // update average yield time + } + } catch (InterruptedException e) { + + } + + // schedule next frame, drop frame(s) if already too late for next frame + nextFrame = Math.max(nextFrame + NANOS_IN_SECOND / fps, getTime()); + } + + /** + * This method will initialise the sync method by setting initial + * values for sleepDurations/yieldDurations and nextFrame variables. + * + * If running windows on windows it will start the sleep timer fix. + */ + private static void initialise() { + initialised = true; + + sleepDurations.init(1000 * 1000); + yieldDurations.init((int) (-(getTime() - getTime()) * 1.333)); + + nextFrame = getTime(); + + String osName = System.getProperty("os.name"); + + if (osName.startsWith("Win")) { + // 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. + Thread timerAccuracyThread = new Thread(new Runnable() { + public void run() { + try { + Thread.sleep(Long.MAX_VALUE); + } catch (Exception e) {} + } + }); + + timerAccuracyThread.setDaemon(true); + timerAccuracyThread.start(); + } + } + + /** + * Get the system time in nano seconds + * + * @return will return the current time in nano's + */ + private static long getTime() { + return (Sys.getTime() * NANOS_IN_SECOND) / Sys.getTimerResolution(); + } + + private static class RunningAvg { + private final long[] slots; + private int offset; + + private static final long DAMPEN_THRESHOLD = 10 * 1000L * 1000L; // 10ms + private static final float DAMPEN_FACTOR = 0.9f; // don't change: 0.9f is exactly right! + + public RunningAvg(int slotCount) { + this.slots = new long[slotCount]; + this.offset = 0; + } + + public void init(long value) { + while (this.offset < this.slots.length) { + this.slots[this.offset++] = value; + } + } + + public void add(long value) { + this.slots[this.offset++ % this.slots.length] = value; + this.offset %= this.slots.length; + } + + public long avg() { + long sum = 0; + for (int i = 0; i < this.slots.length; i++) { + sum += this.slots[i]; + } + return sum / this.slots.length; + } + + public void dampenForLowResTicker() { + if (this.avg() > DAMPEN_THRESHOLD) { + for (int i = 0; i < this.slots.length; i++) { + this.slots[i] *= DAMPEN_FACTOR; + } + } + } + } +} \ No newline at end of file This was sent by the SourceForge.net collaborative development platform, the world's largest Open Source development site. |