In the previous entry we have created a game loop that runs at a constant speed and constant (more or less) FPS.
How can we measure it? Check the new
I introduced a simple measuring function. I count the number of frames every second and store them in the
If the one second is hit then it takes the number of rendered frames and adds them to the array of FPSs. After this I just reset the counters for the current statistics cycle and add the results to a global counter. The average is calculated on the values stored in the last 10 seconds.
Line 171 logs the FPS every second while line 172 sets the
The
Try running it. You should have the FPS displayed in the top right corner.
How can we measure it? Check the new
MainThread.java
class.001 | package net.obviam.droidz; |
002 |
003 | import java.text.DecimalFormat; |
004 |
005 | import android.graphics.Canvas; |
006 | import android.util.Log; |
007 | import android.view.SurfaceHolder; |
008 |
009 |
010 | /** |
011 | * @author impaler |
012 | * |
013 | * The Main thread which contains the game loop. The thread must have access to |
014 | * the surface view and holder to trigger events every game tick. |
015 | */ |
016 | public class MainThread extends Thread { |
017 | |
018 | private static final String TAG = MainThread. class .getSimpleName(); |
019 | |
020 | // desired fps |
021 | private final static int MAX_FPS = 50 ; |
022 | // maximum number of frames to be skipped |
023 | private final static int MAX_FRAME_SKIPS = 5 ; |
024 | // the frame period |
025 | private final static int FRAME_PERIOD = 1000 / MAX_FPS; |
026 | |
027 | // Stuff for stats */ |
028 | private DecimalFormat df = new DecimalFormat( "0.##" ); // 2 dp |
029 | // we'll be reading the stats every second |
030 | private final static int STAT_INTERVAL = 1000 ; //ms |
031 | // the average will be calculated by storing |
032 | // the last n FPSs |
033 | private final static int FPS_HISTORY_NR = 10 ; |
034 | // last time the status was stored |
035 | private long lastStatusStore = 0 ; |
036 | // the status time counter |
037 | private long statusIntervalTimer = 0l; |
038 | // number of frames skipped since the game started |
039 | private long totalFramesSkipped = 0l; |
040 | // number of frames skipped in a store cycle (1 sec) |
041 | private long framesSkippedPerStatCycle = 0l; |
042 |
043 | // number of rendered frames in an interval |
044 | private int frameCountPerStatCycle = 0 ; |
045 | private long totalFrameCount = 0l; |
046 | // the last FPS values |
047 | private double fpsStore[]; |
048 | // the number of times the stat has been read |
049 | private long statsCount = 0 ; |
050 | // the average FPS since the game started |
051 | private double averageFps = 0.0 ; |
052 |
053 | // Surface holder that can access the physical surface |
054 | private SurfaceHolder surfaceHolder; |
055 | // The actual view that handles inputs |
056 | // and draws to the surface |
057 | private MainGamePanel gamePanel; |
058 |
059 | // flag to hold game state |
060 | private boolean running; |
061 | public void setRunning( boolean running) { |
062 | this .running = running; |
063 | } |
064 |
065 | public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) { |
066 | super (); |
067 | this .surfaceHolder = surfaceHolder; |
068 | this .gamePanel = gamePanel; |
069 | } |
070 |
071 | @Override |
072 | public void run() { |
073 | Canvas canvas; |
074 | Log.d(TAG, "Starting game loop" ); |
075 | // initialise timing elements for stat gathering |
076 | initTimingElements(); |
077 | |
078 | long beginTime; // the time when the cycle begun |
079 | long timeDiff; // the time it took for the cycle to execute |
080 | int sleepTime; // ms to sleep (<0 if we're behind) |
081 | int framesSkipped; // number of frames being skipped |
082 |
083 | sleepTime = 0 ; |
084 | |
085 | while (running) { |
086 | canvas = null ; |
087 | // try locking the canvas for exclusive pixel editing |
088 | // in the surface |
089 | try { |
090 | canvas = this .surfaceHolder.lockCanvas(); |
091 | synchronized (surfaceHolder) { |
092 | beginTime = System.currentTimeMillis(); |
093 | framesSkipped = 0 ; // resetting the frames skipped |
094 | // update game state |
095 | this .gamePanel.update(); |
096 | // render state to the screen |
097 | // draws the canvas on the panel |
098 | this .gamePanel.render(canvas); |
099 | // calculate how long did the cycle take |
100 | timeDiff = System.currentTimeMillis() - beginTime; |
101 | // calculate sleep time |
102 | sleepTime = ( int )(FRAME_PERIOD - timeDiff); |
103 | |
104 | if (sleepTime > 0 ) { |
105 | // if sleepTime > 0 we're OK |
106 | try { |
107 | // send the thread to sleep for a short period |
108 | // very useful for battery saving |
109 | Thread.sleep(sleepTime); |
110 | } catch (InterruptedException e) {} |
111 | } |
112 | |
113 | while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) { |
114 | // we need to catch up |
115 | this .gamePanel.update(); // update without rendering |
116 | sleepTime += FRAME_PERIOD; // add frame period to check if in next frame |
117 | framesSkipped++; |
118 | } |
119 |
120 | if (framesSkipped > 0 ) { |
121 | Log.d(TAG, "Skipped:" + framesSkipped); |
122 | } |
123 | // for statistics |
124 | framesSkippedPerStatCycle += framesSkipped; |
125 | // calling the routine to store the gathered statistics |
126 | storeStats(); |
127 | } |
128 | } finally { |
129 | // in case of an exception the surface is not left in |
130 | // an inconsistent state |
131 | if (canvas != null ) { |
132 | surfaceHolder.unlockCanvasAndPost(canvas); |
133 | } |
134 | } // end finally |
135 | } |
136 | } |
137 |
138 | /** |
139 | * The statistics - it is called every cycle, it checks if time since last |
140 | * store is greater than the statistics gathering period (1 sec) and if so |
141 | * it calculates the FPS for the last period and stores it. |
142 | * |
143 | * It tracks the number of frames per period. The number of frames since |
144 | * the start of the period are summed up and the calculation takes part |
145 | * only if the next period and the frame count is reset to 0. |
146 | */ |
147 | private void storeStats() { |
148 | frameCountPerStatCycle++; |
149 | totalFrameCount++; |
150 | |
151 | // check the actual time |
152 | statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer); |
153 | |
154 | if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) { |
155 | // calculate the actual frames pers status check interval |
156 | double actualFps = ( double )(frameCountPerStatCycle / (STAT_INTERVAL / 1000 )); |
157 | |
158 | //stores the latest fps in the array |
159 | fpsStore[( int ) statsCount % FPS_HISTORY_NR] = actualFps; |
160 | |
161 | // increase the number of times statistics was calculated |
162 | statsCount++; |
163 | |
164 | double totalFps = 0.0 ; |
165 | // sum up the stored fps values |
166 | for ( int i = 0 ; i < FPS_HISTORY_NR; i++) { |
167 | totalFps += fpsStore[i]; |
168 | } |
169 | |
170 | // obtain the average |
171 | if (statsCount < FPS_HISTORY_NR) { |
172 | // in case of the first 10 triggers |
173 | averageFps = totalFps / statsCount; |
174 | } else { |
175 | averageFps = totalFps / FPS_HISTORY_NR; |
176 | } |
177 | // saving the number of total frames skipped |
178 | totalFramesSkipped += framesSkippedPerStatCycle; |
179 | // resetting the counters after a status record (1 sec) |
180 | framesSkippedPerStatCycle = 0 ; |
181 | statusIntervalTimer = 0 ; |
182 | frameCountPerStatCycle = 0 ; |
183 |
184 | statusIntervalTimer = System.currentTimeMillis(); |
185 | lastStatusStore = statusIntervalTimer; |
186 | // Log.d(TAG, "Average FPS:" + df.format(averageFps)); |
187 | gamePanel.setAvgFps( "FPS: " + df.format(averageFps)); |
188 | } |
189 | } |
190 |
191 | private void initTimingElements() { |
192 | // initialise timing elements |
193 | fpsStore = new double [FPS_HISTORY_NR]; |
194 | for ( int i = 0 ; i < FPS_HISTORY_NR; i++) { |
195 | fpsStore[i] = 0.0 ; |
196 | } |
197 | Log.d(TAG + ".initTimingElements()" , "Timing elements for stats initialised" ); |
198 | } |
199 |
200 | } |
fpsStore[]
array. The storeStats()
is called every tick and if the 1 second interval (STAT_INTERVAL = 1000;
) is not reached then it simply adds the number of frames to the existing count.If the one second is hit then it takes the number of rendered frames and adds them to the array of FPSs. After this I just reset the counters for the current statistics cycle and add the results to a global counter. The average is calculated on the values stored in the last 10 seconds.
Line 171 logs the FPS every second while line 172 sets the
avgFps
value of the gamePanel
instance to be displayed on the screen.The
MainGamePanel.java
class’s render
method contains the the displayFps
call which just draws the text onto the top right corner of the display
every time the state is rendered. It also has a private member that is
set from the thread.01 | // the fps to be displayed |
02 | private String avgFps; |
03 | public void setAvgFps(String avgFps) { |
04 | this .avgFps = avgFps; |
05 | } |
06 |
07 | public void render(Canvas canvas) { |
08 | canvas.drawColor(Color.BLACK); |
09 | droid.draw(canvas); |
10 | // display fps |
11 | displayFps(canvas, avgFps); |
12 | } |
13 |
14 | private void displayFps(Canvas canvas, String fps) { |
15 | if (canvas != null && fps != null ) { |
16 | Paint paint = new Paint(); |
17 | paint.setARGB( 255 , 255 , 255 , 255 ); |
18 | canvas.drawText(fps, this .getWidth() - 50 , 20 , paint); |
19 | } |
20 | } |
No comments:
Post a Comment