Android Games

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

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
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
// the fps to be displayed
private String avgFps;
public void setAvgFps(String avgFps) {
    this.avgFps = avgFps;
}
 
public void render(Canvas canvas) {
    canvas.drawColor(Color.BLACK);
    droid.draw(canvas);
    // display fps
    displayFps(canvas, avgFps);
}
 
private void displayFps(Canvas canvas, String fps) {
    if (canvas != null && fps != null) {
        Paint paint = new Paint();
        paint.setARGB(255, 255, 255, 255);
        canvas.drawText(fps, this.getWidth() - 50, 20, paint);
    }
}

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.

Do not forget to check out our new Android Game ArkDroid (screenshots below). You feedback will be more than helpful!
Related Articles:
Subscribe
Notify of
guest


This site uses Akismet to reduce spam. Learn how your comment data is processed.

4 Comments
Oldest
Newest Most Voted
Inline Feedbacks
View all comments
Ryan Durel
13 years ago

I’m also having an issue with the FPS.

I’m using the 4.0 emulator and I’m topping at ~9.5 FPS. My code is the same as yours though.

Charity Abbott
Charity Abbott
13 years ago

For the emulator, apparently the act of setting the canvas to null and initializing it “canvas = surfaceHolder.lockCanvas();” takes quite some time. In the original code, it is not considered part of the frame period when it should be. Put “beginTime = System.currentTimeMillis();” right underneath the “while(running) {” line and it will show you that you need to skip a lot of frames because the emulator is slow, as expected. If you leave the beginTime where it is, it makes you think you are faster than 50 fps because it sleeps about 15ms and only takes 5ms to update and… Read more »

Jonathan S. Harbour
12 years ago

System.currentTimeMillis()  uses the system clock, so if the game is paused and later resumed, the repeat calls to update in the inner while loop will cause it to totally wig out.
  e.g. while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {

deepak
deepak
11 years ago

This is a great code, but I faced a little flickering issue. So I changed the location of the while loop given below after the finally block and it worked fine.

Please let me know if this is right.

while (sleepTime < 0 && framesSkipped < MAX_FRAME_SKIPS) {

Back to top button