No Harm In Trying
No Harm In Trying

Reputation: 493

How does 2D drawing frameworks such as Pixi.js make canvas drawing faster?

I found a bunnymark for Javascript canvas here.

Now of course, I understand their default renderer is using webGL but I am only interested in the native 2D context performance for now. I disabled webGL on firefox and after spawning 16500 bunnies, the counter showed a FPS of 25. I decided to wrote my own little very simple rendering loop to see how much overhead Pixi added. To my surprise, I only got a FPS of 20.

My roughly equivalent JSFiddle.

So I decided to take a look into their source here and it doesn't appear to be that the magic is in their rendering code:

do  
{
    transform = displayObject.worldTransform;
            ...
    if(displayObject instanceof PIXI.Sprite)
    {

        var frame = displayObject.texture.frame;

        if(frame)
        {
            context.globalAlpha = displayObject.worldAlpha;

            context.setTransform(transform[0], transform[3], transform[1], transform[4], transform[2], transform[5]);

            context.drawImage(displayObject.texture.baseTexture.source, 
                               frame.x,
                               frame.y,
                               frame.width,
                               frame.height,
                               (displayObject.anchor.x) * -frame.width, 
                               (displayObject.anchor.y) * -frame.height,
                               frame.width,
                               frame.height);
        }                      
    }

Curiously, it seems they are using a linked list for their rendering loop and a profile on both app shows that while my version allocates the same amount of cpu time per frame, their implementation shows cpu usage in spikes.

My knowledge ends here unfortunately and I am curious if anyone can shed some light on whats going on.

Upvotes: 21

Views: 8587

Answers (3)

user1693593
user1693593

Reputation:

I think, in my opinion, that it boils down to how "compilable" (cache-able) the code is. Chrome and Firefox uses two different JavaScript "compilers"/engines as we know which optimizes and caching code differently.

Canvas operations

Using transform versus direct coordinates should not have an impact as setting a transform merely updates the matrix which is in any case is used with what-ever is in it.

The type of position values can affect performance though, float versus integer values, but as both your fiddle and PIXI seem to use floats only this is not the key here.

So here I don't think canvas is the cause of the difference.

Variable and property caching

(I got unintentionally too focused on the prototypal aspect in the first version of this answer. The essence I was trying to get at was mainly object traversing, so here the following text is re-worded a bit -)

PIXI uses object properties as the fiddle but these custom objects in PIXI are smaller in size so the traversing of the object tree takes less time compared to what it takes to traverse a larger object such as canvas or image (a property such as width would also be at the end of this object).

It's a well known classic optimization trick to cache variables due to this very reason (traverse time). The effect is less today as the engines has become smarter, especially V8 in Chrome which seem to be able to predict/cache this better internally, while in Firefox it seem to still have a some impact not to cache these variables in code.

Does it matter performance-wise? For short operations very little, but drawing 16,500 bunnies onto canvas is demanding and do gain a benefit from doing this (in FF) so any micro-optimization do actually count in situations such as this.

Demos

I prototyped the "renderer" to get even closer to PIXI as well as caching the object properties. This gave a performance burst in Firefox:
http://jsfiddle.net/AbdiasSoftware/2Dbys/8/

I used a slow computer (to scale the impact) which ran your fiddle at about 5 FPS. After caching the values it ran at 6-7 fps which is more than 20% increase on this computer showing it do have an effect. On a computer with a larger CPU instruction cache and so forth the effect may be less, but it's there as this is related to the FF engine itself (disclaimer: I am not claiming this to be a scientific test however, only a pointer :-) ).

/// cache object properties
var lastTime = 0,
    w = canvas.width,
    h = canvas.height,
    iw = image.width,
    ih = image.height;

This next version caches these variables as properties on an object (itself) to show that also this improves performance compared to using large global objects directly - result about the same as above:
http://jsfiddle.net/AbdiasSoftware/2Dbys/9/

var RENDER = function () {
    this.width = canvas.width;
    this.height = canvas.height;
    this.imageWidth = image.width;
    this.imageHeight = image.height;
}

In conclusion

I am certain based on the results and previous experience that PIXI can run the code faster due to using custom small-sized objects rather than getting the properties directly from large objects (elements) such as canvas and image.

The FF engine seem not yet to be as "smart" as the V8 engine in regard to object traversing of tree and branches so caching variables do have an impact in FF which comes to display when the demand is high (such as when drawing 16,500 bunnies per "frame").

Upvotes: 11

Stefan Haustein
Stefan Haustein

Reputation: 18793

I'd suspect that this is some canvas compositing issue. Canvas is transparent by default, so the page background needs to be combined with the canvas contents...

I found this in their source...

// update the background color
if (this.view.style.backgroundColor != stage.backgroundColorString &&
    !this.transparent) {
    this.view.style.backgroundColor = stage.backgroundColorString;
}

Maybe they set the canvas to be opaque for this demo (the fiddle doesn't really work for me, seems like most of the bunnys jump out with an extremely large dt most of the time)?

I don't think it's an object property access timing / compilability thing: The point is valid, but I don't think it can explain that much of a difference.

Upvotes: 1

kangax
kangax

Reputation: 39168

One difference I noticed between your version and Pixi's is this:

You render image at certain coordinates by passing x/y straight to drawImage function:

drawImage(img, x, y, ...);

..whereas Pixi translates entire canvas context, and then draws image at 0/0 (of already shifted context):

setTransform(1, 0, 0, 1, x, y);
drawImage(img, 0, 0, ...);

They also pass more arguments to drawImage; arguments that control "destination rectangle"dx, dy, dw, dh.

I suspected this is where speed difference hides. However, changing your test to use same "technique" doesn't really make things better.

But there's something else...

I clocked bunnies to 5000, disabled WebGL, and Pixi actually performs worse than the custom fiddle version.

I get ~27 FPS on Pixi:

enter image description here

and ~32-35 FPS on Fiddle:

enter image description here

This is all on Chrome 33.0.1712.4 dev, Mac OS X.

Upvotes: 7

Related Questions