Jump to content

Which of these canvas approaches do you think is quicker?


rich
 Share

Recommended Posts

Caveat: I've not had time to test this. I don't think it would take long to write a test, I'm just mental busy and am hoping someone else here has covered this already :)

 

I'm wondering which of these approaches will render fastest (especially on mobile browser):

 

Assume you've got a "Stage" (i.e. your large single canvas which is displayed to the user), a Camera and a load of Sprites.

 

Method 1

 

1. Transform the canvas (via setTransform, so maybe some scaling and/or rotation)

2. Now draw a stack of images to it (maybe several hundred), each of which may or may not have their own transform

3. Reset the canvas back to normal

 

This approach draws directly to the Stage.

 

Method 2

 

1. Using a hidden canvas (that belongs to the Camera), draw all the images to it (each of which may or may not have their own transform). This canvas has no transforms or anything, just a straight normal hidden canvas.

2. Now draw the hidden camera canvas to the Stage and apply a transform before doing so, thus rotating/scaling the display.

 

Both methods should give the exact same visual result. In Method 1 the Stage canvas is left in a transformed state and then all the images are drawn to it, so they all take on the effect of that transform (i.e. scaling them, or rotating them). In Method 2 they are all drawn 'flat' with no base transform and at the end the final camera canvas is drawn to the Stage canvas. This has the overhead of doing that final draw to the Stage, so really I'm wondering which saves the most time - transforming and drawing all of those images, or doing effectively two draws.

 

I guess I could probably have written a test case in the time it took to write this :D

Link to comment
Share on other sites

On my own library, I used offscreen canvases to optimize drawing.

You can see an example of how it was used here:

Capture%20du%202012-04-03%2011:42:19.png

 

In this example, there a huge offscreen canvas which stores the level, instead of drawing every tile at every frame.

 

I still use this technique because, the thing is, almost everytime you have something static, storing it in an offscreen canvas will be faster than drawing every small sprite.

I almost always choose the solution that will use the least Javascript instructions.

Link to comment
Share on other sites

I have been using the offscreen canvas approach for all my games. Just the other day I realised I had no idea if it was more efficient or not... In my brain it seeeems like it should be faster - in the olden days a back buffer was essential - but just like the "less javascript instructions == better" philosophy... there's no science behind it ;) Time to do some benchmarkin'!

Link to comment
Share on other sites

Aye, but my original question isn't really about tiles at all. It's about the cost of transforms :)

I don't think transforms cost that much. They are only simple matrix multiplications (4x4 per 4x1, not so big).

 

Drawing AFTER a transform has been made must cost a lot (because of pixel interpolation and stuff).

 

Though, I'm only taking a guess. I didn't benchmark this.

 

What I did benchmark (well, at least I saw a major difference) was on the screenshot above : drawing each part at each frame, or storing the whole thing on an offscreen canvas. And of course, offscreen canvas was waaaaaaaaaaaay faster (tried on really, really bigger levels).

I'm also using it on Infiltration (dunno if you guys tried it). Levels are smaller, but it's still faster.

 

I can see only one reason why it could be slower: when the offscreen canvas contains a lot of empty spaces. And I don't think the difference is that important.

Link to comment
Share on other sites

This is still unrelated to my original question :) and no-one said pre-rendering a level was a solution that worked for every game type. But it obviously worked well for remvst.

Actually, I think there might be some issues on mobile. Sometimes, on Infiltration, with the Android browser, I see the level disappearing and then the whole browser crash. I'm not sure if this is related specifically to my method though, since the game uses several complex algorithms which might consume too much memory.

 

If you want to try it on desktop, on a bigger scale, here is Haunted Gardens: http://haunted.remvst.com/

 

Here is the kind of levels that can be stored (which was clearly faster than redrawing everything at each frame):

4323Capture-du-2012-04-03-20-48-25.png

 

Of course, it works because the levels are static.

 

In the console, you can see it took 96ms to create the offscreen canvas. Then it is much faster to redraw it.

 

