Skip to main content
6 of 7
update wording

Optimizing rendering with a lot of transformations and save/restore

I have implemented a renderer for shapes described with a L-System grammar. Implementation of that is not important. Basically you feed it an angle and some rules, and it spits out a string of characters that represents instructions. For example, "+" means turn right n radians and vice versa. The instructions will only be computed once.

The job of the method .draw is to read the instructions and draw it on a canvas continuously. The angle is always changing (however the instructions stay the same) and therefore this method will be called repeatedly:

LSystem.prototype.draw = function(ctx, x, y) {
    var self = this;
    var path = this.path;
    var table = {
        "F": function(ctx) {
            // draw forward
            ctx.moveTo(0, 0);
            ctx.lineTo(0, -5);
            ctx.translate(0, -5);
        },
        "+": function(ctx) {
            // turn right
            ctx.rotate(self.angle);
        },
        "-": function(ctx) {
            // turn left
            ctx.rotate(-self.angle);
        },
        "[": function(ctx) {
            // save location and rotation
            ctx.save();
        },
        "]": function(ctx) {
            // restore location and rotation
            ctx.restore();
        }
    };
    ctx.save();
    ctx.beginPath();
    ctx.moveTo(x + 0.5, y + 0.5);
    ctx.translate(x + 0.5, y + 0.5);
    for (var i = 0; i < path.length; i++) {
        // take a character from the path
        // and call the corresponding function (if exists)
        var char = path[i];
        var cmd = table[char];
        cmd && cmd(ctx);
    }
    ctx.stroke();
    ctx.restore();
};

A path (in my case) consists of over 6000 characters. It has no problem in drawing at 60FPS. However the main problem is that it uses a lot of CPU and I can hear the fans in my laptop starts spinning. This is not optimal and I'm looking for a way to solve this.

By looking at the profiling tool in Chrome, we can see that it doesn't like .rotate and .restore being called repeatedly. On a 16ms budget it already takes up half of it:

enter image description here

What is a good way of optimizing the rendering and minimize CPU usage? Would calculating the coordinates for the entire path first then pass it to .lineTo a better choice? Keep in mind that the angle is different every time .draw is called, so the coordinates cannot be cached and reused.

A demo can be viewed here.