Milkman Games
5May/111

Optimizing Adobe AIR Games for Mobile GPUs: 7 Simple Rules

For the last month or so, your friends at Milkman headquarters have been working away on porting Aqualux to the BlackBerry Playbook. The Playbook has an integrated Adobe AIR runtime, which means you can develop native apps for it using Actionscript 3.

Frankly, we're impressed. AIR runs on a number of other mobile systems- like Android and iOS, but on the Playbook it's hooked into the operating system on a deep level. Native AS3 APIs let you access in-app purchases, system state, and native GUI directly. On to of that, the device has a pretty beefy GPU and a dual core processor, so there's a lot of head room to create some very performant games using Flash.

Aqualux runs buttery smooth at 30 FPS on the Playbook- so let's explore some of the techniques for optimizing AIR games for phones and tablets.

Rule #1: Using GPU Mode

AIR apps can run in either GPU or CPU mode- the former utilizing the device's dedicated graphics chip to move textures about the screen, and the latter rendering everything in the software vector renderer. Unless you're doing a lot of dynamic (as in, animated) drawing with the Graphics API, GPU mode is the way to go- both for performance, and battery life.

You can activate gpu mode by adding this to your AIR XML Application manifest, under the element:

<renderMode>gpu</renderMode>

Rule #2: Using Bitmaps

The GPU mode renderer is best suited to rendering bitmaps, not vector art. So your next goal is to convert as much artwork as you can into Bitmap objects, instead of Shapes or Sprites with contents drawn into their Graphics node. Until you've done this, you may find performance is slower in GPU mode than CPU.

Tip: Ensure Bitmaps in the Flash Library have their base class set to flash.display.BitmapData.

If you're using the Flash IDE to import graphics, be sure they are actually be treating as Bitmaps in your app. Even if you import a bitmap graphic into flash and drag it to the stage, it will actually be converted to a vector shape and rendered as a fill, unless you specify it's base class as flash.display.BitmapData in the library.

Tip: Convert procedurally drawn vector graphics to Bitmap on the fly

If you're drawing graphics at run time with the vector Graphics API, you'll get better performance by converting these to BitmapData as well (as long as they aren't animated.) (You can use the cacheAsBitmap property as well, but I find this to be pretty finicky; it's too easy to apply it to the wrong part of the DisplayList and end up crippling your performance instead of improving it.)

To convert Graphics content to Bitmap, use the BitmapData.draw function. Yusuke Kawasaki provides a nice class on his blog for doing this, complete with options to control the Antialias level. Note that once your graphics are handled this way, you can set the Stage.displayQuality to LOW, further improving your performance without a noticeable loss in fidelity.

Tip: Text is vector as well.

Remember that TextField objects are also vector graphics; so if you're animating them heavily, you may want to draw them to Bitmaps with the BitmapData.draw() and dispose of the original. (This applies to tweening of static text only – if your text itself is changing, like a score counter, stick with a plain TextField.)

Rule #3: Avoid Setting the .filters Property on DisplayObjects

Filters are rendered in software, and they aren't supported in the GPU render mode. Not only will they not show up- your framerate will drop significantly by having them set, even they though aren't rendered. This doesn't mean you can't have filter effects in your game though!

Evaluate your content first and look for static objects with filters set on them. The easiest thing to do in this case is to apply the filters offline directly to the image with a graphics program like Photoshop.
For anything else, the BitmapData.applyFilter() method can be used to apply the filter to your object in a Bitmap. Since applying a filter such as glow will change the dimensions of the image, you'll need to reposition it accordingly. The following code snippet applies the filter 'filter' to the image 'baseData', and stores the offset of the new Bitmap in a flash.geom.Rectangle:

// calculate filters dimensions
var filteredRect:Rectangle=baseData.generateFilterRect(baseData.rect,filter);
// this will be your offset rect from the original
var iRect:Rectangle=baseData.rect.clone();
// offset
iRect.x=-filteredRect.x;
iRect.y=-filteredRect.y;
// the output bitmap
var res:FilteredBitmapData=new BitmapData(iRect.width,iRect.height,true);
// apply the filter
res.applyFilter(baseData,baseData.rect,new Point(-filteredRect.x,-filteredRect.y),filter);

Rule #4: Use Spritesheets for Frame Animation

For frame by frame animation, avoid using MovieClips- they're slower to redraw. Instead, use a spritesheet- a grid of all the frames in a single Bitmap. There's a number of approaches for this, and the folks at cheezworld give some excellent samples on their blog.

Rule #5: Save Memory by Reusing Textures

In Aqualux, the same pipe piece may appear in 20 or 30 places on the screen at once- but it's not a new texture for each one. Instead of creating a new BitmapData instance for repeated textures, use the same BitmapData inside a New Bitmap object. This can give you some significant memory savings, and also avoids the need to reupload the same texture to the GPU. Gamepoetry has an excellent class for automating this process.

Rule #6: Shallow Display List

Keep the display list hierarchy as shallow as possible- that means, the fewer child objects you have, the better. Use the simplest DisplayObject type that fits your needs as well: Bitmap is better than Sprite is better than MovieClip.

Rule #7: Avoid Instantiation During Game Play

One of the more expensive (in terms of CPU cycles) things you can do is create new () objects. If you have big constructors that call new () on lots of graphics objects, you're likely to see a stutter or slow down. In Aqualux, we mitigated this with the loading screen- major assets are created at the very beginning, and stored for reuse later. I'd recommend doing this step last, and only where you see problems or slowdowns after doing the previous optimizations.

I hope that helps you get started on optimzing Flash for mobile! If you have any other tips or questions, feel free to comment!

Comments (1) Trackbacks (0)
  1. Object pooling is a great design pattern to optimize your app !


Leave a comment

(required)

No trackbacks yet.