Composing BitmapData Scenes
Since Flash 8, BitmapData
has offered a wide range of possibilities to improve performance. Many Flash apps, particularly games, choose to simply display a single, huge BitmapData
, render the entire game scene into it and, for the most part, eschew Stage
‘s whole system of DisplayObject
and DisplayObjectContainer
. When you’re doing this or just generally using BitmapData
for more than just raster (e.g. JPEG, PNG) image display, you should know your options for composing a BitmapData
out of other BitmapData
. Today’s article discussing the performance of the two main ways of composing these BitmapData
scenes: BitmapData.draw
and BitmapData.copyPixels
.
For starters, its worth pointing out that BitmapData.draw
and BitmapData.copyPixels
are two very different functions. For example, BitmapData.draw
can draw any DisplayObject
and optionally supports:
- A transformation matrix (e.g. for scaling or rotating)
- A color transform (e.g. for tinting or grayscale)
- A custom blend mode
- A clipping rectangle (i.e. to draw only a portion of the source)
- Smoothing (i.e. to reduce jagginess)
On the other hand, BitmapData.copyPixels
can only copy from another BitmapData
and supports fewer, yet different options:
- A clipping rectangle
- An “alpha”
BitmapData
whose alpha values should be used instead
Usually, the API with fewer features will be quicker. In this case, it seems as though BitmapData.copyPixels
was custom-designed to copy chunks of pixels from one BitmapData
to another (i.e. “bit blitting“) and should perform very well against the jack-of-all-trades BitmapData.draw
. To find out, let’s look at a performance test that uses the two functions on big and small BitmapData
.
package { import flash.display.*; import flash.utils.*; import flash.text.*; import flash.geom.*; public class CopyingPixels extends Sprite { private var __logger:TextField = new TextField(); private function row(...cols): void { __logger.appendText(cols.join(",")+"\n"); } public function CopyingPixels() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; var logger:TextField = __logger; logger.autoSize = TextFieldAutoSize.LEFT; addChild(logger); var beforeTime:int; var endTime:int; var bigToBigTime:int; var bigToSmallTime:int; var smallToBigTime:int; var smallToSmallTime:int; var i:int; const REPS:int = 10000; var bmdBig:BitmapData = new BitmapData(1000, 1000); var bmdBig2:BitmapData = new BitmapData(1000, 1000); var bmdSmall:BitmapData = new BitmapData(250, 250); var bmdSmall2:BitmapData = new BitmapData(250, 250); const COPY_DIM:int = 220; const COPY_RECT:Rectangle = new Rectangle(0, 0, COPY_DIM, COPY_DIM); const DEST_POINT:Point = new Point(5, 5); const DRAW_MATRIX:Matrix = new Matrix(1, 0, 0, 1, 5, 5); row( "Function", "Big-to-Big", "Big-to-Small", "Small-to-Big", "Small-to-Small" ); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdBig.copyPixels(bmdBig2, COPY_RECT, DEST_POINT); } endTime = getTimer(); bigToBigTime = endTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdSmall.copyPixels(bmdBig, COPY_RECT, DEST_POINT); } endTime = getTimer(); bigToSmallTime = endTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdBig.copyPixels(bmdSmall, COPY_RECT, DEST_POINT); } endTime = getTimer(); smallToBigTime = endTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdSmall.copyPixels(bmdSmall2, COPY_RECT, DEST_POINT); } endTime = getTimer(); smallToSmallTime = endTime - beforeTime; row( "copyPixels", bigToBigTime, bigToSmallTime, smallToBigTime, smallToSmallTime ); beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdBig.draw(bmdBig2, DRAW_MATRIX, null, null, COPY_RECT); } endTime = getTimer(); bigToBigTime = endTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdSmall.draw(bmdBig, DRAW_MATRIX, null, null, COPY_RECT); } endTime = getTimer(); bigToSmallTime = endTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdBig.draw(bmdSmall, DRAW_MATRIX, null, null, COPY_RECT); } endTime = getTimer(); smallToBigTime = endTime - beforeTime; beforeTime = getTimer(); for (i = 0; i < REPS; ++i) { bmdSmall.draw(bmdSmall2, DRAW_MATRIX, null, null, COPY_RECT); } endTime = getTimer(); smallToSmallTime = endTime - beforeTime; row( "draw", bigToBigTime, bigToSmallTime, smallToBigTime, smallToSmallTime ); } } }
I ran this performance test with the following environment:
- Flex SDK (MXMLC) 4.5.1.21328, compiling in release mode (no debugging or verbose stack traces)
- Release version of Flash Player 10.3.183.7
- 2.4 Ghz Intel Core i5
- Mac OS X 10.7.1
And got these results:
Function | Big-to-Big | Big-to-Small | Small-to-Big | Small-to-Small |
---|---|---|---|---|
copyPixels | 256 | 247 | 242 | 231 |
draw | 1474 | 1447 | 1443 | 1423 |
Clearly BitmapData.copyPixels
has a huge advantage regardless of image size. If you’re going to draw a non-BitmapData
several times (e.g. every frame), it’s definitely worth it to draw it once to a BitmapData
and then use BitmapData.copyPixels
from that source. On a related note, smaller BitmapData
tends to perform better than larger BitmapData
, so try to keep your images small if you’re trying to eek out some extra performance.
Spot a bug? Have a suggestion? Post a comment!
#1 by Henke37 on September 19th, 2011 ·
Let’s compare the method over the four different alpha/non alpha pairs!
#2 by jackson on September 19th, 2011 ·
It’s tempting…
#3 by Josh on September 19th, 2011 ·
Your chart doesn’t specify a unit other than “performance”. I assume it’s milliseconds from context, but you should specify that somewhere, for clarity’s sake.
#4 by AlexG on September 19th, 2011 ·
You can see in the source code.
It looks like the results are in ms for 10000 bitmaps.
var bmdBig:BitmapData = new BitmapData(1000, 1000);
var bmdSmall:BitmapData = new BitmapData(250, 250)
And the sizes are 1000x1000px and 250x250px
#5 by AlexG on September 19th, 2011 ·
So it looks like copying pixels within bitmaps of 250x250px takes almost the same time as copying on 1000x1000px. I was thinking to improve performance by splitting a big bitmap into 4 equal bitmaps and coying pixels to them, instead of copying to a single bitmap. But it looks that way will be no improvement.
#6 by ben w on September 20th, 2011 ·
be interesting to see if it was faster with 256 and 1024 as the sizes… compooters like those numbers ;)
#7 by as3isolib on September 22nd, 2011 ·
when rewriting the as3isolib core engine from the display list to a blitting system, I ran several tests for these two APIs. I think a few flash player versions ago, there was some differences, but found that when 10.1 and 10.2 were released, clearly copyPixels speeds by.
I haven’t moved to the FP10.3 yet so I can’t say the benefits of blitting still outweigh all the display list features you’re missing. From what I have heard, 10.3 has really optimized the hell out of the display list APIs such that blitting may be losing ground. I imagine at some point, they may try to move those APIs over to the GPU in FP11? If that occurs, then blitting may be unnecessary moving forward. I’ll believe it when I see it though!
#8 by jackson on September 22nd, 2011 ·
It looks like they’ve already started with Starling.
#9 by Richard Davey on October 4th, 2011 ·
On Flash Player 11 (11.0.1.152 Release Stand-alone) I get:
copyPixels: 516, 243, 232, 175
draw: 659, 644, 642, 606
If you drop the Stage Quality down to LOW, then draw benefits from this quite significantly:
copyPixels: 456, 246, 228, 175
draw: 542, 518, 532, 484
Rather than blanket-set the quality to low you can set it just before the draw op, and then restore the quality setting to whatever you need after it.
This is on a 2.93 GHz Core i7 Win 7 64-bit, however Stand-alone player only runs in 32-bit. wmode direct has no impact on the speed (nor did I expect it to). Using images that are powers of 2 (so 1024 instead of 1000 and 256 instead of 250) made no difference at all, it just took a little bit longer to render.
#10 by Pier on January 28th, 2013 ·
Instead of lowering the quality every time you can use drawWithQuality(). It’s exactly the same method as draw() except you can define a quality value as the last parameter.
#11 by jackson on January 29th, 2013 ·
Good tip. Just remember to target Flash Player 11.3+ or AIR 3.3+ since it hasn’t been there until recently (after Richard’s comment was posted).