Reputation: 52987
I'm coding a JavaScript game. This game obviously needs to be constantly rendering a screen, which, using canvas, must be an Uint8Array(width * height * 4)
(as used by Canvas's ImageData
). In order to test the expected FPS, I've tried filling that array with white. Much to my surprise, the performance was mediocre. I could barely fill a 1000x1000 bitmap with white in a high-end computer at 150 fps. Considering this is the best performance, without any game logic running, the end result will probably be much lower. My question is: why is the performance so low and what can I do to improve it?
Upvotes: 0
Views: 272
Reputation: 566
See these 2 tests
Only doing loop and assign to the same address
http://jsperf.com/variable-assign
Good old array expansion (and also another popular for loop trick that suppose to be faster)
http://jsperf.com/fill-an-type-array-expand/3
First test shows the address look up is taking about 3/4 of the time.
Second test shows the for loop is taking more than 30% of the time.
I think what typed array is really in lack of is some native code that does the block copy that would really be used in a game development (rather than set pixel by pixel in the tests). WebGL might worth considering.
Upvotes: 1
Reputation:
Figuring out how many times you can fill a 1000x1000 canvas using putImageData will not give you any kind of realistic results. The reason is the graphics are pipelined. In a normal app you'd only call putImageData once a frame. If you call it more than once a frame at some point you'll fill the pipeline and stall it. In a real app though you'd be manipulating your data most the frame and only uploading it once not stalling the pipeline.
If you want to see how much work you can do, make a 1000x1000 canvas, call getImageData on it to get an ImageData, manipulate that image data a certain amount, call putImageData, then call requestAnimationFrame and do it again. Slowly increase the amount of manipulation you do until it starts running slower than 60fps. That will tell you how much work you can do realistically.
Here's a fiddle that tries this out http://jsfiddle.net/greggman/TVA34/
Also, using putImageData has a few issues. One is that Canvas requires pre-multiplied alpha but putImageData supplies un-premultiplied alpha which means some process has to convert your data to pre-multiplied alpha. Then, now-a-days, most Canvas implementations are GPU accelerated. That's great for nearly every feature of the Canvas API but it sucks for putImageData since that data has to be transferred from the CPU to the GPU. It's even worse for getImageData since copying data from the GPU back to the CPU generally stalls the GPU. The stories is even more bad on an HD-DPI machine. getImageData and putImageData have to convert from CSS pixels to the resolution that's actually being used.
If you use WebGL you can at least skip the pre-multiplied alpha conversion step.
Here's a WebGL version http://jsfiddle.net/greggman/XLgs6/
Interestingly on my 2012 Macbook Pro Retina I find the canvas version is faster on Chrome and Safari. I'm curious why since I would not expect that having worked on them.
/*
Canvas WebGL
Chrome 32 : 710k 650k numbers are in 'operations per frame`
Firefox 26 : 80k 190k
Safari 7.0.1 : 150k 120k
*/
My test also might not be valid. Manipulating only 710k pixels (of 1000k pixels) seems pretty slow. Maybe one of the functions like Math.random
or Math.floor
is particularly slow, especially given they're using doubles.
Upvotes: 3