Flash vs. HTML5: Bitmap Rotation and Scaling
Normal bitmaps can be boring, so many games spice them up by rotating and scaling them for various purposes. Rotated characters can follow the curve of a 2D terrain in a game like Dragon, Fly!. Mario himself can scale up to huge size in New Super Mario Bros.. So today we continue the HTML5 vs. JavaScript series by testing the performance of rotating and scaling bitmaps to better compare the performance across Windows, Mac, iOS, and Android. Will Flash maintain its lead? Read on to find out.
Today’s test builds off the last test by adding buttons and functionality for bitmap rotation and scaling. Unfortunately for Flash and AS3, this means that BitmapData.copyPixels
can no longer be used. How will the conversion to BitmapData.draw
affect performance? Try out the tests to see for yourself and then check out my results below.
Launch the HTML5 version
Launch the Flash version
Here is the source code for the Flash version:
package { import flash.geom.Matrix; import flash.events.MouseEvent; import flash.text.TextFormat; import flash.geom.Rectangle; import flash.utils.getTimer; import flash.text.TextFieldAutoSize; import flash.display.StageScaleMode; import flash.display.StageAlign; import flash.text.TextField; import flash.display.Bitmap; import flash.display.BitmapData; import flash.display.Sprite; public class BitmapScaleRotFlash extends Sprite { [Embed(source="opaque.jpg")] private static const OPAQUE:Class; [Embed(source="alpha.png")] private static const ALPHA:Class; private var opaqueBMD:BitmapData = (new OPAQUE() as Bitmap).bitmapData; private var alphaBMD:BitmapData = (new ALPHA() as Bitmap).bitmapData; private var screenBMD:BitmapData; private var screenRect:Rectangle; private var drawMat:Matrix; private var time:TextField; public function BitmapScaleRotFlash() { stage.align = StageAlign.TOP_LEFT; stage.scaleMode = StageScaleMode.NO_SCALE; screenRect = new Rectangle(0, 0, stage.stageWidth, stage.stageHeight); screenBMD = new BitmapData(640, 480); addChild(new Bitmap(screenBMD)); drawMat = new Matrix(); makeButtons( "Alpha - Rotation", "Alpha - Scaling", "Alpha - Rotation & Scaling", null, "Opaque - Rotation", "Opaque - Scaling", "Opaque - Rotation & Scaling" ); time = new TextField(); time.text = "Time: 9999"; time.autoSize = TextFieldAutoSize.LEFT; time.x = stage.stageWidth - time.width; time.y = stage.stageHeight - time.height; addChild(time); } private function makeButtons(...labels): Number { const PAD:Number = 5; var curX:Number = PAD; var curY:Number = stage.stageHeight - PAD; for each (var label:String in labels) { if (label == null) { curX = PAD; curY -= button.height + PAD; continue; } var tf:TextField = new TextField(); tf.mouseEnabled = false; tf.selectable = false; tf.defaultTextFormat = new TextFormat("_sans"); tf.autoSize = TextFieldAutoSize.LEFT; tf.text = label; tf.name = "lbl"; var button:Sprite = new Sprite(); button.buttonMode = true; button.graphics.beginFill(0xF5F5F5); button.graphics.drawRect(0, 0, tf.width+PAD, tf.height+PAD); button.graphics.endFill(); button.graphics.lineStyle(1); button.graphics.drawRect(0, 0, tf.width+PAD, tf.height+PAD); button.addChild(tf); button.addEventListener(MouseEvent.CLICK, onButton); if (curX + button.width > stage.stageWidth - PAD) { curX = PAD; curY -= button.height + PAD; } button.x = curX; button.y = curY - button.height; addChild(button); curX += button.width + PAD; } return curY - button.height; } private function onButton(ev:MouseEvent): void { var mode:String = ev.target.getChildByName("lbl").text; switch (mode) { case "Opaque - Rotation": test(opaqueBMD, true, false); break; case "Opaque - Scaling": test(opaqueBMD, false, true); break; case "Opaque - Rotation & Scaling": test(opaqueBMD, true, true); break; case "Alpha - Rotation": test(alphaBMD, true, false); break; case "Alpha - Scaling": test(alphaBMD, false, true); break; case "Alpha - Rotation & Scaling": test(alphaBMD, true, true); break; } } private function test(bmd:BitmapData, rotation:Boolean, scaling:Boolean): void { var REPS:int = 10000; var maxX:int = screenRect.width - bmd.width; var maxY:int = screenRect.height - bmd.height; var beforeTime:int = getTimer(); screenBMD.fillRect(screenRect, 0xffffffff); screenBMD.lock(); for (var i:int = 0; i < REPS; ++i) { drawMat.a = drawMat.d = 1; drawMat.b = drawMat.c = 0; if (scaling) { drawMat.a = 1+Math.random(); drawMat.d = 1+Math.random(); } if (rotation) { drawMat.rotate(Math.random()*Math.PI*2); } drawMat.tx = Math.random()*maxX; drawMat.ty = Math.random()*maxY; screenBMD.draw(bmd, drawMat); } screenBMD.unlock(); var afterTime:int = getTimer(); time.text = "Time: " + (afterTime-beforeTime); } } }
And here are the images it embeds:
You can get the source code of the HTML5 version by simply opening up the HTML file. Its embedded images are Base64-encoded into the img
tags.
Here are the devices tested: (note that the Windows Desktop device has changed since last time)
Name | CPU | GPU | OS |
---|---|---|---|
MacBook Pro Retina | 2.3 GHz Intel Core i7 | Intel HD Graphics 4000 | OS X 10.8.3 |
Windows Desktop | 3.4 GHz Intel Core i7 2600 | Nvidia GeForce GTX 550 Ti | Windows 7 SP 1 |
iPad 2 | 1 GHz ARM Cortex-A9 | PowerVR SGX543MP2 | iOS 6.1.3 |
LG Optimus G | 1.5 GHz Qualcomm Krait | Qualcomm Adreno 320 | Android 4.1 | HTC Evo V 4G | 1.2 GHz Qualcomm Snapdragon | Qualcomm Adreno 220 | Android 4.0 |
And here are the results:
From this data we can draw some interesting conclusions:
- Flash is in last place on Windows by a wide margin of about 3x. On Mac it’s also about 3x slower than Safari and Chrome but Firefox lags behind the browser competition nearly the point where it’s as slow as Flash.
- The slowdown in Flash compared to the plain bitmap draws last week mirrors the slowdown scene in Composing BitmapData Scenes.
- Safari is the fastest mobile browser by far. On the iPad 2 it even bests much faster devices like the LG Optimus G and is nearly as fast as the slower times on Windows and Mac machines.
- There isn’t much difference between opaque and alpha images regardless of environment. The same goes for rotation and scaling: they’re about the same cost.
Flash was looking great in the results of the last article but this time around we’ve seen a dramatic reversal: it’s in last place on both Windows and Mac. On Android and iOS it doesn’t even get a chance to get started in browsers, only as AIR apps (not tested). Between the two tests these are probably the more realistic results for most games, but this is still far from a full performance comparison. Stay tuned for more in this series to see if Flash can make a comeback!
As a disclaimer with mobile, there are many devices out there with wildly different performance characteristics. Even iOS has five base models of iPad, six of the iPhone, and five of the iPod Touch. There are hundreds to thousands of Android devices. It’s therefore really hard to get a complete picture of mobile performance since very few people have access to so many devices. I certainly don’t. However, I’m guessing many people reading this article have a couple of minutes to point their mobile browsers at the above tests. Want to contribute your own test results? Post a comment with your device name, opaque and alpha times, and if you have them or can look them up, CPU and GPU specs.
#1 by Jonathan on June 17th, 2013 ·
I have a really bad quality for the images (alpha tests) of the Flash version.
#2 by ben w on June 17th, 2013 ·
@Jonathan, maybe if he turned on smoothing in the draw function it would look a lot better.
10,000 matrix rotations in flash is going to be slow and in reality most objects in games will be static, even if rotated that will only need to have their transforms set the once.
If you took out the matrix operations it would likely change the outlook.
a busy game screen may have 100 static objects and say 50 dynamic ones, of which most will not be rotating every frame anyway.
It does appear that the canvas matrix methods are much faster. I would think that is where the difference is, not the actual drawing phase. I could be wrong though but a simple test would be to cache the transformations and draw it again with the cached ones so you are just checking the speed of drawing with a transformation matrix not actually changing them each time, as it stands there is no way of knowing where the bottleneck really is. Interesting test though and as ever I could be completely wrong.
:)
#3 by Till Schneidereit on June 17th, 2013 ·
@ben: I’m fairly sure that it’s the drawing operations themselves. I don’t have a Windows machine, so I can’t test there, but at least on OS X, and at least in Firefox, profiling this shows almost all time spent in the graphics backend. On OS X, Firefox’s Canvas backend is known to be fairly slow, which is why it’ll be replaced by a *much* faster one, soon.
In current Nightly versions of Firefox, the new backend can be activated by going to about:config, and switching the setting “gfx.canvas.azure.accelerated” to true. For me (on a retina MBP), this changes the numbers from
Opaque
rot/scale/rot+scale
102/125/180
107/132/186
to
Alpha
rot/scale/rot+scale
13/19/13
18/19/18
Given that the matrix calculations aren’t implemented in the gfx backend, they can’t figure into things, too much.
#4 by jackson on June 17th, 2013 ·
Sounds great. I’d love to re-test when these changes make it into the stable releases. Any idea what version that’s slated for?
#5 by ben w on June 17th, 2013 ·
You might be right, a quick test in flash with transforms show that less that 10ms of the time is done transforming (if you comment out the draw call altogether). 5-6ms to rotate, 2-3ms to scale, 2-3ms to scale, 2ms to translate, 10 to do all of em. Some overhead in calling Math.random() 50,000 times.
If you draw with a null transform the time is 130ms ish
If you draw with an identity transform the time is 130ms ish
If you draw with an translated transform the time is 130ms ish
If you draw with an translated and scaled transform the time is 110 – 150ms ish scaled from 0.5 – 1.5)
If you draw with an translated and scaled and rotated transform the time is 400+ms
rotation is a big doozy here :(
turn on smoothing and times get worse
change stage.quality down to “low” though and boom much much faster.
stage3d ftw?