Fast Rendering in AIR: Cached SpriteSheet’s
In a previous post I showed how proper use of AIR 3.0′s GPU RenderMode can boost your frameRate by 500% on mobile devices. Here we’ll look at how you can do the same thing, and get even bigger gains with your MovieClip animations (like 4000% faster! Seriously, that’s a real number…)
Now, the basic premise of the previous tutorial was to use a single bitmapData instance, for each type of Sprite. We’d cache the sprite to a Static bitmapData property, and then use that to render our Bitmap()’s later.
The difference now, is that instead of shareing a single bitmapData, we’re going to share an array of bitmapData’s. Let that sink in, read it again. Ok. And we’re also gonna cache frameLabels and numFrames so we can have some gotoAndPlay() action
There’s like a million ways you can do this in flash, here’s just one…
The first step involves determining which of your assets will need to become SpriteSheet’s. Anything that is repeated many times, or is rendered constantly on screen, should be made into a SpriteSheet. For items that are only displayed briefly, and only a single instance of them occurs, you can just let the normal Flash rendering engine do it’s job. This is one of the beautiful things about gpu render mode, not everything needs to be cached, you can cheat alot(ie straight embed library animations), as long as you optimize what’s important.
Note: Transforms are extremely cheap on the displayList with this method. So, if you’re just scaling, rotating, or moving, don’t make a spriteSheet for it, just Tween it instead, it’s only a little slower, and you save a ton of memory on the gpu.
Once you’ve decided which Animations you want to accelerate, you’ll need the export them as a PNG Sequence. We’ll use Zoe from gskinner.com to help. Zoe will take a swf, and convert each frame to a png, it will also inspect the timeline for any labels, and save all the data in a JSON file.
The steps to do so are as follows:
The next step is to load the JSON and PNG Files into flash, and play them back. And, we want to make sure that all instances of a specific animation, share the same spriteSheet in memory, this is what will give us full GPU acceleration.
Including the JSON and Bitmap’s is simple:
Next you need a class to take these objects, and figure out how to play them. This is essentially just a matter of analyzing the JSON file from Zoe, and cutting out the big bitmapData into small bitmapData’s. You also need to devise an API to play those frames, swapping the bitmapData each frame, and respecting your basic movieclip api’s.
I wrote a simple class to aid in this called SpriteSheetClip.
SpriteSheetClip directly extends Bitmap, and emulates the movieClip API. Without going over the entire class, the core code here is the caching and ripping of the SpriteSheets that are passed in. Notice how I use the JSON data to get frameWidth and frameHeight, and getQualifiedClassname for my unique identifier, after that it’s a simple loop:
Now, using this class, we can make multiple copies of the same Animation, and run them extremely cheaply. You can run 100′s of animations, even on the oldest of Android Devices. On newer devices like iPad 2 or Galaxy Nexus you can push upwards of 500-800 animations at once. Plus scaling, alpha and rotation are all very cheap.
You probably noticed in the code, but for performance reasons, my class will not update itself, it must be manually stepped! Rather than have a bunch of enterFrame listeners, I put the responsibility of the parent class to call step() on all it’s running children, so a single enter frame handler instead of hundreds.
There’s a bit more to the class in terms of managing frames, so feel free to check it out in the attached source project. Be warned though, it’s a little buggy…. I consider this a sample implementation rather than production code, but do as you will.
Next up let’s run some benchmarks, and see how many of these we can push…
In this benchmark I will add as many Animations’s as possible while maintaining 30 fps.
Now, the basic premise of the previous tutorial was to use a single bitmapData instance, for each type of Sprite. We’d cache the sprite to a Static bitmapData property, and then use that to render our Bitmap()’s later.
The difference now, is that instead of shareing a single bitmapData, we’re going to share an array of bitmapData’s. Let that sink in, read it again. Ok. And we’re also gonna cache frameLabels and numFrames so we can have some gotoAndPlay() action

