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.

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.

	// 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

Download the code here.
LinkedInRedditTumblrDiggTechnorati FavoritesShare

34 Responses - Add Yours+

  1. dory says:

    Hi!

    Can this be applied for video also, with VideoView?

  2. ubul says:

    It seems to me that you’re calling storeStats() every update, even when the frame was skipped. Shouldn’t skipped frames be excluded from the FPS calculation? Please correct me if I’m wrong.

  3. Hai says:

    thanks for your sharing.
    when I import your project to eclipse, there are some compiling errors.
    ‘Syntax error on tokens, delete these tokens, _Speed, _Droid’
    there is a under-line for every class

    at last I delete the src folder & re-make them again, and everything works

  4. Michael says:

    Hi there! Thanks for the cool tutorial. I’m currently stuck on a modification to this game. I wanted to have a pause screen. I added an alert dialog which pops up when you press in a specific location in the onTouchEvent(), but I’m not sure how to handle effectively pausing and unpausing, or restarting the game.

    Can anyone offer some insight?

  5. nikhil says:

    Last paragraph of above article is confusing for me. Last Paragraph says:

    “Because we are setting our desired FPS to 50 that means that the speed will increase by 50*speed.value every update. To have the speed of let’s say 40 pixels/second you will need to set the speed delta for every tick to 2 (40 / (1000 / 50) = 2). In other words you need the droid to advance 2 pixels every game update (when you have 50 game updates per second) to cover 40 pixels per second.”

    My Doubts
    1) “If desired FPS is 50, speed will increase by 50*speed.value every update” ??
    Speed of droid should be constant

    2) “droid to advance 2 pixels every game update (when you have 50 game updates per second) to cover 40 pixels per second” ??

    If it travels 2 pixels every update and in total 50 updates happen in a second, then it will travel 2*50=100 pixels and not 40 pixels ??

    Please help me understand this.

  6. Conrad B. Hart says:

    Got the downloaded code working OK.
    However, what part of the code sets the droid in motion and sets its initial direction, etc?

  7. Marc says:

    Thanks so much for the tutorials.

    I am just getting into Android programming and found that if I use your code I am only getting around 10 FPS where as on you pic it seems like you are up at 50 FPS.

    I am using the Android 2.2 – API Level 8 emulator from the SDK.

    Any suggestions?

    Thanks in advance.

  8. hjr says:

    Is this line redundant?

    statusIntervalTimer += (System.currentTimeMillis() – statusIntervalTimer);

  9. Ben says:

    I got a concurrency issue, probably caused by this code. When the code calls sleep, it’s still in the synchronized block. This means that any other thread trying to access a variable guarded by the same lock, will be put on hold. This caused an Application Not Responding in my main thread. My advice is to move the sleep code outside of the lock.

    Anyhow, nice tutorial, mate!

    • Impaler says:

      Thanks for the comment Ben.
      Looking back what I did when I was starting, I would change a lot of things.
      I appreciate your comment and hopefully I will find the time to revisit the posts and optimize them based on all the good comments and learnt lessons.

      Cheers,
      I

  10. Meshx86 says:

    i have galaxy s ii, am getting 39 FPS, is this normal ?

    note: the downloaded project from this post is not working, i copied the code and modified project from previous post

  11. Koren says:

    Hi Impaler,

    This site is great! Going through this site was fun.

    I think you’ve got a mistake in the FPS calculation. the numbers just seems to good to be true, in 50, 100, 1000, 2000 MAX_FPS, they are always accurate.

    i think you’ve got a double dependency on the stats average that causes the stats to be so clean.

    in the storeStats() method, I’ve changed the code to

    long time = System.currentTimeMillis();

    // assuming that the sleep works each call to storeStats
    // happens at 1000/FPS so we just add it up
    statusIntervalTimer += (time – lastTime); //MainThread.FRAME_PERIOD;

    instead of getting the time from the constant, i took it from the difference of last stats update from the system

    this gives completely different results.

    not only that, but at 1000fps, the stats don’t come out at 1sec intervals. since the 10ms time for the thread isn’t enough and is fake.

    Thanks,
    Koren

  12. Ed says:

    Nice tutorial, I modified your code a little to see if I could get it to run in my game so I could see the fps. I am using landscape mode and when I take out the collision effects it gives me a error in the displayFPS method. When I try to give the drawText actual coords it will not show up. Do you know how I could fix this to where the FPS shows.

    • Impaler says:

      Could you give more info? What is the error? What exactly did you change?
      It might help to get an idea what is going wrong….

      Cheers

      • Ed says:

        I fixed it, I changed the .setRGB to setColor because when I tried to change the color using RGB values it didn’t work for some reason but it shows just fine now.

  13. See the last tutorial, some refactoring and renaming was done. Also make sure you update the name of your onDraw() method to render().

  14. Noobprogrammer says:

    where do u define the update method i cant get my program to compile because i cant find it

  15. R says:

    Great information here. Just curious about the following line. Am I missing something, or is this redundant?

    statusIntervalTimer += (System.currentTimeMillis() – statusIntervalTimer);

  16. Guru says:

    @impaler

    hey. i tried modifying your code to create another fixed droid and wanted to display a toast whenever a collision occurs between the 2.
    but i’m not able to implement the toast thing. i tried using Handler but i think i’m doing it wrong.
    cud u help me out in this part?

    • Guru says:

      the 2nd droid is created at position (100,100)

      so i used an if..else construct in the update() method.

      if((droid.getX()>=77&&droid.getX()77&&droid.getY()<123))
      {
      //code to display toast
      }

  17. Anirudh says:

    @impaler

    great tutorial dude….
    but i’m still facing a problem getting it to run. i downloaded the code and there’s no errors but when the app fires up, it crashes saying it has stopped unexpectedly and force closes.
    please help me out.

    • Impaler says:

      Have you checked what’s in the logs?
      You can use LogCat to get some information.
      Please give some more data so we can help you. (Window -> Show View -> LogCat)

      • Anirudh says:

        Found the error in the LogCat. there was some mismatch in the filename….
        code runs perfectly.
        brilliant job. i’m kinda new to Android programming and this helped me out a lot.

        \m/

  18. Anirudh says:

    @impaler
    great tutorial dude….
    but i’m still facing a problem running this app on my emulator(Android 2.1-update 1).
    there’s no error but when the app fires up, a dialog box appears telling the app has stopped unexpectedly and force closes.
    pls help me out.

  19. Drew says:

    Thanks for the update. I will give it a go here in a few min.

  20. Drew says:

    I seem to be having a problem here. The “averageFPS” does not change… its always the same value as “MAX_FPS”.

    I changed MAX_FPS to 500 and averageFPS returns 500. I really doubt my phone can do 500FPS.

    Any idea?

    • Impaler says:

      Thanks for notifying me about it.

      It seems to be a problem with the statistics gathering tick. I will revisit the code and do an update as soon as I get the time.

      • Drew says:

        Thanks. I’m trying to figure it out but a little bit over my head… if i figure it out i will report back. Until then i await a fix :)

    • Impaler says:

      I have uploaded a fixed version.
      The storeStats() method was called with a delay in case of a lag but the statusIntervalTimer was still incremented with the normal FRAME_PERIOD.

      What needed to happen is keep the last statusStore in a variable (I have added lastStatusStore) and let the statusIntervalTimer be incremented by the number of milliseconds elapsed since the last statusStore and once the status tick is achieved (one second) the actual FPS is added to the average array and the average is being calculated.

      Check the new MainThread and compare it with the old one.

      Thanks for the heads up.

      • pramod says:

        Where is updated code showing correct fps? When I download the above code it was not updated. I am finding difficulty to display correct fps as per above solution, please help me..

  21. John says:

    Great Articles… keep them up.