HTML5 Games 0.1: Speedy Sprites
Speed, it seems to me, provides the one genuinely modern pleasure.
Aldous Huxley, 1949
Bruce Rogers and I graduated from Facebook’s Engineering Bootcamp in January and began researching how HTML5 could apply to games across the Web. We found HTML5 poised to become a potent platform for game development but still hampered by significant performance variance among browsers and drawing techniques. We're hosting a tech talk this evening on what's becoming possible with HTML5 this evening along with speakers from Zynga and SproutCore which will be streamed live.
With wide adoption and industry support, HTML5 will transform desktop and mobile gaming, creating amazing user experiences that are only a link away. Already, over 125 million people visit Facebook using HTML5 capable browsers just from their mobile phone, and that number skyrockets when we add in desktop browsers. The future is clear.
So we spent the last few weeks -- and one hackathon -- building JSGameBench and we think version 0.1 is ready for release. As with any new project, there will be numerous bugs to fix and new ideas to try. We fully expect to have missed easy ways to achieve higher sprite performance on various browsers and look forward to incorporating those patches as well as really creating a larger dialog around building high performance 3D games using HTML5. We’re really passionate about working with others to collectively move this entire platform forward.
The User Viewpoint
JSGameBench exists to explore HTML5’s game performance limits. For version 0.1, the focus is sprite performance a player is likely to see. Scoring is how many sprites are drawn, so large scores are better. Going forward, we want to collect peak and average scores for browsers across a range of hardware configurations. We hope JSGameBench helps the community improve game performance and we look forward to HTML5 game engine developers improving on our ideas and crushing these numbers!
For each render path, JSGameBench draws as many moving, animating sprites as possible at 30fps against a background with both axis-aligned and rotated sprites. We try both because significant performance differences between the two indicate flaws or oversights in current rendering techniques. More importantly, while animation can be used instead of sprite rotations, it is often an unacceptable tradeoff that game developers should not be forced to make.
The final score is the geometric mean of the axis aligned and rotated scores. Geometric mean is used to prevent a high axis aligned score from hiding the poor rotated performance.
What we found was while there is an incredibly large range of performance in these tests when comparing browser versions across different operating systems, every browser tested could play some games and the high-end performance was spectacular.
On Windows, both Internet Explorer 9 and Google Chrome 10 are able to push an incredible number of sprites at high frame rates. We view 1000 sprites per frame as an important performance milestone, as that level of draw performance provides great flexibility to developers. Both are almost certainly leveraging hardware acceleration throughout their render paths.
That is not yet the case for the middle range. The middle performers cover a large spectrum, ranging from 50 to 200 sprites. While this range does not allow developers to create every type of game, 50+ sprites is sufficient performance to explore most game genres. These render counts likely indicate incomplete hardware acceleration. For these browsers, either final compositing or individual element rendering is happening in software, greatly reducing throughput.
The remaining browsers push 20 to 40 sprites per frame at 30fps, more if the developer dropped to lower frame rates. Many game concepts are still possible with this number of sprites.
We found the performance results to be very encouraging. Both improvements to our code and continued browser development will only push these results upward.
Next Steps for Us
The 0.1 release of JSGameBench is just the beginning. In addition, we think there are some obvious technologies to work on going forward:
JSGameBench generally works on mobile browsers, but properly abstracted touch and gesture events are key to games working across multiple phones. There are already libraries trying to provide this layer, so we will investigate those first.
We dropped socket.io into JSGameBench late last week and certainly did not do everything correctly, so revising the networking layer and various library options is on the list.
Prediction and synchronization
Along with a solid networking layer, the next step for great synchronous games is a proper set of prediction and synchronization tools. We have all written this code multiple times, so we plan to take a hard look at whether it can be effectively generalized.
Full reference engines
Some Thoughts for Browser Developers
Mobile auto-rotate control
The performance difference between full hardware rendering paths and partial software paths is clear.
Accelerated matrix/transform properties, not CSS transitions
Although CSS transitions can provide a nice speed increase on some browsers, syncing game motion to CSS transition movement is a nightmare. A far simpler solution for game developers is to ensure matrix/transform properties are accelerated.
Can’t we all get along?
Browser inconsistencies are nothing new, but disagreement about tag transform inheritance or whether offset components of matrix transformation should have “px” labels are making code more complex than it needs to be.
Wrapping Up For Now
JSGameBench explores and compares various methods for high performance sprite rendering in browsers . This is just a first step, and we fully expect to improve the performance, compatibility, and flexibility of this code. It is available now on GitHub with an Apache license. This code is not yet ready to run a game, but we believe you will be able to make faster and better games by building off of what we learn. We can’t wait to see what you do!
So, You’ve Read This Far...
When considering whether a platform has sufficient performance to build games, a few rules of thumb can provide a good place to start:
Thus, the kinds of games developers are able to create revolve around what the platform allows you to draw at interactive frame rates. Only capable of drawing a few sprites at 10fps? Focus on games that only require typing and rare clicks. Dozens of sprites at 20fps? Build an experience that focuses on a few moving elements at any moment. Hundreds of sprites at 30fps? Create a full real-time strategy game or action game!
Sprites vs. 3D
Most games built on modern game consoles, mobile, and desktop platforms predominantly use hardware to create 3D scenes. Although WebGL is on the way, today the HTML5 sweet spot is going to be 2D and isometric games, so the question becomes how to draw as many sprites as possible at interactive frame rates.
In the modern 3D game world, drawing is straightforward, leveraging either DirectX or OpenGL variants. For the Web, it’s a bit more complicated.
One option is to leverage the canvas element. The canvas tag provides a 2D buffer that will feel familiar to most game developers. While it lacks modern niceties like depth buffering, it allows pixel manipulation and promises to be hardware accelerated over time. There are only a few different approaches for creating the scene:
A second option is to recognize that Web browsers have been drawing sprites since Marc Andreessen introduced the tag in 1993 and modern browsers are quite good at handling document object models. Unfortunately, drawing with the DOM is more complex than using the canvas tag. Worse, manipulating the DOM just looks weird to developers used to GL.
Rendering choices include:
- Again, sprite sheets vs. individual sprites?
- For sprite sheet, div elements with CSS background vs. div masking img element?
<div style="background:url('s.png’);background-position: -5px -5px"></div>
<div><img src=’s.png’ style="left:-5px;top:-5px"></img></div>
- Transform style property with matrix vs. rotate?
sprite.style.left = '37px';
sprite.style.top = '54px';
sprite.style.transform = 'rotate(1.234rad)';
sprite.style.transform = 'matrix(0.33,0.94,-0.94,0.33,37,54)';
- Use CSS transitions?
sprite.style.transition = 'left 1s linear, top 1s linear';
- innerHTML vs. Updating?
viewport.innerHTML = spriteArray.join('');
sprite.style.left = '54px';
So How Are the Tests Run?
The tests are run in random order, blowing away the DOM between tests before reloading the approximately 6MB of texture data used per test. We considered reloading the page for each test, but went with random order instead because if a browser needs to fully reload pages between tests to achieve maximum performance, the browser is doing something wrong. Game developers should be able to load new levels and game entities without constantly being forced to reload the page.
Things that Make You Go Hmmm?
Just a few of the discoveries/bugs that kept us up late working on JSGameBench:
- In Chrome 10 on OS X, when rendering in <canvas> the first texture call needs a slightly non-identity matrix to kick in accelerated rendering
- In IE9 platform preview, the browser will run at 9fps unless you open the debug window
- In Mobile Safari on iPhone, <script> long polling seems to trigger some kind of page loading mode, which updates the screen at 1fps unless there is a user event
- Browsers split on whether to apply transform to replaced <img> elements
Is Facebook Making Games?
Definitely not. We think the best way to share knowledge is through code and examples, so we will continue to refine JSGameBench and experiment with what is possible. We also believe there is a knowedge gap in game development we would like to help fill.
There are great teams and companies building on the Web, creating app store games, and building games for consoles. They face difficult but largely different challenges.
When we look at what HTML5 developers will need to master, we see a blend of all of these skills. JSGameBench is one step towards filling that gap.
That’s all for now folks. See you on GitHub!
 Tests were performed on a 2.4 GHz Mac Pro with 16GB of RAM under OS X 10.6.6, Windows 7 Enterprise under VMware, and Windows XP under VMware Fusion. We expect performance to only get better for Windows-based browsers when running on native hardware, but IE9 and Chrome were already quite impressive.