drawImage performance on iOS vs CSS

I noticed something interesting earlier whilst trying to improve the performance of Dragons that I thought I should share. When I first considered how the game would look I settled on a full screen scene. I blogged how excited I was about this and that I could finally sit back and create full screen renderings in Photoshop. I’m using .png files for the backdrops. I tend to use that format for everything these days.

The theory was simple in that I would run through setLevel() once per level and set everything up. One of the things to set up is the current stage backdrop. For this I set the backgroundImage property of the canvas’ style to be whatever background I choose.
Something like this:

g.canvas.style.backgroundImage = “url(‘…’);”;

Very basic CSS stuff. g is my global namespace.
I figured that this would save me a hefty drawImage() call during the main game loop and therefore preserve some frame rate.
This was a poor assumption. At least on iOS. And even then it appears more so on iPhone 4.

I scratched my head as to why first gen iPod Touch could render the game at roughly 20FPS and iPhone 4 struggled along at around 6FPS.
So I played around with it a bit and took stuff out. I also killed shadows on text and cut back on the number of sprites on screen. None of it made a difference.

Meanwhile over on Android the game was flying along like a train.

I then decided to take a closer look at the “big” stuff. By big stuff I mean the operations that involve the most pixels. I use drawImage for everything so I reduced the size of the sprites and took out sprite scaling and rotation. No real difference was recorded. So I put it all back in.
I then took a look at the CSS call to paint the backgroundImage of the canvas in what I thought was a one-time-per-level operation that exists outside of the loop.
I stripped out the CSS bit and rendered the full screen scene on every game tick. To try and help it along a bit I also reduced the file size of the .png files from 43k to 3k. I also ignored diffusion since this doubles the file size even at 8bits.

I uploaded the new game code and assets and refreshed Safari on the iPhone4.

To my amazement the frame rate had more than doubled to (a still poor but more acceptable) 16-20FPS. Android was, to be frank, no different. It still motored on beautifully.

So the valuable lesson that I’ve learned here is that the best course of action on iPhone (at least it is right now before iOS5 arrives) is to do all full screen drawing with drawImage and not rely on CSS. Alternatively of course don’t do ANY full screen drawing.
One of the smoother games I’ve completed, Galactians, works against a plain black backdrop a la the arcade games of the early 1980s. Perhaps there’s something in this.

I’d love to hear some thoughts on this. I really am not a great coder so muddling through this stuff is often mesmerising at best. Be interesting to hear what other HTML5 game developers are doing to steal back some FPS.

5 Comments

    Colin Godsey

    I’ve also tried every form of trickery in an attempt to make this work better. I think the main issue is just the size of the canvas period. Any canvas calls seem to cause the browser to pull the FULL canvas to update it in the DOM (copy it into an image buffer, a software double-buffer almost), where I’m pretty sure Android lets you draw directly to the visible buffer (causing tears if you dont vsync). I think the real test would be to see if you can use setInterval and see if there are tears in android vs iOS. My bet is there are non in iOS.

      Mark

      Thanks, Colin. I will give it a try.

    Mark

    The main game loop (for the actual game as opposed to “Get Ready” screens and such) executes this order of events:

    —–

    Full screen drawImage() 3k png file

    Monsters Loop (4 iterations)
    [
    moveMonsters()
    drawMonsters() — uses drawImage()
    ]

    drawPlayer() — uses drawImage()
    movePlayer()

    Items Loop (4 iterations)
    [
    moveItems()
    drawItems() — uses drawImage(), includes rotation
    ]

    Bonus Items Loop (12 iterations)
    [
    moveBonusItems()
    drawBonusItems() — uses drawImage()
    ]

    Fireball Loop (4 iterations)
    [
    moveFireball()
    drawFireball() — uses drawImage(), includes scaling
    ]

    Text Objects Loop (4 iterations)
    [
    moveTextObject()
    drawTextObject() — includes shadow, uses context.fillText()
    ]

    Update Score — uses context.fillText()

    Display Player Lives — uses drawImage(), up to 5 loop iterations

    Display Gold Bar — uses context.fillRect() to a potential size of 280 x 24 – drawn twice per game loop

    —–

    So for scenario #1 (the background image being placed with drawImage within the game loop) this is potentially 31 drawImage() calls on every loop. The sprites are small both in terms of screen size and file size. The background images are obviously full screen but very small in terms of file size.

    I’ve not included number crunching in the above flow since collision detection routines and such have never affected performance in my experience. At least the routines that I write don’t !

    Scenario #2 (the background image being placed with CSS outside of the game loop) is identical to the above but without the first line.

    I’m willing to concede that some of this is borne out of my own rather arrogant stance toward DIVs and Images. I spent years making games with that approach and ultimately became frustrated by how 1995 it all felt. So much so that when Canvas came along I took the attitude that I should build everything using the newer technology. So I now stand firm and tap my foot waiting for browser vendors to catch up.
    Alas it seems that Apple have an agenda. One that sees them cripple their own browser to encourage the expansion and uptake of their own App Store.
    I hope that this is soon a thing of the past and we get lightning performance across all mobile browsers.

    As always, interested in the opinions of other HTML5 developers.

    Ash

    This could be something to do with hardware acceleration. On many desktop browsers at least, the drawImage call is hardware accelerated so it’s faster to draw than conventional methods. Perhaps there’s an overhead to having a transparent background on your potentially hardware accelerated canvas? (I don’t know.)

    Interestingly, hardware acceleration for the Android browser didn’t appear until 3.0, so I’m not too sure why it would be any better than iOS.

    I’d be interested to know if painting the background with a drawImage would perform any better.

    Chen

    Hello,

    it would be interesting to know what else happend on the screen.

    At the moment i’m playing around with the performance differences between canvas and the DOM.
    Currently, the DOM is far ahead of canvas on older browsers or mobile devices.
    The best was to use a mixture of both. Only using canvas when it was necessary like tile backgrounds and only draw the canvas when something changed like an animation. Every other object is a single dom node with absoult position.

    But i ran the same test as you a couple of month ago and observed the same as long as the overlapping canvas was fullscreen.

    Regards,
    Chen

Leave a reply

%d bloggers like this: