Tutorials, Videos

Bitmap Caching for Android

9 Comments 15 September 2010

Bitmap Caching for Android

I plan to develop a small game during my free time targeting the Adobe AIR 2.5 runtime on Android devices. Before that, I wanted to understand the cacheAsBitmapMatrix property introduced in a recent build of the runtime (still in beta on labs.adobe.com). Bitmap caching was introduced in Flash Player 8. When you need to render complex vector graphics on the stage, it can dramatically improve the performances of your animation. But it will consume more memory… and you need to understand when Flash Player redraws the element and mount it in memory.

If you use simple transformations using the x and y properties, then just set the cacheAsBitmap property to true. Flash Player won’t regenerate an image in memory for every frame. Then, if you start playing with the scale, or the rotation, or even the alpha properties… the nightmare starts. Flash Player will regenerate at every frame a new bitmap in memory for every cached object.

In this particular case, you must use the new cacheAsBitmapMatrix property. The good news is that on Android, it will be hardware accelerated (setting the renderMode to GPU in the app description file). To apply this property, just add these two lines:

myDispOject.cacheAsBitmapMatrix = myDispOject.transform.concatenatedMatrix;
myDispOject.cacheAsBitmap = true;

To illustrate the benefits of this caching technic and the final behavior on a smartphone, I’ve recorded this video. The source code of the application is available at the end of this article.

Optimizing Flash on Android using Bitmap caching from michael chaize on Vimeo.

Here is the source code of the main class. The cloud class is linked to my principal MovieClip.

package
{
 
	import flash.display.MovieClip;
	import flash.events.MouseEvent;
	import flash.desktop.NativeApplication;
	import flash.display.Sprite;
	import flash.events.Event;
	import flash.display.DisplayObject;
	import flash.system.System;
	import flash.utils.getTimer;
 
	public class MainCaching extends MovieClip
	{
 
		private var theCurrentAnimation:Number = 0;
		private var frames:int=0;
		private var prevTimer:Number=0;
		private var curTimer:Number=0;
 
		public function MainCaching()
		{
			// constructor code
			// Event listeners for buttons
			button1.addEventListener(MouseEvent.MOUSE_DOWN, withoutCaching);
			button4.addEventListener(MouseEvent.MOUSE_DOWN, withoutCaching2);
			button2.addEventListener(MouseEvent.MOUSE_DOWN, withMatrixCaching);
			button5.addEventListener(MouseEvent.MOUSE_DOWN, withMatrixCaching2);
			button3.addEventListener(MouseEvent.MOUSE_DOWN, withCaching);
			button6.addEventListener(MouseEvent.MOUSE_DOWN, withCaching2);
			btnCleaner.addEventListener(MouseEvent.MOUSE_DOWN, cleanAll);
			btnCleaner.addEventListener(Event.ENTER_FRAME, calculateFps);
			buttonExit.addEventListener(MouseEvent.MOUSE_DOWN, exitApp);
		}
 
		public function calculateFps(evt:Event):void{
			frames+=1;
			curTimer=getTimer();
			if(curTimer-prevTimer>=1000){
				txtFps.text = String(Math.round(frames*1000/(curTimer-prevTimer)));
				prevTimer=curTimer;
				frames=0;
			}
		}
 
		public function cleanAll(evt:MouseEvent):void{
 
			for (var i:Number=0; i<30; i++)
			{
				var theCloud:DisplayObject = this.getChildByName("cloud" + i);
				theCloud.removeEventListener(Event.ENTER_FRAME,moveThings);
 
				if (theCloud != null)
				{
					this.removeChild(theCloud);
					theCloud = null;
				}
			}
			System.gc();
 
		}
 
		public function moveThings(event:Event):void{
 
			//txtMem.text = String(flash.system.System.totalMemory);
 
			if(theCurrentAnimation == 1){
 
				event.target.x -= ( event.target.x - Math.random() * ( stage.stageWidth) ) * 0.25;
				event.target.y -= ( event.target.y - Math.random() * ( stage.stageHeight/2) ) * 0.25;
				event.target.rotation += Math.random() * 360;
				event.target.alpha = Math.random();
			}
 
			if(theCurrentAnimation == 2){
				event.target.x += 10;
				if(event.target.x > stage.stageWidth + 100) event.target.x = Math.random() * -300;
			}
		}
 
		public function withoutCaching(evt:MouseEvent):void
		{
			theCurrentAnimation = 1;
 
			for (var i:Number=0; i<30; i++)
			{
 
				var myNewDisp:cloud = new cloud();
				myNewDisp.x = Math.random() * ( stage.stageWidth - ( myNewDisp.width / 2 ) );
				myNewDisp.y = Math.random() * ( stage.stageHeight/2 - (myNewDisp.height / 2 ) );
				myNewDisp.addEventListener( Event.ENTER_FRAME,moveThings);
				myNewDisp.name = "cloud" + i;
				this.addChild(myNewDisp);
				myNewDisp.cacheAsBitmap = false;
 
			}
		}
 
		public function withoutCaching2(evt:MouseEvent):void
		{
			theCurrentAnimation = 2;
 
			for (var i:Number=0; i<30; i++)
			{
 
				var myNewDisp:cloud = new cloud();
				myNewDisp.x = Math.random() * -300;
				myNewDisp.y = Math.random() * ( stage.stageHeight/2 - (myNewDisp.height / 2 ) );
				myNewDisp.addEventListener( Event.ENTER_FRAME,moveThings);
				myNewDisp.name = "cloud" + i;
				this.addChild(myNewDisp);
				myNewDisp.cacheAsBitmap = false;
 
			}
		}
 
		public function withCaching(evt:MouseEvent):void
		{
 
			theCurrentAnimation = 1;
 
			for (var i:Number=0; i<30; i++)
			{
 
				var myNewDisp:cloud = new cloud();
				myNewDisp.x = Math.random() * ( stage.stageWidth - ( myNewDisp.width / 2 ) );
				myNewDisp.y = Math.random() * ( stage.stageHeight/2 - (myNewDisp.height / 2 ) );
				myNewDisp.addEventListener( Event.ENTER_FRAME,moveThings);
				myNewDisp.name = "cloud" + i;
				this.addChild(myNewDisp);
				myNewDisp.cacheAsBitmap = true;
 
			}
		}
 
		public function withCaching2(evt:MouseEvent):void
		{
			theCurrentAnimation = 2;
 
			for (var i:Number=0; i<30; i++)
			{
 
				var myNewDisp:cloud = new cloud();
				myNewDisp.x = Math.random() * -300;
				myNewDisp.y = Math.random() * ( stage.stageHeight/2 - (myNewDisp.height / 2 ) );
				myNewDisp.addEventListener( Event.ENTER_FRAME,moveThings);
				myNewDisp.name = "cloud" + i;
				this.addChild(myNewDisp);
				myNewDisp.cacheAsBitmap = true;
 
			}
		}
 
		public function withMatrixCaching(evt:MouseEvent):void
		{
 
			theCurrentAnimation = 1;
 
			for (var i:Number=0; i<30; i++)
			{
 
				var myNewDisp:cloud = new cloud();
				myNewDisp.x = Math.random() * ( stage.stageWidth - ( myNewDisp.width / 2 ) );
				myNewDisp.y = Math.random() * ( stage.stageHeight/2 - (myNewDisp.height / 2 ) );
				myNewDisp.addEventListener( Event.ENTER_FRAME,moveThings);
				myNewDisp.name = "cloud" + i;
				this.addChild(myNewDisp);
				myNewDisp.cacheAsBitmapMatrix = myNewDisp.transform.concatenatedMatrix;
				myNewDisp.cacheAsBitmap = true;
 
			}
		}
 
		public function withMatrixCaching2(evt:MouseEvent):void
		{
 
			theCurrentAnimation = 2;
 
			for (var i:Number=0; i<30; i++)
			{
 
				var myNewDisp:cloud = new cloud();
				myNewDisp.x = Math.random() * -300;
				myNewDisp.y = Math.random() * ( stage.stageHeight/2 - (myNewDisp.height / 2 ) );
				myNewDisp.addEventListener( Event.ENTER_FRAME,moveThings);
				myNewDisp.name = "cloud" + i;
				this.addChild(myNewDisp);
				myNewDisp.cacheAsBitmapMatrix = myNewDisp.transform.concatenatedMatrix;
				myNewDisp.cacheAsBitmap = true;
 
			}
		}
 
		public function exitApp(evt:MouseEvent):void
		{
			NativeApplication.nativeApplication.exit();
		}
 
	}
 
}

