Jump to content

Myths and realities of canvas / JavaScript performance


rafinskipg
 Share

Recommended Posts

Hi people.

 

I've been doing a game for a year or so. Now i'm facing some troubles with the performance.

 

I know i can use cocoonjs but before talking about that, i want to ask you if this are myths or realities , if you have faced it: 

 

Aclaration: Performance was not measured, it was what I saw with the lag of the game. This was tested with chrome 35.

 

 

  • Using different canvas layers.

I've been doing all in the same canvas element, i tried one time to add more canvas layers but I didn't noticed any change. Have you tried this? How is your experience with that?

  • Rendering in a virtual canvas, and then painting on the visible canvas.

This is one of the last changes i did, now i'm painting all in a hidden canvas created at runtime, and then drawing  It on the visible canvas after drawing all the individual entities on the virtual one. I have noticed no change of performance.

 

  • Not drawing too much entities

This made the game work better. As expected.

 

 

What other tips could you provide to improve the performance?

I'm using map/ functional style of programming for updating the entities, may this lead into a black hole of performance?

 

function updateEnemies(){  enemies = _.compact(enemies.map(update)    .map(move)    .map(removeIfOutOfScreen));}

I know "for" loops are better performants, but i don't know if this is what hits the performance of the game.

 

Other thing I'm not doing is using a QuadTree algorithm for checking near positions. How do you check that? Any algorithm? Brute force?


This is the game I'm doing.  http://cooldog.venturastudios.org/

 

I'm opensourcing everything but before I have to clean all the f**ing mess of code I did in one year. (I think i have at least 6 more months to work on cleaning and performance)

 

I accept all the suggestion and ideas you have for performance, or whatever you want to say! Thank you bros!

 

Link to comment
Share on other sites

I know that save() and restore() are very expensive.

 

Can anyone comment on the optimization strategies for calling save and restore efficiently if you have to transform or rotate many sprites each frame? 

Link to comment
Share on other sites

  • 2 weeks later...

I noticed recently that images really make my game heat up, the whole time through development, I have been making my game with js rects and circles and the game runs fine and no heat, after I add a full screen image (bg) my macbook is heating up during testing and so is my phone. Reading into it, I feel right now, the way textures are bilted to the canvas is inefficient.

Link to comment
Share on other sites

  • 2 weeks later...

Hello!

 

In the past year, I have decided to do a HTML5-based game, and I kept going at it ever since. Full production is something that is still a bit far ahead, but I managed to squeeze out every single little detail of canvas-related performance that I could find.

 

Here are my experiences, tips, and all that jazz.

 

1.On buffers
Simply using a buffer canvas (an invinsible one) and then drawing that to the screen instead of using image-> canvas is really, at first glance, not faster.

The applications of this is not meant to be as simple as that, however.

 

You want parallax scrolling? You want fancy effects generated from a multi-layered spritesheet? You can predict that is going to be needed? Then you can save processing time by using buffer canvases.

 

There are all sorts of clever tricks to this. Imagine if you need to compile a 4-layered parallax background scroll in about...240 frames (4 seconds). You can process the picture, add the layers to a hidden canvas, frame by frame, in small parts. No single frame will make this buffering draw more than, say....50*50, once per frame.

And then once it is completed (pre-painted), you can just draw parts of the buffered image, showing the user the parallax scroll in all its glory, by rendering it only once per frame.

 

If you omitted this process, you will need to draw the bacgkround as it is moving N times, where N is the number of layers.

This is just one trick. You can build animations from multiple layers, buffer it, and only draw one square (frame) instead of requiring multiple layers of composition and different draws over and over.

 

Conclusion: buffer-canvases are good, when used with preparation. Just using them natively (for the sake of using them) is not a performance benefit.

 

2. Canvas size matters, especially (but not only) in mobiles.

Never use bigger sized canvases than you would truly need. There is a directly testable performance gap between canvas sizes. Sounds like a no-brainer initially, but it is not. Logic would dictate that if I redraw an x*y size area of an 1200*800 canvas, that would be similar if I did the same redraw of an 1000*600 canvas, but it is not. Use clever positioning, CSS, background images if you want to be as optimal about this as you  want to be.

 

3. The most important canvas optimization #1: Be smart about redraws.

There has been many tutorials describing this. You should not redraw everything all the time. Multiple canvases are good because you do not need to clear/redraw stuff when one object moves.

 

In my rpg game, there are currently 5 layers used. Background, UI-BG (frames) layer, character layer,puzzle layer, text layer. When the character moves around, it does it in a different canvas than the background.

