CanvasShapes - experimental implementation of multilayer canvas renderer
In 2005, during a practice session before the US Grand Prix, Ralf Schumacher crashed heavily in Turn 13 due to unsafe tyres (a fact which was later confirmed by Michelin). As it turned out, none of the teams using Michelin tyres could take part in the race - which was later won by Ralf's brother, Michael, whose Ferrari was fitted with Bridgestone rubber.
There are many existing canvas renderers around, so why bother writing another one and (you might say) re-inventing the wheel? It's not exactly the wheel we're re-inventing though. The wheel itself, used without a tyre is pretty useless. You can easily test it if you own a car... please don't! It was only a joke... but I hope you get my point. Even if it was safe, you simply wouldn’t do it because of the embarrassing noise.
"Surprise honey, I came back home earlier today!" - "Oh dear! I heard you miles away, so started cooking a dinner... shall we eat? It's ready!"
OK let me finally be more technical. HTML canvas element itself is a great wheel, offering a lot, but it's kind of painful to use. It needs a tyre, and if you bear with me, you'll know what makes my tyre special.
First up though, there are couple of domain specific terms used throughout this article and I would like to get you familiar with them first. Let's talk about scenes. A scene is a single animation or drawing and is associated with a block element in your DOM. Usually this is the job of a canvas element. In my approach though, a canvas element serves as a layer, one of many layers existing on the scene. For a static drawing you would probably create only one layer but remember:
Cool and shiny stuff is never static!
There is also a renderer. As well as managing all the scenes, it controls rendering and animation, calculates frame rate (globally - per whole page) and enables the adding of shapes to all the scenes it currently manages. Another key word here is shape- albeit a pretty self explanatory one.
Let's start with an example showing off everything I've been talking about so far, plus some more.
First of all, as you can see we have couple of scenes here but each is managed by the same renderer, which makes it very easy to achieve the same animation on all of them. It's also worth noting that some of the shapes are being rendered relative to the size of the scene. This is very cool, as relative rendering accepts percentage-based coordinates which means, that you don't need to worry about the size of the scene when you create it. There is of course one fundamental problem in this approach - rescaling the scene should keep the aspect ratio. Otherwise a square can easily become a rhombus when the scene size changes. Check out this example, which shows scaling in action with some nice visual scenery.
The goal of CanvasShapes was to achieve maximum of performance, so I tested a couple of things measuring FPS. In fact an FPS counter is always shown on the test case, so you can easily check the performance of those examples on your machine as well. I am curious to see if the results of your tests align with my observations, or whether they're a little bit different. You can ping me (details on about me page) with your results and hardware specs. What I have here, is a MacBook Pro with retina, i7 2.3GHz, 16GB DDR3 RAM and GeForce GT 750M 2GB.
But back to business… the Mozilla Development Network have published some performance tips here and I tried to address the ones which seemed crucial from a library architecture perspective. Firstly, to achieve maximum of performance
we should only redraw those parts of a canvas which actually change.
Easier said than done of course. In complex animations it would require a really smart engine to achieve this. Of course it’s possible, but in my opinion there is a less complex, better solution available... Having many layers sitting on top of each other allows you to organize static and animated objects in a better way. Each non-changing background has it's own layer, the animated object with its layer can then be put on top of it. Of course redrawing the simple background shape in the example above wouldn't slow down the performance, because it's not complex enough, but I think you get my point. So let's try with something which can drop the frame rate below 60.
What you see there is a grid of the same (featuring the same beautiful house) scenes you've seen before, with a weird mesh applied on top of it. The mesh is simply one long path, designed specifically to slow down the rendering rather than to serve any aesthetic purpose. The only animated object there is the sun, the rest is static. And the key thing about it is that everything is being re-rendered and re-drawn in every single frame. My FPS rate jumps to around 13 here... Let me then show you something faster - exactly the same page, with only one thing different: multilayer approach. The FPS now is around 53!
Secondly, MDN also advise that
some complex things can be rendered off the screen and copied into the target main canvas.
However, my tests showed this not to be fully true. Using exactly the same multilayer approach as in the example above, I thought that this method would speed it up a little bit more by taking all the layers and rendering them off the screen, and then copying them back to the target canvas shown on a page. My hopes were quickly crashed though. The performance differences between single and multilayer approach are still true, but all in all it was much worse (see single and multilayer approach with around 6 and 12 FPS respectively).
The CanvasShapes repository is open source, so you can fork it and play with it as you want. The code is documented but I'm planning to write some wiki pages to explain the architecture and some of the main concepts behind it all. But if you're a developer and possibly like general idea, please feel free to suggest improvements or contribute!
This article was also published on Learnosity blog.