You can download the FLA file here: http://www.riagora.com/pvt_content/android/AIR_bitmapCaching.fla.zip

And finally, a link to the official optimization guide: http://help.adobe.com/en_US/as3/mobile/index.html

Post to Twitter

Your Comments

9 Comments so far

  1. derek knox says:

    Helpful stuff, so the general rule of thumb is to utilize cacheAsBitmap when moving a vector clip on the x,y axis with no other changes (transformations/opacity/rotation)? But use cacheAsBitmapMatrix when planning to scale, rotate, and adjust the alpha? Is there an advantage to not using either of these properties on a vector clip?

  2. Here is the caching what I was looking for. Thanks for the introduction.

  3. admin says:

    @derek. You got it. If you can avoid Bitmap Caching, please do. You will consume less memory. Imagine you plan to cache a 250×250 pixels vector square, then it will uses 250KB of memory instead of 1KB uncached. It really depends on the complexity of your object. Two many vectors, moving, can be hard to render for the Flash Player.

  4. Shawn says:

    There’s a bit of an issue with the demo, you forgot to turn GPU mode on!

    If you download the FLA, make sure to go into your publish settings and turn GPU mode on, otherwise you won’t see any difference between the two types of caching.

    With GPU mode on, on the nexus one I get:
    CPU: ~6 FPS
    cacheAsBitmap: 1FPS
    cacheAsBitmapMatrix: 16fps

    It should be noted though that cacheAsBitmapMatrix is still plagued with some issues, especially anytime you are using masks, or complex display objects. If you are going to use it, test early and often.

  5. admin says:

    Hi Shawn. The rendermode is set to GPU in my demo, so it’s on. I’ll add a note in the post to make sure that everybody knows how to set the rendermode.
    I’m surprised that this paramater is not saved in the FLA… the Android Packager extension is still in beta, so…
    I have the same results on my N1 (6, 1 and 18FPS).

  6. Shawn says:

    Hmm, I guess it’s probably saved in the app.xml file, which isn’t included, so when you download and run the FLA it creates a new one.

    Thanks for the cool demo anyways :)


Trackbacks/Pingbacks

  1. AdobeMAX, one week later – my list* | @radley - November 4, 2010

    […] cacheAsBitmapMatrix – use it. […]

  2. What Creative Suite 5.5 Brings To Game Developers — FlashRealtime.com - April 13, 2011

    […] rotating and scaling – it will re-cache every change – however this can be solved with cacheAsBitmapMatrix on mobile platforms, Export as Bitmap is another very useful option to […]

  3. What Creative Suite 5.5 brings to game developers (Adobe Flash Platform Blog) - April 14, 2011

    […] start rotating and scaling – it will re-cache every change. Although this can be solved with cacheAsBitmapMatrix on mobile platforms, Export as Bitmap is another very useful option to […]

Share your view

Post a comment

Who am I ?

I'm Michaël CHAIZE, Adobe Flash Platform Evangelist based in Paris. I'm a big fan of Rich Internet Applications and I promote the Flash Platform in the Enterprise world.
You can follow me on twitter: http://twitter.com/mchaize

Magazine

Follow us on Facebook

© 2018 RIAgora. Powered by WordPress.

Daily Edition Theme by WooThemes - Premium WordPress Themes