Flash vs. HTML5: Text Rendering Speed
The “Flash vs. HTML5” series has covered bitmap drawing pretty well by now, but what about text rendering? Virtually every game has text in it and sometimes a lot. Quest text, name tags, button labels, tooltips, and so on combine to fill the screen with quite a bit of the stuff. So how does Flash’s text rendering compare with that of HTML5? Read on to find out!
Much like the bitmap tests, today’s test draws 10,000 strings to a canvas
‘s “2d” context for HTML5 and a BitmapData
for Flash. Two strings are used- a short string (“the”) mostly to measure overhead and a long string (a sentence) to reduce overhead. The result is an unintelligible garble of overlapping strings, but the visual representation isn’t the point. If you want to see more clearly, just lower the REPS
variable to some small value like 10. 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 TextTest extends Sprite { private var shortTF:TextField; private var longTF:TextField; private var screenBMD:BitmapData; private var screenRect:Rectangle; private var drawMat:Matrix; private var time:TextField; public function TextTest() { 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(); var textFormat:TextFormat = new TextFormat(); textFormat.size = 12; textFormat.color = 0xff000000; shortTF = new TextField(); shortTF.autoSize = TextFieldAutoSize.LEFT; shortTF.defaultTextFormat = textFormat; shortTF.text = "the"; longTF = new TextField(); longTF.autoSize = TextFieldAutoSize.LEFT; longTF.defaultTextFormat = textFormat; longTF.text = "the quick brown fox jumped over the lazy dog"; makeButtons("Short", "Long"); time = new TextField(); time.text = "Time: 9999"; time.autoSize = TextFieldAutoSize.LEFT; time.x = stage.stageWidth - time.width; time.y = stage.stageHeight - time.height; time.background = true; time.backgroundColor = 0xffffffff; 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 "Short": test(shortTF); break; case "Long": test(longTF); break; } } private function test(tf:TextField): void { var REPS:int = 10000; var maxX:int = screenRect.width - tf.width; var maxY:int = screenRect.height - tf.height; var beforeTime:int = getTimer(); screenBMD.fillRect(screenRect, 0xffffffff); screenBMD.lock(); for (var i:int = 0; i < REPS; ++i) { drawMat.tx = Math.random()*maxX; drawMat.ty = Math.random()*maxY; screenBMD.draw(tf, drawMat); } screenBMD.unlock(); var afterTime:int = getTimer(); time.text = "Time: " + (afterTime-beforeTime); } } }
You can get the source code of the HTML5 version by simply opening up the HTML file.
Here are the devices tested:
Name | CPU | GPU | OS |
---|---|---|---|
MacBook Pro Retina | 2.3 GHz Intel Core i7 | NVIDIA GeForce GT 650M | 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 |
Motorola Xoom | 1 GHz NVIDIA Tegra 2 T20 | NVIDIA Tegra 2 T20 | Android 4.1 |
And here are the results:
Device | Short | Long |
---|---|---|
HTC Evo V 4G – Android 4 Browser | 349 | 1081 |
HTC Evo V 4G – Google Chrome 27 | 503 | 1585 |
Apple iPad 2 – Safari | 165 | 419 |
LG Optimus G – Android Browser | 418 | 881 |
LG Optimus G – Google Chrome 27 | 309 | 609 |
Motorola Xoom – Android 4 Browser | 301 | 1104 |
Motorola Xoom – Google Chrome 27 | 221 | 564 |
MacBook Pro Retina – Google Chrome 27 | 40 | 84 |
MacBook Pro Retina – Firefox 21 | 119 | 305 |
MacBook Pro Retina – Safari 6 | 26 | 94 |
MacBook Pro Retina – Flash Player 11.7 | 509 | 673 |
Windows Desktop – Google Chrome 27 | 38 | 98 |
Windows Desktop – Firefox 21 | 152 | 278 |
Windows Desktop – Internet Explorer 10 | 124 | 202 |
Windows Desktop – Flash Player 11.7 | 310 | 570 |
From this data we can draw some interesting conclusions:
- Flash renders text 3-8x slower than HTML5 depending on OS, browser, and string length
- Flash’s text rendering is so slow that it is about on par with HTML5 on mobile despite a huge CPU advantage
- All browsers and Flash take about 2-3x longer to render the long text than the short text despite the long text being 15x longer than the short text. Overhead is costly in text rendering!
- Safari on iOS is 2-3x faster than the Android Browser or Chrome for Android on devices with similar CPUs (e.g. iPad 2 is similar to Motorola Xoom)
- On desktops, Firefox is about 3x slower than Chrome and marginally slower than Internet Explorer. Safari is on par with Chrome and essentially ties for best performance.
- On mobile, there is no clear winner between the Android Browser and Chrome. Sometimes Chrome is faster than Android Browser and sometimes not. Sometimes one is 2x faster than the other.
As seems to be the theme with this series of articles, you need to plan for quite a lot of variation based on OS and browser if you’re going the HTML5 route. Targeting such a wide array means that you’ll need to expect up to a 12x difference in performance from the fastest desktop to the slowest mobile, more if you’re counting older mobile devices like iPad 1. Even on the same device you can expect a 2-3x performance gap depending on browser choice. And one thing’s for certain: if you’re going with Flash you need to expect very slow text rendering compared to HTML5. Make good use of cacheAsBitmap
or GPU textures for those strings that don’t change frequently&em;most of them&em;and you may even see a performance advantage over HTML5 as in previous articles in this series.
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, short and long times, and if you have them or can look them up, CPU and GPU specs.
#1 by Ben on July 8th, 2013 ·
Mac Mini (Os X 10.8.3 2.4 GHz Core 2 Duo, 4 GB DDR3 1333 MHz):
Safari 6.0.4:
HTMl 5: Short 43; Long 192 (sometimes over 500; seems pretty inconsistent)
Flash: Short 554; Long 823
Opera 12.16:
HTMl 5: Short 392; Long 2347
Flash: Short 565; Long 853
Firefox 22:
HTMl 5: Short 203; Long 528
Flash: Short 541; Long 796
——–
Windows 7 64 Bit (i7-3770 @ 3.4 GHz; 32 GB RAM):
Opera 12.16:
HTMl 5: Short 153; Long 833
Flash: Short 301; Long 586
Firefox 22:
HTMl 5: Short 147; Long 258
Flash: Short 305; Long 547
Internet Explorer 10:
HTMl 5: Short 122; Long 192
Flash: Short 274; Long 530
#2 by ben w on July 8th, 2013 ·
Interesting results. When I run the HTML short test in IE I get ~270ms vs 50ms in chrome, huge difference!
That said text is not likely to be rendered in such a fashion frequently in real world apps, I am sure static text could all be cached beforehand anyway so the majority can be blitted without the need for drawing text.
#3 by jackson on July 9th, 2013 ·
It’s true that text is often cached as a bitmap to avoid re-drawing, but sometimes you can’t help it. For example, an MMO game may have dozens of players on screen at once all chatting to each other in pop-up word bubbles. Or the game may have a large amount of scrollable quest text that pops up over the main scene. It definitely won’t be as extreme as the 10,000 strings as in the article, but even a tenth of that would yield less than 10 FPS on some browser/device combinations (Motorola Xoom – Android 4 Browser) and a hundredth of that would make smooth frame rates difficult to achieve.
That said, I do agree that if you’re doing a good job caching to bitmaps or textures and your game isn’t text-heavy then you’ll probably never have to worry about text rendering performance. For text-heavy scenarios though, there could be major problems on some browser/device combinations.
#4 by jrmcv on July 14th, 2013 ·
We have had this issue at work although we aim for windows/osx machines and not mobile. We load our games in a loader written by a 3rd party and it logs everything in the background. Even if we disable the logger so the log window textfield isn’t attached to the stage, it can still cause huge frame drops every time something is logged. At first we couldn’t believe the frame drops could be from the logging until we saw through scout that almost 80% of total rendering time was for text rendering. A different loader is used for released games so its not that much of an issue although it can make development a bit awkward. It does make me think a bit longer before adding text to flash though – even for debugging tools.
#5 by Josh Tynjala on July 8th, 2013 ·
I’m curious if rendering text with Flash Text Engine is faster or slower than TextField.
#6 by Etherlord on July 8th, 2013 ·
FTE is probably slower:
http://stackoverflow.com/questions/16316165/does-the-flash-text-engine-display-faster-than-typical-textfields
But it has a nicer quality than TextField, and it has some flow features (like justification) that as far as I know, HTML5 canvas lacks.
TLF is many orders of magnitude slower…
#7 by Matt on July 9th, 2013 ·
FTE is extremely slow. Much, much slower than TextField. I had a deep relationship with FTE and it’s buggy and incomplete and slow as hell.
#8 by Mario on July 10th, 2013 ·
Any idea if using the GPU may help in this case?
firetype library, for instance:
https://github.com/MaxDidIt/firetype
#9 by jackson on July 10th, 2013 ·
Sounds like a cool library. I haven’t tested it, but I’m guessing it’s slower to set up the font but then much quicker to render. For
Stage3D
-based games, this would be a huge help compared to rendering aTextField
, drawing it to aBitmapData
, uploading it to aTexture
, and using that texture on a quad.#10 by Triynko on October 28th, 2013 ·
Drawing to BitmapData isn’t really a fair comparison. Since Flash is technically already rendering to its own plugin canvas; a more fair comparison would be to just add 10,000 TextFields to random locations in a Flash Display object and allow it to render one frame. It achieves exactly what you’re trying to do by rendering to a canvas, except Flash achieves it much faster.
Even if we allowed the extra (and quite unnecessary) step of rendering that 10,000 text-field-containing display object to a BitmapData object, it’s nearly twice as fast as drawing individual text fields to it. So, for example, if you were to set it up in advance, begin a timer, lock the bitmap data, randomly update the positions of those 10,000 textfield instances in the display object, call the draw command to render the display object into the bitmap data object, unlock it, and end the time… it would finish in half the time as what you’re trying to do here. Still, as I said, without that extra and unnecessary step of drawing to a bitmap data object, Flash can already natively do anything you’re trying to render on a canvas much faster, because it already handles clipping, caching, GPU acceleration, not to mention all the filters and 3D matrix transform features of a Flash display object.
You’re comparing apples to oranges here.
#11 by jackson on October 28th, 2013 ·
Flash only draws a
DisplayObject
at two times. The first is the most obvious: when Flash is rendering theStage
. The second is a special case:BitmapData.draw
. This means that you can do all of the setup you want on aTextField
and it will not be drawn into any buffer, canvas, screen or any other place until one of the two events occurs: a frame needs to be drawn to the screen or you explicitly callBitmapData.draw
.In the article I am using
BitmapData.draw
to gain maximum control over the timing of theTextField
rendering. I callgetTimer
before and after the calls toBitmapData.draw
so I’m sure that the only work Flash is doing is the rendering of theTextField
.In HTML5 I am doing something very similar. There’s no
TextField
object, so strings must be rendered directly to the 2D context of the<canvas>
tag. Again I wrap thesefillText
calls in timers to ensure that the only work being done is the actual rendering of the text.To me, these two seem very equivalent. In neither case is the text already rendered to some other place as a bitmap and the bitmap simply copied. In both cases I’m ensuring that I’m measuring only the time that the draw calls take, not inter-frame work like event dispatching, vertical sync, etc.
There is, however, one case that may skew the results of my tests. Since the text that’s being rendered happens to be the same string with the same settings (e.g. size) every time, the text could be cached somewhere rather than being re-rendered with each draw call. I’m not sure if this is actually happening in any environment I tested, but it’s an optimization that either Flash or any of the web browsers could have implemented. I don’t think that it’s happening in Flash as it would seem to make more sense if Flash were to save on the memory costs of keeping a per-
TextField
bitmap around until you explicitly set thecacheAsBitmap
field on it totrue
. Given discussions like this, it seems even less likely since the result would be sub-optimal text quality.