Onaluf.org http://www.onaluf.org write something smart here! en http://www.onaluf.org/en/rss mohiboen onaluf@onaluf.org Performance of per-pixel image access for the JavaScript canvas/en/entry/132010-05-14 00:00:00

Update: Like Marc Weber pointed out to me you don't need to "attache the array back" to the image since you're only manipulating references to the array!

Last week I spent some time optimizing a small canvas demo that I've done with some good results (~40% speedup in Google Chrome). During those hacking session I came up with a general hack that can lead to a big increase in performance. So I thought that it was worth looking at it in details.

The problem

HTML 5 canvas element is a formidable tool and for a lot of things it's really fast (just look at chrome experiments or canvas demo for some nice example). However there is one domain where the speed you can achieve is less than satisfactory: per-pixel writing. Let say you want to fill the canvas surface with pixels generated by some calculation, you will have to write every pixel of the canvas with some new values. The resulting code would look like that

canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); var pixels = SCREEN_WIDTH*SCREEN_HEIGHT; while(--pixels){ image.data[4*pixels+0] = r; // Red value image.data[4*pixels+1] = g; // Green value image.data[4*pixels+2] = b; // Blue value image.data[4*pixels+3] = a; // Alpha value } context.putImageData(image, 0, 0);

For each pixel of the image we have to write 4 values (one for each color and the alpha channel) so if this operations turn out to be slow, this loop will be very inefficient.

The Hack

After listening to a YUI talk about javascript performance insisting on how DOM access was slow and should be avoided I wondered if every write to an element of the data array was a DOM access. This would explain the slowness of it. So I came up with this idea: Why not "detaching" the image's data array to manipulate it and copy it back before rendering. It turned out that it provided a nice speed-up in chrome, so much in fact that I made a simple benchmark to isolate the effect from the big mess of the application I was optimizing.


canvas = document.getElementById("canvas"); context = canvas.getContext("2d"); image = context.getImageData(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT); var pixels = SCREEN_WIDTH*SCREEN_HEIGHT; var imageData = image.data; // here we detach the pixels array from DOM while(--pixels){ imageData[4*pixels+0] = r; // Red value imageData[4*pixels+1] = g; // Green value imageData[4*pixels+2] = b; // Blue value imageData[4*pixels+3] = a; // Alpha value } image.data = imageData; // And here we attache it back (not needed cf. update) context.putImageData(image, 0, 0);

The advantage of this hack is that it is very easy to apply in any situation of this kind.

The Benchmark

I added to this benchmark a test for something else that I noticed earlier on the development of the same application: going from a globally scoped to a more "Crockfordy" namespaced one may sometime have negative effect on performance. So the benchmark tests for a combination of the following: global vs namespaced methods and standard vs "detached" access to the image's data. To run the benchmark just go there and wait for the four tests to end. They are run one after the other, each in its own frame.

Results

There is more information here as is it seems at first sight. The first thing is that caching the image's data array is always a good idea (red values are always under blues values and green always under oranges values). I can only speculate regarding to why that is... it probably has to do with the cost associated with "crossing the DOM bridge" as describe in the talk I mentioned earlier.

You may wonder "What if instead of just writing the value of each pixel I need to read them first?". Well I did too because intuitively it would seams that this should be an even better candidate for this kind of optimisation. It turned out that this give the exact same result as when you're just writing the value. This would seams to indicate that accessing a value of the data array don't require a "flush" of the pending modification on the page (which make sense since the change to those data are not dirrectly reflected on the page).

The second interesting point is: using namespace has a big impact on performance. This as probably to do with the way modern compilers use tracing to speed things up. It seams that namespacing can make it harder for the compiler to find a valid trace through the code. Indeed, if you look at the second result for Firefox without JIT (tracing disabled) you can see that globally scope function loose their advantage.

There may well be a way to use namespace that doesn't break the tracing but I didn't find any.

Final Word

This is just a simple benchmark. Testing javascript is not a simple task and many factors can come into play. Furthermore those results are of no use comparing performances between browsers. This small article is there to start a discussion and I am open to any comments. Post your results if you use a browser not mentioned here!

As always the code is under a MIT License, so don't hesitate to roll you own version of this benchmark!

Brushes App on iPhone/en/entry/122009-08-12 00:00:00

I've been using Brushes on my iPhone for a few days now and I must say that after some time getting use to it, it's a pretty sweet app!

This is an example of what I've drawn yet with the app. It is generated with a small free tool for OS X that can read drawing made with brushes.

Voxel Spacing/en/entry/112009-07-17 00:00:00

After some hours of development here come a small experiment in javascript using canvas and ImageData.You'll find more information on voxel.onaluf.org.

ps. the screenshot is from an higher resolution as what you'll find online. That's because it's really slow otherwise :) ...

gameQuery 0.1/en/entry/102008-06-25 22:09:37

I just published on jQuery's plugins site the alpha version of gameQuery. It's a plugin aimed at easing the development of javascript games. The goal is to give a practical alternative to flash in some specific situation.

Using gameQuery and jQuery allow a higher level and cross-browser approach to the game display.

You can find the gamequery main page here

This is a technical demo of gameQuery usage:

K-Stet/en/entry/92008-05-18 13:28:35

K-Stet is a small game I wrote in OpenGL a while ago. It's not really finished yet and will probably never be... The source code is given with the game. You'll find bellow different versions of the game. It's build on the OpenGLUT framework and uses SDL to read the images files so it shouldn't be to hard to compile it on every reasonable platform but only the win32 binary are given for now.

The aim of this was to test a gameplay idea that turned out to be not as fun as I expected. In the game you control the rotation of a board on which drop colored drop of water fall. The drops should be lead to the hole with the same color. Try it maybe you'll find it more fun than I do.

K-Stet 0.15
K-Stet 0.1
K-Stet 0.05