Jan 302012
 

Shawn Blais taught me all I needed to know about mobile optimizations of my AS3 code. His blog only has a dozen or two articles, but they are chock-full of interesting information (and even sales figures!). The most useful stuff for me, at this point, is the graphics pipeline optimizations.

The thing that ties the following three steps together is one unifying theory: use bitmaps for everything. You can start with MovieClips and vector Sprites, and you can even stick with Flash’s DisplayList to keep things organized. But the actual image data? Bitmaps! Always bitmaps.

Step One: Use the GPU rendering mode

When designing a mobile application, you’ll have an “application.xml” (or similarly named) file that contains all sorts of nice settings. One of those is going to tell the mobile device whether to render using the CPU or the GPU. Most defaults (including the FlashDevelop template file) will point you to CPU, and that may be fine for flash’s standard vector art. Switching to bitmaps and the GPU setting will give us much better performance.

Open up application.xml and make sure this exists:

[xml]

<initialWindow>

<renderMode>gpu</renderMode>

</initialWindow>

[/xml]

(source article is same as step 3)

Step Two: Lower the Stage Rendering Quality

AS3′s stage-rendering quality setting determines how vector art (probably your “Sprite” and “MovieClip” classes) is rendered. The thing is, even with a low setting, the stage still respects your bitmaps “smoothing” flag and draws it without any discernible difference. No need to spare the CPU cycles on something we aren’t using!

[as3]

stage.quality = LOW;

[/as3]

If you have vector art you are loading and converting during runtime (see step 3), AS3 even lets you change the stage quality on the fly! Just use this:

[as3]

stage.quality = HIGH;

convertMySprite(); // Or whatever your function is

stage.quality = LOW;

[/as3]

(source article)

Step Three: Use Bitmaps, and Cache them

This is probably the best performance-enhancing-drug my mobile apps have used so far, but it only gets the big performance gains if you use it in conjunction with Step 1 (GPU render mode).

The basic idea is to take all of your image data, and cache the bitmap data only once - dynamically – to a dictionary reference.  In GPU render mode, this stores the data as a texture in GPU memory on the mobile device. As long as all duplicate images are pulled from the original data, no new memory is used and creation of new graphics is lightning fast.

This works particularly well for common images used frequently – say, badguys, bullets, and common tiles. But I use it for everything!

Shawn’s original article laid out some source code and a longer explanation if you want to get into details and performance charts. His code does all of the conversions automatically for you, and the discucssion in the comments of his article improved upon it. I added a few tweaks myself, and it is now the only class I use for any type of image data. Imported .PNG file? Sprite or MovieClip in a .SWC? Class reference to an object you custom made? Doesn’t matter! All automated, all quick, all easy to use. Best of all: the code is really short, simple, and easy to read in about a minute. 

It’s a bit too long to paste here, so here’s a link to the class I use right now. Feel free to use it, just let me know if you improve on it :) Copy and paste it to the root of any project and you should be able to start using it right away.

(source article)

Bonus Step: Convert MovieClips on the fly

I haven’t tried this step out yet, but it’s an extension of the class I offered up in Step 3: automatically convert each frame from a MovieClip to cached bitmap data (and store that stuff in the GPU). If I had animations in my most recent games, I would be all over this too!

The Drawbacks

The biggest drawback I’ve found with the GPU-rendering mode is that you lose your runtime access to all filters and blendmodes. If you want to put a nice glow filter on a flame graphic, it’s easy to pre-bake that. But dynamically adding things – like adding a stroke to a font – are pretty hard to workaround (using system fonts, as opposed to spritesheet based font rendering). Hit me up if you have trouble with these, I’ve found some solutions (but not all).

A secondary detriment is that a complex DisplayList will slow down your app a bit more than usual. Try to avoid nesting that goes too deep. But you were doing that already, right? ;)

A compounded drawback here is that GPU-rendering isn’t available on your desktop (without Stage3D routines). That means when you do a test-run of your mobile app on your desktop, you will still see your filters and blendmodes, which can lead to some confusion, particularly if you’re porting a complex app written for desktop use.

The Gains?

The most complex scenes I’ve created with these methods run beautifully on my two-year-old Nexus One. I ran an hours-long performance test on my iPad – constantly drawing new moving objects to the screen (and never removing them). Though things (understandably) dropped to about 3FPS after quite a while – memory usage was nominal and the app never came close to crashing.

