Android Game Development - Measuring FPS

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  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  * @author impaler
011  *
012  * The Main thread which contains the game loop. The thread must have access to
013  * the surface view and holder to trigger events every game tick.
014  */
015 public class MainThread extends Thread {
016  
017     private static final String TAG = MainThread.class.getSimpleName();
018  
019     // desired fps
020     private final static int    MAX_FPS = 50;
021     // maximum number of frames to be skipped
022     private final static int    MAX_FRAME_SKIPS = 5;
023     // the frame period
024     private final static int    FRAME_PERIOD = 1000 / MAX_FPS;
025  
026     // Stuff for stats */
027     private DecimalFormat df = new DecimalFormat("0.##");  // 2 dp
028     // we'll be reading the stats every second
029     private final static int    STAT_INTERVAL = 1000//ms
030     // the average will be calculated by storing
031     // the last n FPSs
032     private final static int    FPS_HISTORY_NR = 10;
033     // last time the status was stored
034     private long lastStatusStore = 0;
035     // the status time counter
036     private long statusIntervalTimer    = 0l;
037     // number of frames skipped since the game started
038     private long totalFramesSkipped         = 0l;
039     // number of frames skipped in a store cycle (1 sec)
040     private long framesSkippedPerStatCycle  = 0l;
041  
042     // number of rendered frames in an interval
043     private int frameCountPerStatCycle = 0;
044     private long totalFrameCount = 0l;
045     // the last FPS values
046     private double  fpsStore[];
047     // the number of times the stat has been read
048     private long    statsCount = 0;
049     // the average FPS since the game started
050     private double  averageFps = 0.0;
051  
052     // Surface holder that can access the physical surface
053     private SurfaceHolder surfaceHolder;
054     // The actual view that handles inputs
055     // and draws to the surface
056     private MainGamePanel gamePanel;
057  
058     // flag to hold game state
059     private boolean running;
060     public void setRunning(boolean running) {
061         this.running = running;
062     }
063  
064     public MainThread(SurfaceHolder surfaceHolder, MainGamePanel gamePanel) {
065         super();
066         this.surfaceHolder = surfaceHolder;
067         this.gamePanel = gamePanel;
068     }
069  
070     @Override
071     public void run() {
072         Canvas canvas;
073         Log.d(TAG, "Starting game loop");
074         // initialise timing elements for stat gathering
075         initTimingElements();
076  
077         long beginTime;     // the time when the cycle begun
078         long timeDiff;      // the time it took for the cycle to execute
079         int sleepTime;      // ms to sleep (<0 if we're behind)
080         int framesSkipped;  // number of frames being skipped
081  
082         sleepTime = 0;
083  
084         while (running) {
085             canvas = null;
086             // try locking the canvas for exclusive pixel editing
087             // in the surface
088             try {
089                 canvas = this.surfaceHolder.lockCanvas();
090                 synchronized (surfaceHolder) {
091                     beginTime = System.currentTimeMillis();
092                     framesSkipped = 0;  // resetting the frames skipped
093                     // update game state
094                     this.gamePanel.update();
095                     // render state to the screen
096                     // draws the canvas on the panel
097                     this.gamePanel.render(canvas);
098                     // calculate how long did the cycle take
099                     timeDiff = System.currentTimeMillis() - beginTime;
100                     // calculate sleep time
101                     sleepTime = (int)(FRAME_PERIOD - timeDiff);
102  
103                     if (sleepTime > 0) {
104                         // if sleepTime > 0 we're OK
105                         try {
106                             // send the thread to sleep for a short period
107                             // very useful for battery saving
108                             Thread.sleep(sleepTime);
109                         catch (InterruptedException e) {}
110                     }
111  
112                     while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {
113                         // we need to catch up
114                         this.gamePanel.update(); // update without rendering
115                         sleepTime += FRAME_PERIOD;  // add frame period to check if in next frame
116                         framesSkipped++;
117                     }
118  
119                     if (framesSkipped > 0) {
120                         Log.d(TAG, "Skipped:" + framesSkipped);
121                     }
122                     // for statistics
123                     framesSkippedPerStatCycle += framesSkipped;
124                     // calling the routine to store the gathered statistics
125                     storeStats();
126                 }
127             finally {
128                 // in case of an exception the surface is not left in
129                 // an inconsistent state
130                 if (canvas != null) {
131                     surfaceHolder.unlockCanvasAndPost(canvas);
132                 }
133             }   // end finally
134         }
135     }
136  
137     /**
138      * The statistics - it is called every cycle, it checks if time since last
139      * store is greater than the statistics gathering period (1 sec) and if so
140      * it calculates the FPS for the last period and stores it.
141      *
142      *  It tracks the number of frames per period. The number of frames since
143      *  the start of the period are summed up and the calculation takes part
144      *  only if the next period and the frame count is reset to 0.
145      */
146     private void storeStats() {
147         frameCountPerStatCycle++;
148         totalFrameCount++;
149  
150         // check the actual time
151         statusIntervalTimer += (System.currentTimeMillis() - statusIntervalTimer);
152  
153         if (statusIntervalTimer >= lastStatusStore + STAT_INTERVAL) {
154             // calculate the actual frames pers status check interval
155             double actualFps = (double)(frameCountPerStatCycle / (STAT_INTERVAL / 1000));
156  
157             //stores the latest fps in the array
158             fpsStore[(int) statsCount % FPS_HISTORY_NR] = actualFps;
159  
160             // increase the number of times statistics was calculated
161             statsCount++;
162  
163             double totalFps = 0.0;
164             // sum up the stored fps values
165             for (int i = 0; i < FPS_HISTORY_NR; i++) {
166                 totalFps += fpsStore[i];
167             }
168  
169             // obtain the average
170             if (statsCount < FPS_HISTORY_NR) {
171                 // in case of the first 10 triggers
172                 averageFps = totalFps / statsCount;
173             else {
174                 averageFps = totalFps / FPS_HISTORY_NR;
175             }
176             // saving the number of total frames skipped
177             totalFramesSkipped += framesSkippedPerStatCycle;
178             // resetting the counters after a status record (1 sec)
179             framesSkippedPerStatCycle = 0;
180             statusIntervalTimer = 0;
181             frameCountPerStatCycle = 0;
182  
183             statusIntervalTimer = System.currentTimeMillis();
184             lastStatusStore = statusIntervalTimer;
185 //          Log.d(TAG, "Average FPS:" + df.format(averageFps));
186             gamePanel.setAvgFps("FPS: " + df.format(averageFps));
187         }
188     }
189  
190     private void initTimingElements() {
191         // initialise timing elements
192         fpsStore = new double[FPS_HISTORY_NR];
193         for (int i = 0; i < FPS_HISTORY_NR; i++) {
194             fpsStore[i] = 0.0;
195         }
196         Log.d(TAG + ".initTimingElements()""Timing elements for stats initialised");
197     }
198  
199 }

I introduced a simple measuring function. I count the number of frames every second and store them in the  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(255255255255);
18         canvas.drawText(fps, this.getWidth() - 5020, paint);
19     }
20 }

Try running it. You should have the FPS displayed in the top right corner.

FPS displayed


Reference:   Measuring FPS  from our  JCG  partner Tamas Jano from " Against The Grain " blog.
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值