The rendering mode used for Android is called GPU Vector. Both the creation of individual pixel buffers and scene compositing are done by the GPU. The GPU is particularly useful in this performance-constrained environment.
The GPU rendering technique only benefits vector art. It reduces the time needed to rasterize vector graphics, especially complex ones, to a bitmap. A bitmap is already a pixel buffer, also called a texture. There is no need to duplicate it. In fact, doing so would waste precious memory.
To perform GPU rendering in Android using Flash Professional, go to File→AIR Android settings. Select the General tab, and under Render Mode, select GPU.
In Flash Builder, set the renderMode node of the initialWindow node of your application descriptor to gpu:
[code]
<initialWindow>
<renderMode>gpu</renderMode>
…
[/code]
If the user has a device that does not perform hardware acceleration, or if it does not work reliably with its drivers, AIR ignores the setting. No message is dispatched.
The cacheAsBitmap Property
The cacheAsBitmap property was added in Flash 8 with the Surface Renderer. Its purpose is to optimize the animation of vector graphics along the x- and y-axes, a process called translation.
When the cacheAsBitmap property is set to true, the display object rendered as a bitmap is cached in memory for reuse, as shown in Figure 14-1.
Because vector graphics use subpixels and bitmaps don’t, objects snap to the full pixel and may look different from what you intended. To avoid this, apply the vector image on a full whole-number pixel.
You can set the cacheAsBitmap property in Flash Professional under Properties→Cache As Bitmap. You can only apply it to symbols. The art will then be converted to a bitmap at runtime, but it will not be modified in the IDE.
You can also set the property in code:
[code]
var myVector:Sprite = new Sprite();
myVector.cacheAsBitmap = true;
[/code]
An object is only cached when it has its visible property set to true. Additionally, if its visibility is turned off, its bitmap representation is discarded.
The following example puts 100 circles on the stage. They animate frantically. Try the code on your device. Click on the stage to toggle the cacheAsBitmap property to true or false and notice the difference in performance. Note that this example is intended to illustrate a point regarding the use of many display objects. If an element always looks the same, as in the example, the best approach would be to create it once and copy it:
[code]
import flash.display.Shape;
import flash.events.Event;
import flash.events.MouseEvent;
var container:Vector.<Shape>;
const MAX:int = 100;
var boundsX:int;
var boundsY:int;
var cacheIndicator:Shape;
var toggleCache:Boolean = true;
container = new Vector.<Shape>;
boundsX = stage.stageWidth;
boundsY = stage.stageHeight;
// to keep track of when cacheAsBitmap is on
cacheIndicator = new Shape();
cacheIndicator.graphics.beginFill(0x999999, 1);
cacheIndicator.graphics.drawRect(5, 5, 50, 50);
cacheIndicator.graphics.endFill();
addChild(cacheIndicator);
[/code]
Create the circles and store them:
[code]
for (var i:int = 0; i < MAX; i++) {
var temp:Shape = createCircle();
container[i] = temp;
addChild(temp);
}
// to toggle between cacheAsBitmap true or false
stage.addEventListener(MouseEvent.CLICK, toggle);
// animate all circles on EnterFrame
stage.addEventListener(Event.ENTER_FRAME, move);
[/code]
Create individual circles with cacheAsBitmap set to true. Also give them an alpha to make the renderer work harder for testing purposes:
[code]
function createCircle():Shape {
var shape:Shape = new Shape();
shape.graphics.beginFill(Math.random()*0xFFFFFF, Math.random()*1);
shape.graphics.drawCircle(0, 0, 50);
shape.graphics.endFill(); shape.x = Math.floor(Math.random()*boundsX);
shape.y = Math.floor(Math.random()*boundsY);
shape.cacheAsBitmap = toggleCache;
return shape;
}
// purposely didn’t optimize code to make it work harder
function move(event:Event):void {
for (var i:int = 0; i < MAX; i++) {
var mc:Shape = container[i];
mc.x += (Math.random()-Math.random())*25;
mc.y += (Math.random()-Math.random())*25;
if (mc.x < 0 || mc.x > boundsX) {
mc.x = boundsX/2;
}
if (mc.y < 0 || mc.y > boundsY) {
mc.y = boundsY/2;
}
}
}
[/code]
Turn cacheAsBitmap on and off to test performance on the device:
[code]
function toggle(event:MouseEvent):void {
toggleCache = !toggleCache;
cacheIndicator.visible = toggleCache;
for (var i:int = 0; i < MAX; i++) {
var mc:Shape = container[i];
mc.cacheAsBitmap = toggleCache;
}
}
[/code]
Toggling the cacheAsBitmap property in a live application is not recommended, but it is a good development and debugging technique.
If the display object is scaled, skewed, or rotated, its bitmap copy needs to be updated over and over. You would therefore lose all the benefits of caching.
The cacheAsBitmapMatrix Property
The cacheAsBitmapMatrix property is a new DisplayObject property. It must always be set, along with cacheAsBitmap, equal to true. There is no option to set it manually in the Flash IDE.
You need to create a Matrix and apply it to the object’s cacheAsBitmapMatrix property:
[code]
import flash.geom.Matrix;
myVector.cacheAsBitmap = true;
myVector.cacheAsBitmapMatrix = new Matrix();
[/code]
The added benefit of using cacheAsBitmapMatrix is that you can also change its alpha, scale it, skew it, and rotate it. And it will stay cached, so you can keep the cached element and make more use of it. You do lose some visual quality, but on devices with very high PPI, the quality loss is typically not noticeable.
Another important new feature is the ability to apply the matrix while the display object is not visible so that you have more control over monitoring the initial caching and its performance hit. Furthermore, once your object is cached, you can change its visibility property and it will stay cached. Be careful not to forget invisible objects on the stage:
[code]
myVector.cacheAsBitmap = true;
myVector.cacheAsBitmapMatrix = new Matrix();
myVector.visible = false;
// object is still cached
[/code]
Let’s try another example, but this time, with cacheAsBitmapMatrix, the shapes can rotate and scale without losing the performance benefit of caching. Again, try it on your device:
[code]
import flash.geom.Matrix;
var container:Vector.<MovieClip>;
var toggleCache:Boolean = true;
const MAX:int = 200;
var boundsX:int;
var boundsY:int;
var myMatrix:Matrix;
container = new Vector.<MovieClip>;
boundsX = stage.stageWidth;
boundsY = stage.stageHeight;
[/code]
Create the Matrix only once and apply it to all the MovieClips:
[code]
myMatrix = new Matrix();
for (var i:int = 0; i < MAX; i++) {
var temp:MovieClip = createCircle();
container[i] = temp;
addChild(temp);
}
[/code]
Animate all the MovieClips on EnterFrame:
[code]stage.addEventListener(Event.ENTER_FRAME, move);[/code]
Create a square-shaped MovieClip. Give it a random alpha, scale, and direction:
[code]
function createCircle():MovieClip {
var mc:MovieClip = new MovieClip();
mc.graphics.beginFill(Math.random()*0x09FFFF, Math.random()*1);
mc.graphics.drawRect(-30, -30, 60, 60);
mc.graphics.endFill();
mc.x = Math.random()*stage.stageWidth;
mc.y = Math.random()*stage.stageHeight;
var scale:Number = Math.random()*1;
mc.scaleX = mc.scaleY = scale;
mc.dir = 1;
// cache it for transformation
mc.cacheAsBitmap = toggleCache;
mc.cacheAsBitmapMatrix = myMatrix;
return mc;
}
[/code]
Scale and rotate individual MovieClips:
[code]
function move(event:Event):void {
for (var i:int = 0; i < MAX; i++) {
var mc:MovieClip = container[i];
mc.scaleX += 0.05*mc.dir;
mc.scaleY += 0.05*mc.dir;
mc.rotation += 5*mc.dir;
if (mc.scaleX < 0.05 || mc.scaleX > 1.0) {
mc.dir *= -1;
}
}
}
[/code]
You should notice a great improvement in tweening animation, even with the large number of objects.
In an effort to preserve memory, all the objects share a single Matrix instance. There is no need to create a unique matrix per object because, once assigned, the Matrix will not be modified.
In our example, the cacheAsBitmapMatrix property is applied to objects in a flat display list. Caching becomes a more meticulous exercise when dealing with the display list hierarchy. We will go over this in the next section.