1 /* 2 * Copyright (c) 2002-2012 LWJGL Project 3 * All rights reserved. 4 * 5 * Redistribution and use in source and binary forms, with or without 6 * modification, are permitted provided that the following conditions are 7 * met: 8 * 9 * * Redistributions of source code must retain the above copyright 10 * notice, this list of conditions and the following disclaimer. 11 * 12 * * Redistributions in binary form must reproduce the above copyright 13 * notice, this list of conditions and the following disclaimer in the 14 * documentation and/or other materials provided with the distribution. 15 * 16 * * Neither the name of 'LWJGL' nor the names of 17 * its contributors may be used to endorse or promote products derived 18 * from this software without specific prior written permission. 19 * 20 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 21 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED 22 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 23 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR 24 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, 25 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, 26 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR 27 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF 28 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING 29 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 30 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 31 */ 32 package org.lwjgl.opengl; 33 34 import org.lwjgl.Sys; 35 36 /** 37 * A highly accurate sync method that continually adapts to the system 38 * it runs on to provide reliable results. 39 * 40 * @author Riven 41 * @author kappaOne 42 */ 43 class Sync { 44 45 /** number of nano seconds in a second */ 46 private static final long NANOS_IN_SECOND = 1000L * 1000L * 1000L; 47 48 /** The time to sleep/yield until the next frame */ 49 private static long nextFrame = 0; 50 51 /** whether the initialisation code has run */ 52 private static boolean initialised = false; 53 54 /** for calculating the averages the previous sleep/yield times are stored */ 55 private static RunningAvg sleepDurations = new RunningAvg(10); 56 private static RunningAvg yieldDurations = new RunningAvg(10); 57 58 59 /** 60 * An accurate sync method that will attempt to run at a constant frame rate. 61 * It should be called once every frame. 62 * 63 * @param fps - the desired frame rate, in frames per second 64 */ sync(int fps)65 public static void sync(int fps) { 66 if (fps <= 0) return; 67 if (!initialised) initialise(); 68 69 try { 70 // sleep until the average sleep time is greater than the time remaining till nextFrame 71 for (long t0 = getTime(), t1; (nextFrame - t0) > sleepDurations.avg(); t0 = t1) { 72 Thread.sleep(1); 73 sleepDurations.add((t1 = getTime()) - t0); // update average sleep time 74 } 75 76 // slowly dampen sleep average if too high to avoid yielding too much 77 sleepDurations.dampenForLowResTicker(); 78 79 // yield until the average yield time is greater than the time remaining till nextFrame 80 for (long t0 = getTime(), t1; (nextFrame - t0) > yieldDurations.avg(); t0 = t1) { 81 Thread.yield(); 82 yieldDurations.add((t1 = getTime()) - t0); // update average yield time 83 } 84 } catch (InterruptedException e) { 85 86 } 87 88 // schedule next frame, drop frame(s) if already too late for next frame 89 nextFrame = Math.max(nextFrame + NANOS_IN_SECOND / fps, getTime()); 90 } 91 92 /** 93 * This method will initialise the sync method by setting initial 94 * values for sleepDurations/yieldDurations and nextFrame. 95 * 96 * If running on windows it will start the sleep timer fix. 97 */ initialise()98 private static void initialise() { 99 initialised = true; 100 101 sleepDurations.init(1000 * 1000); 102 yieldDurations.init((int) (-(getTime() - getTime()) * 1.333)); 103 104 nextFrame = getTime(); 105 106 String osName = System.getProperty("os.name"); 107 108 if (osName.startsWith("Win")) { 109 // On windows the sleep functions can be highly inaccurate by 110 // over 10ms making in unusable. However it can be forced to 111 // be a bit more accurate by running a separate sleeping daemon 112 // thread. 113 Thread timerAccuracyThread = new Thread(new Runnable() { 114 public void run() { 115 try { 116 Thread.sleep(Long.MAX_VALUE); 117 } catch (Exception e) {} 118 } 119 }); 120 121 timerAccuracyThread.setName("LWJGL Timer"); 122 timerAccuracyThread.setDaemon(true); 123 timerAccuracyThread.start(); 124 } 125 } 126 127 /** 128 * Get the system time in nano seconds 129 * 130 * @return will return the current time in nano's 131 */ getTime()132 private static long getTime() { 133 return (Sys.getTime() * NANOS_IN_SECOND) / Sys.getTimerResolution(); 134 } 135 136 private static class RunningAvg { 137 private final long[] slots; 138 private int offset; 139 140 private static final long DAMPEN_THRESHOLD = 10 * 1000L * 1000L; // 10ms 141 private static final float DAMPEN_FACTOR = 0.9f; // don't change: 0.9f is exactly right! 142 RunningAvg(int slotCount)143 public RunningAvg(int slotCount) { 144 this.slots = new long[slotCount]; 145 this.offset = 0; 146 } 147 init(long value)148 public void init(long value) { 149 while (this.offset < this.slots.length) { 150 this.slots[this.offset++] = value; 151 } 152 } 153 add(long value)154 public void add(long value) { 155 this.slots[this.offset++ % this.slots.length] = value; 156 this.offset %= this.slots.length; 157 } 158 avg()159 public long avg() { 160 long sum = 0; 161 for (int i = 0; i < this.slots.length; i++) { 162 sum += this.slots[i]; 163 } 164 return sum / this.slots.length; 165 } 166 dampenForLowResTicker()167 public void dampenForLowResTicker() { 168 if (this.avg() > DAMPEN_THRESHOLD) { 169 for (int i = 0; i < this.slots.length; i++) { 170 this.slots[i] *= DAMPEN_FACTOR; 171 } 172 } 173 } 174 } 175 }