Most importantly, and this is a pretty big thing: this performance boost is BETTER than what you get from blitting on a mobile device. That means following those 3 basic steps will give you better performance than Flixel and FlashPunk’s default rendering engines.

In many cases, it’s possible for your mobile devices to outperform your desktop thanks to the direct GPU access. Hopefully this will change with Stage3D? Until then, blitting engines (like Flixel and FlashPunk) are probably your best bet for desktop and browser performance.

For all sorts of performance charts and figures, stress tests and numbers, check out Shawn’s posts (linked in the respective steps above). He sees up to a 4000% speed boost.

EDIT: Here’s another blog post confirming these results, too. :)

  30 Responses to “How to improve your mobile AS3/AIR performance”

Comments (26) Pingbacks (4)
  1. I’m so new at this stuff I’m terrified that I might have messed something up or mislabelled something somewhere. But the concept stands, despite my ignorance! All 3 steps turned my last two mobile games from unplayable messes, to silky-smooth performance.

  2. Just a little question like that, I read Shawn’s post and it advises against to many levels on the display list, wouldn’t it be even better if the CachedSprite class would return a Bitmap that you add to the stage instead of being a Sprite with a Bitmap in it? I am wondering if it would help. But I guess that way you couldn’t change the clip x and y.

  3. Shawn actually addressed that in his original post:

    *Note: It’s tempting to subclass Bitmap directly, and remove one extra layer from the display list, but I’ve found it’s beneficial to wrap it in a Sprite. Sprite has a mouseEnabled property for one thing, which a Bitmap does not (not sure why…), and using a Sprite is what allows you to easily oversample the texture, without the parent knowing or caring about it.

    I thought the same thing, though.

  4. Bitmap is such a wierd displayObject, it doesn’t have mouseEnabled, it doesn’t dispatch mouseEvents, but at the same time will block any mouse events underneath it. Just kinda becomes a pain in the ass to deal with…

    This is my new favorite lil’ class:

    public class BitmapSprite extends Sprite
    {
    protected var bitmap:Bitmap;

    public function BitmapSprite(bitmapData=null, pixelSnapping=”auto”, smoothing=true){
    mouseChildren = false;
    bitmap = new Bitmap(bitmapData, pixelSnapping, smoothing);
    addChild(bitmap);
    }

    public function get bitmapData():BitmapData { return bitmap.bitmapData; }
    public function set bitmapData(value:BitmapData):void {
    bitmap.bitmapData = value;
    }
    }

  5. Just a question isn’t FlashPunk and Flixel make that caching (if I am using embedded PNG files for graphics)?

    and if that is true isn’t using FlashPunk or Flixel most of these steps for me :)

  6. Check out the last few paragraphs of the article for FP and flixel. :P

  7. Hey Andy.
    Firstly, love the post. Very informative. :)

    I’m currently trying to raise the FPS in a game that I’m working on in AS3 for iOS. The enemies that I cycle through (contained in an array) for collision detection seems to be the main problem, i.e. the number of them (which is currently 60). These enemies are recycled when they are killed, so after I create the initial 60 I never need to recreate them.

    The enemies are contained in a movie clip loaded externally via a swc. Each frame has an animation movieclip. For example, the enemy movieclip’s frame 1 has his running animation, frame 2 has his dying animation, both in separate movieclips.
    Those movieclips all contain at this point, pngs. They were vectors, but I exported the frames as pngs and reimported them.

    In this kind of set up, what do you suggest to improve my performance? I was trying to implement your step 3, but I think with movieclips that contain movieclips and each of those have animation frames, I’m not too sure it’s a straightforward usage with your class.

    Any help would be much appreciated.

    Thanks! :)

  8. Hi Andy
    I continue to really enjoy this post, as I learn more.
    However, I’m really stuck with a simple thing I hope you can tell me what I’m doing wrong with the conversion of Charlie Chao’s AS3 carousel to Air-Android (http://charliechao.com/blog/?page_id=89).
    The movie works well in Flash CS6 simulator but the images don’t load/show in the direct connection to my Android phone via USB. I’ve set gpu in the AIR properties, it’s also in the app.xml file. I’ve changed cacheAsBitmap to cacheAsBitmapMatrix and still the images don’t appear. The images (using .pngs) are imported at runtime via an XML file. Arrrrrgh ! ! !
    Hope you can shed some light.
    Cheers, Michael

    • I’m pretty sure that mobile apps can’t load external files. You’ll have to import at compile-time, not runtime!

  9. I just built a system to convert (or export) movieclips to bitmapdata on the fly. It’s working pretty good so far but I added a sub-system that tries to smart cut the movieclips, which if you build your own system at some point I highly recommend you do. What I’m doing is checking the current frame’s generated bitmapdata against all the previous frames for that animation and creating an array of frame indexes to use when playing the animation back.
    I’m doing it in a per frame comparison to take into account any filter effects applied to the movieclip instance. In my test example I had one animation of 100 frames convert done to 5 unique frames. The drawback is the load time of the apps using this system is greatly increased due to the optimization so I’m looking for a better way to do that unique frame check. Any suggestions?

  10. What do you do to handle filters and blendmodes? Do you know the reason they’re not supported?

    • For filters and blendmodes, I apply them to the art and then I do bitmapdata.draw() and draw the blend/filter to a new art asset. Not sure why that limitation exists!

  11. I was just looking at the CachedSprite class. Could this be used to make Flex faster? Also, if you are using an up to date version of AIR / FP you could use drawWithQuality in place of draw. Though I’m not sure if the specifics of the class would benefit from it?

  12. Had a question about drawing bitmaps in AIR. I have an iOS app, stage.quality LOW and gpu render mode.

    I was having performance issues (low/erratic fps) with a movieclip created dynamically and made of multiple other movieclips. This was fixed by drawing a bmp of the entire mc and removing the other mcs.

    However, the big problem is in when testing on device, the smoothing doesn’t work and the graphics look really bad. I’ve tried setting the stage quality to HIGH, drawing and then setting to LOW. No luck. I’m doing this draw method: bmd.drawWithQuality(_mc, null, null,null,null,true,StageQuality.HIGH); Still no luck.

    I read that in gpu mode: “Note: Smoothing is only applied by the renderer, so if you attempt to bitmap draw a smoothed bitmap, it will not appear smoothed” here: http://www.indieflashblog.com/understanding-gpu-rendering-in-adobe-air-for-mobile.html that

    Might you have insights/workarounds? Any help would be greatly appreciated.

    Thanks,
    Mark

    • I’m not at my dev PC right now to look up the specifics, but you just have to enable Bitmap Smoothing.

      Set stagequality to HIGH, make a bitmap with .smoothing=true, do bitmap.bitmapdata.draw(movieclip), and set stagequality back to low. You should be golden!

      Let me know if you don’t figure it out and I’ll post some actual code for you later.

      • Wow, quickest response ever (had to step out). Thank you.

        Yep, here is my function:

        private function makeBitmap(h:int):void {
        var bmd:BitmapData = new BitmapData(DataModel.APP_WIDTH, h, true, 0x00FF0000);
        var bm:Bitmap = new Bitmap(bmd);
        bm.smoothing = true;
        bmd.draw(_mc);
        removeChildren(); //removes exisiting mcs from _mc
        _mc.addChild(bm);
        }

        When setting and tracing out the stage.quality before that function gets called, it is HIGH. Afterward, it gets set back to LOW.

        Still not working though. Looks great when testing in Flashbuilder. Once it is put on the iPad, it looks bad.

        Any other ideas?

        The mc being drawn is from an externally loaded swc with “Allow smoothing” checked, using Lossless/PNG compression. If I don’t make it a bitmap, it looks fine on device. However, the performance is significantly worse.

        Thanks again for the help.

        • Oh, and just tried recompiling in CPU render mode. It looks great, but performs poorly. Stumped…

          • FYI update on my issue. It seems to happen only on more complex pages. Simple pages with few elements render perfectly with the draw method. Thinking it could be a memory issue. More debugging needed but thanks for all the info. This blog is is incredibly helpful and informative.

  13. I’m having a problem with a simple example project that just displays an image – no matter what I try, if I set the render mode to GPU, the image doesn’t appear on my iOS device. However, as soon as I set the render mode to CPU, it appears.

    Do you have any idea what’s causing this?

    • Turns out it was an Adobe AIR problem – I updated from 3.2 to 3.4 and now the image shows up.

  14. Awesome/useful article. Looks like the link to the confirmation blog post at the bottom is broken though. I think this is the link that it’s meant to point to?

    Thanks!

  15. I would add, Step4, profile your code with Adobe Scout once you are almost done with your app.. good chance you will discover Garbage collection problems and other slow area in your code.
    also, its good to learn about AS3 optimizations in general (lots of blogs about it, including http:://www.guihaire.com/code)

Sorry, the comment form is closed at this time.