Game of Life Clock Optimisations - Blitting

20 November 2013

Previously we were able to reduce the work involved in the step-by-step redraw, this was achieved by only drawing the cells that had changed state in that step and was very effective at speeding the clock up. Unfortunately, as evidenced in the profile below, when the clock first loads the cells have no previous state and we must draw them all.

Our goal, by my logic, is still to reduce the number of calls to the fill method. The next way we can attempt to do this is by calling it a limited number of times to produce a block of them and then copying that block across the entire canvas using the drawImage method. We could pre-fill a single cell and copy that 1,109,556 but we're then left with an awful lot of copying operations. The optimal way is somewhere between these 2 extremes.

I've chosen an arbitrary block size of 100x100 to try this with, the rough code looks like this:

private fillWithDeadCells() {
    var cells: number[][] = [],
        x: number,
        y: number,
        block: HTMLCanvasElement,
        ctx: CanvasRenderingContext2D,
        resolution: ISize;

    block = document.createElement('canvas');
    block.width = 100;
    block.height = 200;
    ctx = block.getContext('2d');

    resolution = this.getResolutionForCanvas(block);

    for (y = 0; y < resolution.y; ++y) {
        cells[y] = [];
        cells[y].length = resolution.x;
    }

    this.render(null, cells, ctx, true);

    for (x = 0; x < this.canvas.width + block.width; x += block.width) {
        for (y = 0; y < this.canvas.height + block.height; y += block.height) {
            this.ctx.drawImage(block, x, y);
        }
    }
}

After profiling this we can see an improvement on the time spent on the initial render:

Still to come

Whilst the performance is now acceptable for the resolution we were working at I want to take it further and up the resolution by a factor of 10 next time. I think we'll see a significant slow down and it will probably be relatively localised to the methods that calculate the next set of live cells. The current implementation of this is crude and so we'll take a look at what we can do to improve on it.