- 1) Only redraw animation frames when you need them to. Non-moving object? No redraw.

- 2) Only clear (clearRect) when you need to. Be very precise about the areas needed to be cleared. Doing 5*(40*100) clears is usually better than doing one 400*100 clear.

- 3) This has yet to be carefully tested - more like anecdotal evidence, but you need to organize your drawing like.....canvas 1: Perform all clears, all draws. canvas 2: perform all clears, all draws. DO not do canvas1 clear, canvas 2 draw, canvas 1 draw canvas1 clear canvas 3 draw canvas 2 draw canvas 1 clear canvas 4 clear. It is a performance loss.

 

My own "camera" framework gets draw/clear commands thrown at it, and it keeps separate clear/drawQueries, and at the end of an animFrame, it draws all queries and clears all queries. Goes from canvas to canvas. I was afraid it is going to be convulted, but in a HTC Desire 310, I can achieve 30-60fps on my game, even without further resolution adjustments. And it has constant animations, parallax scrolling, moving puzzle, five layers. I am almost satisfied with it. Before I started optimizing the game, it crawled in single digits.

 

4. The main culprit behind slowdowns is usually the garbage collector.

Seriously. I have looked at so many HTML5 Canvas/webGL games in the past few months, looking for answers, and most of them have the quick sawtooth performance that is painful to watch. Let us assume your game runs at 60fps. Theoretically. Now, with regular GC, you can get a visible slowdown every seconds, even if it is just a hitch to lower 50's. (60->57->51->60->51->etc.). You need to have a VERY carefully written code to eliminate this. Basically, no anonymous functions, no var declarations hundreds of times per frame, no spamming of new objects per frames. I could write pages on this - and if there is a relevant topic for it, I might. :D -,but this is the main part of it.

 

5. Be aware of smaller performance traps.

Text rendering is one like that. Changing alpha value of a canvas again is. Doing translations is once again. (Better buffer both of these on a hidden canvas in a loading stage, and keep the alpha intact!). Do not use console.log on mobile versions, they will just throw exceptions sometimes multiple times per frames. What you started with is true: aim for the lowest amount of redraws and clears every frame. Find tricks to average these out and manage them that way instead of having busy and calm periods.

 

I am right now at the end of my canvas "patience". I still love this little tech, but I am at a point where I am looking at just porting all that to webGL. Canvas support will stay in the game just in case, but it is clearly not going to be flawless on low-end mobiles for a few more years. It is going to be "fine" in better-than-average mobiles, average or better tablets and excellent on iPads/iPhones, viable on most modern desktop PC's. But you have to be careful about the resource uses.

Link to comment
Share on other sites

About garbage collection: Well, this one is pretty hard to deal with and often it doesn't even make sense how it is behaving.

The most you can do is to preallocate a pool for objects which consume a great deal of memory and minimizing the creation of objects and functions. So you don't have to create those each frame (if you need them that often).

For example I have rewritten a dom events manager for three.js, reusing everything I can, not adding unused eventlisteners, using 1 raycaster permanently stored in the memory instead of creating one each mousemove (which can be 3 raycasters per frame at 60fps) etc.

However, my version does have a minimal sawtooth pattern while the original, which is creating new instances of classes each mousemove, does not have this tiny sawtooth. Even though my version does have it, it performes much better.

Link to comment
Share on other sites

I'll complete with a few tips.
 
Be aware with some popular js lib
jQuery, prototype, ... They are all amazing but add an extra complex couch to your code. Avoid using them in the rendering loop just to save 2-3 lines, it will run slowly and your garbage collector will eat a lot of cats (because of a lot of closure, object creation, ...).
If you have to use it, then do it smartly.

Kill setTimeout, clearTimeout
Do not use them. If you have to do, them implement your own function that is checked and executed in your rendering loop (better to render in only one function).
It will avoid the browser to run extra timer, re-paint everything in screen more than needed (if you draw/update a DOM element).

Audio
Try to avoid spamming audio, or duplicating audio sound to play more than one at the same time. Sounds can really drop your FPS and wake up your Garbage Collector. If possible cache them.

Re-usable objects

To avoid problem with the garbage collector, avoid objects creations in rendering loop (array, image, sound, object, class instance, function).

If possible re-use objects you currently have, or add them in a closure. Sometimes singleton can save your life, really.

For example if you have a lot of sprite, when you remove one, push it to a cache system then when a new sprite have to be created, re-use the one from the cache and set it with your new parameters. 

 

Shadow, gradient

