Note : this post is from 2011, the techniques described here are probably no longer necessary
If we’re serious about making HTML games then we need to know the most efficient ways to render multiple sprites. Many are saying that Canvas isn’t fast enough for gaming and we should use DOM objects instead. But before we give up Canvas altogether, let’s see if we can squeeze out just a little more performance…
A little while ago my podcasting mate Iain Lobb converted his sprite blitting test “Bunny Benchmark” to HTML5 canvas and was impressed with the results – he got 3000 bunnies rendering quite comfortably on all of his browsers.
But I was massively unimpressed – I could only get 5-10fps on all of my browsers. The reason for the difference in performance – Iain’s on Windows and I’m on OSX.
Whole pixel bunny vs sub-pixel bunny
If you draw into canvas using sub-pixels (not whole numbers) the browser will interpolate the image as though it was actually between those pixels. It’ll give you much smoother animation (you can genuinely move at half a pixel per update) but it’ll make your images fuzzy.
And it seems that browsers in OSX are super slow at doing this interpolation.
The solution : round your x and y position to whole numbers before rendering.
A quick search on JSPerf shows that using a sneaky bitwise operation is faster than the built in Math.round.
If you apply a binary NOT (represented by the tilda : ‘~’) every bit that was at 1 is now 0 and vice versa. If you do that again, the number is switched back to what it was. Except that any bitwise operation takes away any digits after the decimal point, which rounds it down to the nearest whole number.
But we’re trying to round to the nearest whole number, not just round down (like Math.floor). So if we add 0.5 first it rounds up to the next number if we’re close to it, so the final operation is:
x = ~~ (x+0.5);
Note that this only works on positive numbers!
[UPDATE] thanks to lab9 in the comments below who reminded me that you can do a binary OR with 0. This compares every binary digit in your number to every digit in 0 :
In other words, it does nothing! But again, because it’s a binary operation, you lose everything after the decimal point. I just added it to the JSPerf test and it’s even faster – thanks again lab9!
Now I wouldn’t normally advise premature optimisation, especially if it makes your code unreadable, but as we’re making a benchmark I thought it would probably be fair.
[GEEKY DIVERSION ENDS]
And so without further ado, see the results for yourself:
I get a massive improvement on most browsers – 7fps to 30fps. It’s only Firefox that only improves marginally. 7fps to 10fps, even on FF4. Is it the same for you? Is there a improvement on Windows with snapping? Do any of you know how I could implement something like this on JSPerf.com? I’d like to hear from you!
Coming next: DOM object bunnies!