Re-use your BitmapData objects, speed up your apps

December 12th, 2006

This is a post that I started writing about 5 months ago, and I may not be posting for a while, so here’s some info I gathered when doing per-frame analysis of Onyx‘s bitmap rendering engine …. I tried to optimize per-frame bitmapdata drawing, since there were so many bitmapdata events happening on a per frame basis. The flow for one layer was as follows:

var bitmap = new BitmapData();
bitmap.draw(layer);
filter.apply(bitmap);
filter.apply(bitmap);
filter.apply(bitmap); // repeat for as many filters are added into the layer

preview.draw(bitmap, scaleDownMatrix);

Now on average, even without any filters being applied, drawing one layer was taking 8ms on a really really fast machine. With 5 active layers in their simplist state – that’s 40ms — which is already reducing the application to around 24fps. Not so great. Now there’s not really a ton you can do about it, but hey, if you can speed up your operations by 1ms or 2ms, that will make a big difference if you’re running 5 of those processes. Here’s what little I found about BitmapData operations:

Expensive Operations (from most expensive to least expensive):

BitmapData.draw() – drawing content into a bitmap
new BitmapData() – creation of a new bitmap in memory
BitmapData.clone() – clones a bitmap
BitmapData.fillRect() – fill a bitmap with a color
BitmapData.copyPixels() – Copy pixels from a bitmap to another

10000 operations of “BitmapData.draw()” 4068 ms
10000 operations of “new BitmapData()” : 2324 ms
10000 operations of “BitmapData.clone()” 1012 ms
10000 operations of “BitmapData.copyPixels()” 962 ms
10000 operations of “BitmapData.fillRect()” : 515 ms

Methods to speed up bitmap operations.

1) When doing per-frame bitmap operations, create and store a bitmap outside of the per-frame operation, and do a fillrect per-frame.

Slower:
- creation of a bitmapdata
- bitmapdata.draw on an object every frame

Faster:
- Create and store a bitmapdata
- on every frame fillRect with 0×00000000, then do your bitmapdata.draw

Slower:
- BitmapData.clone() into a newly created bitmapdata

Faster:
- Create and store a bitmap in memory
- BitmapData.copyPixels into another bitmapdata object

Below is the code i used for the tests (i executed the code a second after the plug-in started). If I’m wrong with any of this, I give you permission to shoot me.

/**
 * 	Testing bitmap speed operations
 */
public function test():void {

	var start:int, count:int, times:int;

	times = 10000;

	var bmp1:BitmapData = new BitmapData(320,240,true,0x00000000);
	var bmp2:BitmapData = new BitmapData(320,240,true,0x00000000);

	start = getTimer();
	for (count = 0; count < times; count++) {
		bmp2.fillRect(bmp2.rect, 0x00000000);
	}
	trace(times, 'operations of "BitmapData.fillRect()" :', getTimer() - start, 'ms');

	start = getTimer();
	for (count = 0; count < times; count++) {
		bmp1 = new BitmapData(320,240,true,0x00000000);
	}
	trace(times, 'operations of "new BitmapData()" :', getTimer() - start, 'ms');

	start = getTimer();
	for (count = 0; count < times; count++) {
		var bmp3:BitmapData = bmp2.clone();
	}
	trace(times, 'operations of "BitmapData.clone()"', getTimer() - start, 'ms');

	start = getTimer();
	for (count = 0; count < times; count++) {
		bmp1.copyPixels(bmp2, bmp2.rect, new Point(0,0));
	}
	trace(times, 'operations of "BitmapData.copyPixels()"', getTimer() - start, 'ms');

	start = getTimer();
	for (count = 0; count < times; count++) {
		bmp1.draw(bmp2);
	}
	trace(times, 'operations of "BitmapData.draw()"', getTimer() - start, 'ms');
}