Maybe things change since the last time I tested it, but some browser native functions can be really expensive for your games. What I have in mind is gradient (css, canvas), and shadow (box-shadow, text-shadow). Avoid using them if possible, sometimes better to create an image containing this effect instead of letting the browser generate it.

(Maybe an outdated tips).

 

Do not read the image pixels

If ever you are using canvas, do not read the canvas pixels in rendering loop. It will really slow down your application (specially in webgl).

There is no valid reason to do it.

Link to comment
Share on other sites

This article suggests wrapping requestAnimationFrame inside setTimeout to set the frame rate:

 

http://creativejs.com/resources/requestanimationframe/

 

Is this a bad idea?

Yes, it's an bad idea. If you want to set the frame rate, you should do this based on the milliseconds between each requestAnimationFrame call.

This is due to the fact, that the requestAnimationFrame takes a minimum of 16ms itself and then you add the setTimeout on top of that.

Let's say you want to run your game at 50 frame per second.

Using the method described on that page, you would get 16ms per raf call + 20ms per setTimeout, putting your draw call at 36ms minimum. That's only 28 frames per second.

If your game takes more than 16ms per rendering call the framerate will drop even more and your game can end up being unplayable.

On top of that, some older browsers seem to have a minimum delay on setTimeout.

The linked example is even worse because it does create an anymous function with the whole rendering logic each frame, which will then have to be garbage collected.

Ofcourse there are differences on how the browser deals with raf and settimeout, but here is a fiddle that is supposed to run at 50 fps with above mentioned method and only runs at 20 fps in IE:

http://codepen.io/anon/pen/fFLHy

Link to comment
Share on other sites

 requestAnimationFrame syncs to display refresh rate, triggering it as fast as possible. 

That's true.

requestAnimationFrame just gives the browser permission to draw when it's ready.

The actual time that it takes to do that could just be a fraction of a millisecond.

 

Sebastian has proven that the setTimeout wrapper doesn't work properly (I get 20-30 fps in Safari, 47-50 in Chrome)

 

.... so now I have two more questions! :)

 

1. Is the reason it doesn't work property due to poor browser vendor implementation?

2. What is actually the best way to set the frame rate (or at least clamp the upper limit?)

Link to comment
Share on other sites

Maybe just don't cap the framerate and animate everything based on elapsed time instead of elapsed frames.
Why not always display everything as smooth as possible?
 
Other than that:
You can calculate the average fps or take the fps of the previous second and use that to guess how many fps the user would have in the current loop.
E.g. the user had 60 fps in the prior second, so you could skip each 6th frame in the current second, to achieve 50 fps.
 
@Antriel

My bad, you are right on that :D

Link to comment
Share on other sites

there is no magic tip for performance optimisation but there is Chrome Developers Tools !

My only solution for performance is : profiling, profiling and      profiling :D


thank's to chrome developers tools which can now profile desktop and mobiles you have a precise view of what's consuming the most CPU time, the biggest memory size ...etc

 

Link to comment
Share on other sites

I have a question about that article that I have not yet found a definitive answer for:

 

Are the techniques he describes applicable to HTML5 games using requestAnimationFrame?

I haven't found an HTML5 implementation of those techniques anywhere yet.

Of course, instead of while(true) loop, you have requestAnimationFrame callbacks. Calculate time difference and you know exactly how much further to move your state.

I'm coding with Haxe using Flambe so I can't give you js code, but essentially:

1. You calculate time diff since last RAF callback (from given timestamp)

2. Add that difference to accumulator

3. while(accumulator >= STEP_TIME) {accumulator-= STEP_TIME; updateLogic();}//step time being your target fps.. 1/60, 1/30...

4. setInterpolation();//see below

5. render();

 

Interpolation is simply remembering last state and new state (positions/rotations), and setting the actual values of clips in between these states, according to the accumulator amount (i.e. how close it is to next frame). So I get alpha value as accumulator/STEP_TIME and interpolate.

i.e.:

render_x = (position.x - position.old_x) * alpha + position.old_x;

 

This way my game runs at 60/120 fps with everything looking smooth despite logic/physics running at 30 Hz.

Link to comment
Share on other sites

  • 2 weeks later...
  • 4 weeks later...

Some brilliant points by Sawamara (particularly about minimising redraws) that sound like they've been learnt through hard experience. Unfortunately, I can sympathise with being at the end of my canvas patience at times!

 

