Skip to content

Rendering performance and throttled updates

Igor Zinken edited this page Jan 17, 2024 · 7 revisions

When a Canvas instance has been configured to be animatable, The Canvas will automatically sequence a render cycle to continuously update the visual contents of the canvas. zCanvas uses the requestAnimationFrame API to draw whenever the browser is ready for its next paint cycle. There are however some considerations to make with regards to performance and achievable frame rates.

Sixty or not?

zCanvas assumes that the average frame rate is 60 fps, which is a standard among a large range of devices and browsers and luckily achievable by the most standard consumer devices like a phone that goes back a few years. There are however devices and browsers that run at a shorter rendering interval (such as the new Apple M1 which achieves 120 fps).

zCanvas will render as fast as possible to meet the desired frame rate (either the default of 60 fps, or a custom frame rate configured in the Canvas instance). zCanvas will automatically take care of doing no unnecessary or the right amount of work by deferring the actual rendering.

If your application uses no continuous logic within your sprites update() or canvas custom update()-handlers (for instance to adjust object properties / animate), you will not have to worry about anything...

...unless

...unless you use the update() handlers to proceed to the next step in your "world simulation", for instance: to move a game object to the right at the speed of 2 pixels per frame.

In this case you might want to read on as now you have two different use cases that could make you consider throttling...

framesSinceLastUpdate

The update() methods of your sprites or of the custom update handler you optionally provided to your zCanvas constructor, receive two arguments:

update( timestamp: DOMHighResTimeStamp, framesSinceLastUpdate: number ): void

As the timestamp argument is specified within the linked Wiki pages, we are going to focus on the framesSinceLastUpdate argument here: basically this number specifies the amount of frames that have elapsed since the current and last time update() was invoked. The purpose for this is that this value can be used as a scalar for all step based calculations done in your update handlers. Ideally this value is 1, but in reality (and consequently the possibility whether you want to ignore it or not) the value is fractional and can differ depending on one of the following two scenarios:

"I want 60 fps"

zCanvas got you covered. Even when running on a device that is capable of 120 fps, the rendering will be deferred to only occur at 60 fps. This implies that update() is also called sixty times per second (because update() always precedes a render).

In this case, framesSinceLastUpdate will always be (around) 1. You can even choose to disregard the value as in most cases the environment is guaranteed to render at that speed. As such, the code in our example can simply be:

SpriteInstance.update( timestamp: DOMHighResTimeStamp, framesSinceLastUpdate: number ): void {
    this.setX( this.getX() + 2 ); // simply moving 2 pixels to the right per frame
}

Simple enough (and also the way Demo #2 works). Please do read about the following scenario though, because you might want to consider the possibility that frames can be dropped when your device is under stress :

"I want to throttle speeds or support low refresh setups"

Okay, maybe you don't want 60 fps because you are:

  • doing something extremely taxing in your render routines (your code only has a little over 16 ms to render each frame after all)
  • or maybe you want a more retro flavour to your animations and are rendering at a lower frame rate, such as 25 fps
  • or maybe you have a brilliantly artistic idea and want to adjust frame rate during gameplay, from 60, to 30, to 15, who knows ?
  • or maybe you want to render at 120 fps and are worried that this affects the performance on devices that can only offer 60 fps ?

...or MAYBE you are just concerned that your application will not perform as well on old phones because you are well aware that while 60 fps are often achievable, at others a few frames might be dropped here and there.

In this case you want your "simulation" to proceed at the same speed. In other words: in our scenario where we want to "move 2 pixels per frame at a desired frame rate of 60 fps", the simulation moves at 2px * 60 = 120 pixels per second.

If your frame rate is 30 fps, that means you would need to move 4 pixels instead of two (as the frame rate has halved). Luckily the math to address this is quite easy:

SpriteInstance.update( timestamp: DOMHighResTimeStamp, framesSinceLastUpdate: number ): void {
    this.setX( this.getX() + ( 2 * framesSinceLastUpdate ));
}

Notice how we multiplied the 2 pixels by the amount of frames elapsed between the last update ? This means that your simulation updates at the same rate, even though the screen doesn't. In other words: even though we have only rendered half the amount of frames we would have had at 60 fps, we have still covered the same pixel distance over the same amount of time. This ensures rock solid timing of your application state under all circumstances.

You can consult demo #1 to view how adjusting the frame rate at runtime does not affect the game speed.