There’s like a million ways you can do this in flash, here’s just one…
Step 1: MovieClip’s to SpriteSheet’s
The first step involves determining which of your assets will need to become SpriteSheet’s. Anything that is repeated many times, or is rendered constantly on screen, should be made into a SpriteSheet. For items that are only displayed briefly, and only a single instance of them occurs, you can just let the normal Flash rendering engine do it’s job. This is one of the beautiful things about gpu render mode, not everything needs to be cached, you can cheat alot(ie straight embed library animations), as long as you optimize what’s important.Note: Transforms are extremely cheap on the displayList with this method. So, if you’re just scaling, rotating, or moving, don’t make a spriteSheet for it, just Tween it instead, it’s only a little slower, and you save a ton of memory on the gpu.
Once you’ve decided which Animations you want to accelerate, you’ll need the export them as a PNG Sequence. We’ll use Zoe from gskinner.com to help. Zoe will take a swf, and convert each frame to a png, it will also inspect the timeline for any labels, and save all the data in a JSON file.
The steps to do so are as follows:
- Take your animation, and move it into it’s own FLA. Save the fla somewhere in your assets directory, and export the SWF.
- Download and install Zoe: http://easeljs.com/zoe.html
- Within Zoe, open the SWF you just exported, Zoe should auto-detect the bounds. Click “Export”.
Step 2: Playback the SpriteSheet’s in Flash, really really fast.
The next step is to load the JSON and PNG Files into flash, and play them back. And, we want to make sure that all instances of a specific animation, share the same spriteSheet in memory, this is what will give us full GPU acceleration.Including the JSON and Bitmap’s is simple:
[Embed("assets/Animation.png")]
public var AnimationImage:Class;
[Embed("assets/Animation.json", mimeType="application/octet-stream")]
public var AnimationData:Class;
I wrote a simple class to aid in this called SpriteSheetClip.
//Just pass in the data from zoe...
var mc:SpriteSheetClip = new SpriteSheetClip(AnimationImage, AnimationData);
mc.gotoAndPlay("someLabel");
addChild(mc);
//For max performance, all cached sprites must be manually tickes
function onEnterFrame(event:Event):void {
cachedAnimation.step();
}
public static var frameCacheByAsset:Object = {};
public function SpriteSheetClip(bitmapAsset:Class, jsonAsset:Class){
_currentStartFrame = 1;
var assetName:String = getQualifiedClassName(bitmapAsset);
//Check cache, if cached, do nothing
if(frameCacheByAsset[assetName]){
frameCache = frameCacheByAsset[assetName].frames;
frameLabels = frameCacheByAsset[assetName].labels;
_frameWidth = frameCache[0].width;
_frameHeight = frameCache[0].height;
}
//If not cached, rip frames from bitmapData and grab json
else {
//rip clip!
var data:Object = JSON.parse(new jsonAsset().toString());
var bitmap:Bitmap = new bitmapAsset();
var spriteSheet:BitmapData = bitmap.bitmapData;
_frameWidth = data.frames.width;
_frameHeight = data.frames.height;
frameLabels = data.animations;
var cols:int = spriteSheet.width/_frameWidth|0;
var rows:int = spriteSheet.height/_frameHeight|0;
var p:Point = new Point();
var l:int = cols * rows;
frameCache = [];
_currentStartFrame = 1;
var scale:Number = drawScale;
var m:Matrix = new Matrix();
//Loop through all frames...
for(var i:int = 0; i < l; i++){
var col:int = i%cols;
var row:int = i/cols|0;
m.identity(); //Reset matrix
m.tx = -_frameWidth * col;
m.ty = -_frameHeight * row;
m.scale(scale, scale);
//Draw one frame and cache it
var bmpData:BitmapData = new BitmapData(_frameWidth * scale, _frameHeight * scale, true, 0x0);
bmpData.draw(spriteSheet, m, null, null, null, true);
frameCache[i] = bmpData;
}
_currentEndFrame = i;
numFrames = _currentEndFrame;
_frameWidth *= scale;
_frameHeight *= scale;
//Cache frameData
frameCacheByAsset[assetName] = {
frames: frameCache, //Cache bitmapData's
labels: frameLabels //Cache frameLabels
};
}
//Show frame 1
this.bitmapData = frameCache[_currentStartFrame-1];
}
You probably noticed in the code, but for performance reasons, my class will not update itself, it must be manually stepped! Rather than have a bunch of enterFrame listeners, I put the responsibility of the parent class to call step() on all it’s running children, so a single enter frame handler instead of hundreds.
There’s a bit more to the class in terms of managing frames, so feel free to check it out in the attached source project. Be warned though, it’s a little buggy…. I consider this a sample implementation rather than production code, but do as you will.
Next up let’s run some benchmarks, and see how many of these we can push…
Benchmarks!
In this benchmark I will add as many Animations’s as possible while maintaining 30 fps.- Download the Flash Builder Project and run it yourself
댓글 없음:
댓글 쓰기