Regarding save() and restore() being expensive for translations and rotations: if you simply need to translate or scale a canvas, rather than using translate()/scale() wrapped in save() and restore(), you could simply use the additional parameters to drawImage() to draw at a given location, and at a given size. This can save a lot of time, particularly if drawing many sprites. I'd imagine frameworks that cater for the general case would not take advantage of this "optimisation" - EaselJS certainly does not (through no fault of its own). If you need rotation, drawImage() alone can't help you. One option would be to pre-render your sprite at a series of rotations, to a texture atlas, then simply grab the required sub-image using drawImage().

 

I've found CPU bottlenecks easier to track down than GPU bottlenecks. For example, a long-running function would tend to show up in the Chrome CPU profiler, but a GPU related hitch might only show up as an occasional tall empty bar in the Chrome timeline. Worse still, I put an FPS counter in my code that counts the number of requestAnimationFrame() calls per second, and even with this showing a constant 60 FPS, the animation may not appear smooth - the human eye is surprisingly discerning! In order to reduce redraws, it is common practice to use layered canvases. However, I've found that, particularly at high resolutions e.g. full retina resolution iPad Air or even Macbook Pro, due to the additional compositing load on the GPU, after a certain point, adding an additional canvas actually makes GPU-related glitches *more* likely, despite the redraw savings. So it can be a tricky balance. One tip here would be to make the layered canvas only as large as necessary, and to set its visibility to none in CSS when it is not used.

 

Best of luck with this.

Link to comment
Share on other sites

well i've learnt a lot. :)

i will add these.....
1. try to use html and css for widgets as much as you can. such as menu, options and stuff. canvas based text cost and will not give you much options.

2.try to compressed ur audio file and cache them where u can.

3. i disagree with not using setInterval and setTimeout completely. what you want an event to be triggered at a certain time? Every game and it's requirements. How ever avoid them as much as you can.

4. Test you game performance please please and please. I recommend chrome dev tools. :wub:

Link to comment
Share on other sites

  • 3 weeks later...

You calculate new position in logic update and before you do that, you store the old position to be used for interpolation.

Thanks Antriel!

You seem to know a lot about this, so I have some more question for you that I haven't been able to find the answer for :)

 

- Is it better to use interpolation (the old position) than extrapolation (the current position)?

 

- I've noticed that when I do interpolation or extrapolation I sometimes get slight off-by-one errors with physics. The rendered sprite will appear slightly ahead (extrapolation) or slightly behind (interpolation) the game logic. Is there a way to compensate for this, or should interpolation/extrapolation not be used for precise physics simulations?

Link to comment
Share on other sites

- Is it better to use interpolation (the old position) than extrapolation (the current position)?

 

Extrapolation will always be inprecise, so I wouldn't recommend that. Especially with physics you would get things like objects changing directions in a weird way or being rendered inside other objects.

 

- I've noticed that when I do interpolation or extrapolation I sometimes get slight off-by-one errors with physics. The rendered sprite will appear slightly ahead (extrapolation) or slightly behind (interpolation) the game logic. Is there a way to compensate for this, or should interpolation/extrapolation not be used for precise physics simulations?

 

 

Yes, that's the only way this can work, you are always a bit behind with interpolation. But by how much? If your logic update is 30 Hz, then it cannot be more than 33~ ms. Which isn't a lot really. Afaik human reaction time from seeing to reaction is about 200ms, with muscle memory reflexes it's around 50ms iirc. So it can't be noticeable by player. Now if you were doing something extremely skill based like Super Meat Boy, I would worry more, but then you are sure to have at least 60 Hz updates, so again the time delay is very small. I think even with the delay, brain learns to compensate for it anyway. Anyway I wouldn't worry about it, most games these days (on pc) enable triple buffering, so you are always 2 frames behind anyway.
 
Regarding the question: There is no way to compensate. But there is no reason not to use it for precise physics. It doesn't alter the physics in any way, just what the player sees and that is more precise than you could get by just redrawing all the time. That goes for interpolation though, extrapolation as I mentioned before, would draw stuff at wrong places (actually noticeable).
Link to comment
Share on other sites

Thanks so much, Antriel, with your help I've got a working prototype:

 

http://jsbin.com/janibo/1/edit

I don't think that's right.

Running the physics at a lower rate than the renderer will increase your total FPS (when the physics simulation is non-trivial ie. unlike the prototype much more expensive than the interpolation), but it will not help your perceived frame rate as you just make the frame rate more uneven for frames the update runs in/before.

To get an even frame rate and benefit from running the physics at a lower rate you either need to use another thread or spread the work around eg. updating half the entities for every frame. Not that either of these is that easy to implement I suspect...

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...