What you can do is use an intelligent way of storing it: once the camera reaches the limits of your offscreen canvas, you can create a new one, which enables you to store only one smaller canvas at the same time. I haven't had the opportunity to try it, but it's certainly possible.

Link to comment
Share on other sites

Wouldnt you be holding essentially 3 versions of all your images in cache with every draw? The original image files, the offscreen canvas image and the onscreen canvas image.

Yep, but that's the whole idea of cache: you use more memory to consume less CPU cycles.

Link to comment
Share on other sites

My guess is that drawing operations are always transformed, just that the default transformation matrix is an identity matrix. Since canvas drawing is hardware accelerated, it just doesn't make sense that the browser would do some sort of check whether the current transformation matrix is an identity matrix or not, especially that applying a transformation on the GPU costs nothing and it's only done per vertex. IMHO the first approach is the way to go, the second approach potentially has to draw content that isn't going to be displayed on the final canvas, not to mention the copying operation and the memory footprint.

Link to comment
Share on other sites

Wouldnt you be holding essentially 3 versions of all your images in cache with every draw? The original image files, the offscreen canvas image and the onscreen canvas image.

 

You've got the original image data, which you need regardless of approach so that's a must anyway. Plus the on-screen game sized canvas, which again you need regardless.

 

So the only extra one is a single hidden canvas to which you'd render whatever you need cached, and then copy that one canvas to the game canvas. I'm not using this for tilemaps just for cameras, so each camera has its own hidden canvas and at the end I composite them all back onto the main game canvas. The only time I don't do this is when there aren't any custom cameras, in which case I'm drawing direct to the game canvas.

 

The thought behind it was that say I've got 2 cameras side by side, each viewing a different part of the game world. Maybe for some reason the camera can rotate a bit (perhaps a shake effect happens during play), or it scales down/up in size a bit. I'm testing out rendering all the sprites that the camera can see to a hidden canvas, repeat for the 2nd camera, and then finally applying the transform (rotate/scale/skew) before rendering the 2 hidden canvases to the game.

 

Alternatively I could not use a hidden canvas and instead set-up a global transform, then render all the sprites, then repeat for the other camera. This avoids using any hidden canvases but comes with 2 drawbacks: 1) You have to use context.clip() to stop sprites bleeding out over the edge of the camera, which is expensive on mobile (2) I'm not yet sure if the cost of drawing all of this sprites with the camera transform applied is more or less expensive than drawing them all 'flat' and then just doing one single transform during the camera render phase (so it only has to interpolate all those pixels once).

 

As it happens I'm experimenting and benchmarking this at the moment, so should find out very soon.

Link to comment
Share on other sites

Are hidden canvases still bound by the same memory limits as images? (i.e. you can't create bigger than 2048x2048 on most mobiles).

 

 

Yep, that is true at the moment.

 

Here is nice tool to determine which size is ok for particular device http://www.williammalone.com/articles/html5-javascript-ios-maximum-image-size/

 

In order to have larger canvas you can use few of them joined together, but this can multiply available surface in 4 times and no more because of global memory limitations.

 

Funny that iOS browser have no limit on this and browser just crashes after 6 x 2000 x 2000 canvases were created and used.

 

Sorry for getting out off topic.

Link to comment
Share on other sites

Ok experiments over and I'm going to run with using a hidden canvas per Camera. The benefits far outweigh the cost (in my case at least) and there was another 'gotcha' I forgot about until now - you can only have one active global composite operation per canvas at once. So if I want to apply a mask to my camera (with maybe xor) it needs to be a canvas anyway! and I often need that.

Link to comment
Share on other sites

It doesn't have GPU cost, but it has JavaScript cost (as it performs the matrix math for every sprite, every frame) and I'm not then sure which rendering process is faster - rendering a load of rotated sprites, or rendering them "normally" and then just doing one final rotation at the end on the whole scene. I've kind of answered my own question anyway as I need to use a canvas for the effects I require, but I'm still curious :)

Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.
Note: Your post will require moderator approval before it will be visible.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

Loading...
 Share

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...