HTML5 Game Development Blog

  • Tiled Game Engine slide



Game render optimization: cache, check and invalidate

Category : Tiled Game Engine Feb 27th, 2015

Pages: 1 2

Previously we’ve managed to render TMX map on multi-layered canvas. Now, it’s time to optimize our code, to get 60 FPS on any map size. First of all let’s start with game render optimization. First thing you should take in mind, that you do not need to redraw scene on each frame. Canvas is a persistent object, that means that it doesn’t clear itself. So, we need to track if our scene has been changed and redraw it, if any. You’ve probably already noticed some conditions in the TiledGameEngine code, that was not explained early. Now it’s time to cover it.

Optimization tip #1: call render only if required

Tracking scene updates is very handy optimization technique. If you do not use it – your scene will be redrawn on each frame. Most of that work will be “junk” work, so let’s avoid it and optimize render loop – simply check, if scene need to be rendered, before you call render code:

    TiledGameEngine.prototype.renderFrame = function() {
        if (this.activeStage) {
            // redraw canvas only if required
            if (this.activeStage.redraw || forceRedraw) {
                this.activeStage.render();
            }
        }

        forceRedraw = false;
    }

Render method will be called only if scene is invalidated (redraw flag is true). Redraw flag is managed by the stage object and indicates if scene need to be redrawn on the next scene update.

forceRedraw flag tells TiledGameEngine to call render function forcibly. Usually that flag will be switched on if Tiled Game Engine handle invalidateStage event:

    TiledGameEngine.prototype.init = function () {
        ...
        TiledGameEngine.bus.subscribe('invalidateStage', this.onInvalidateStage.bind(this));
        ...
    }
    TiledGameEngine.prototype.onInvalidateStage = function(key, value) {
        if (this.activeStage) {
            if (this.activeStage.name === value) {
                forceRedraw = true;
            }
        }
    }

Optimization tip #2: cache results

Rendering TMX map require a lot of canvas manipulation. In the easiest case – a lot of drawImage routines will be called. Doing such on each render cycle is not a good option. Most of time scene (or its parts) is not changed. In the multi-layered scene the following optimization is widely used – off-screen cache. Using off-screen canvas to render current map state and draw that off-screen. From the first glance it will require some extra code and, probably, will decrease rendering performance on initial caching. Let’s see.

The correct answer is: yes and no. On the desktop browsers you will not notice any changes, actually. But on tablet and mobiles significantly performance boost can be achieved using that technique. I’ve implemented this in the TiledMapStage class:

    TiledMapStage.prototype.render = function() {
        // stage is not active, map is not defined, no view port or map is not parsed yet
        if (!this.active || !this.tmap || !this.screen) return;
        if (!this.tmap.ready) return;

        if (!this.cached) {
            // pre-render to the off-screen buffer
            this.prerender();
            this.cached = true;
        } else {
            // mark stage as up to date - no redraw needed
            this.redraw = false;
            ...
            // draw all layers from back buffer to the screen
            for (; i < layerscount; ++i) {
                layer = this.tmap['layers'][i];
                ...
                ctx = this['screen']['getLayer'](layer['ctxlayer']);
                ctx.clearRect(0, 0, viewport.width, viewport.height);
                ...
                // copy off-screen buffer (visible part) to the screen
                ctx.drawImage(this.backbuffer[i],
                              -viewport.left, -viewport.top, viewport.width, viewport.height,
                              0, 0, viewport.width, viewport.height);
            }
        }

Such optimization technique is also helpful, if render content is heavy. Rendering procedural images is not very fast and, mostly, required pixel manipulations or heavy computations. Also, canvas path routines and text rendering is terribly slow. If you use them – you should cache them. Handy offScreenRender function added to the base Stage class:

    Stage.prototype.offScreenRender = function (width, height, fn) {
        var offScreenCnv = document.createElement('canvas');
        offScreenCnv.width = width;
        offScreenCnv.height = height;
        fn(offScreenCnv.getContext('2d'));
        return offScreenCnv;
    };

Pages: 1 2

SHARE :

(2) comments

Skulty
4 years ago · Reply

Hi,

Please continue posting these tutorials, they are great.

    Pavel
    4 years ago · Reply

    Hi, Skulty.

    Thank you. New posts are ongoing. Keep in touch.

Leave a Reply

Your email address will not be published.


Like it? Share It!

We're making our best to provide good and valuable content. Please help us by clicking on one of the social icons, thanks for everything.