art with code

2011-07-19

Story of Tomte's New Hat


To break the monotonic programmingification, here's a story about Tomte's new hat.

2011-07-06

Microbenchmark findings

Looping through canvas ImageData pixels: 1D traversal for (var i=0; i<data.length; i+=4) ... is slightly faster than 2D traversal for (var y=0; y<height; y++) { for (var x=0; x<width; x++) ... }. Caching the ImageData width, height and data properties to local variables is slightly faster than not (except in the 2D case, where it's a good bit faster). Stay away from crazy counting hacks, they're more likely to slow you down than speed you up (probably because they make the compiler's job harder and it can't optimize the code properly).

Based on the above, my preferred pixel loop format is:

var width = id.width;
var height = id.height;
var data = id.data;
for (var y=0; y<height; y++) {
for (var x=0; x<width; x++) {
var off = (y*width + x) * 4;
var r = data[off];
var g = data[off+1];
var b = data[off+2];
var a = data[off+3];
// ...
}
}

And if you just need to map over the pixels and don't care about the coords, the simple 1D loop:

var data = id.data; // or just use id.data directly, no big perf impact
for (var i=0; i<data.length; i+=4) {
var r = data[i];
var g = data[i+1];
var b = data[i+2];
var a = data[i+3];
// ...
}


Sparse blitting vs. dense blitting: If all you need to do is change the values of a small number of pixels, use fillRect. If you need to fill the entire canvas or a significant portion (say, 256x256) of it with custom data, use putImageData. There's no major performance difference between clearing an ImageData buffer before use vs. allocating a new one, so I'd go with clearing the buffer just to avoid extra GC work.

Text drawing: fillText is significantly faster than strokeText. Whether the text is aligned on an integer pixel only seems to matter on Opera's fillText.

Path drawing: Nothing very conclusive, path point count doesn't seem to have a major impact on path point throughput.

Spritesheets: For best performance, cache your spritesheet frames to separate canvases.

Image drawing: Align your images to integer pixels, don't transform them, use canvas elements instead of IMGs. Transformations perform very badly on non-accelerated canvases. Just offsetting an image by fractional pixels causes the browser to use the slow path. On accelerated canvases, transformations don't really matter all that much. You still get the best perf by doing aligned non-transformed draws though.

Clearing the canvas: Just use clearRect.

WebGL texture sources: Use premultiplied textures if possible. Use ImageData if possible, just don't specify it as premultiplied (same goes for typed arrays). Canvases are faster than images on Chrome, images are faster than canvases on Firefox. Typed arrays are about as fast as canvases.

Blog Archive