fabricjs / fabric.js

Javascript Canvas Library, SVG-to-Canvas (& canvas-to-SVG) Parser
http://fabricjs.com
Other
29.03k stars 3.51k forks source link

Using pre-rendering techniques [$25] #318

Closed kingschnulli closed 7 years ago

kingschnulli commented 11 years ago

Currently the _draw implementation of static canvas will always do a full render of each object - no matter what has been modified. The HTML5 Canvas best practices suggest to render an object only once if you have a lot of render passes and then just draw the bitmap representation if needed - this is equal to what flash does using the cacheAsBitmap property and it's display list .

Without caching:

// canvas, context are defined
function render() {
  drawMario(context);
  requestAnimationFrame(render);
}

Using caching:

var m_canvas = document.createElement('canvas');
m_canvas.width = 64;
m_canvas.height = 64;
var m_context = m_canvas.getContext(‘2d’);
drawMario(m_context);

function render() {
  context.drawImage(m_canvas, 0, 0);
  requestAnimationFrame(render);
}

For Fabric this would mean that the objects manage a property to tell if a redraw is really needed - this could be bound to the stateProperties. Then the static_canvas will use this property to call render or not. After rendering the bitmap will be cached and reused on the next calls.

Maybe I just did not inspect the code good enough, is fabric maybe already doing this?

See:

http://www.html5rocks.com/en/tutorials/canvas/performance/

There is a $25 open bounty on this issue. Add to the bounty at Bountysource.

kangax commented 11 years ago

No, Fabric is not doing this kind of caching yet. We tried some optimizations once with complex SVG paths (replacing them with temporary images) but ran into few problems and eventually dropped it.

Avoiding rendering of objects that weren't modified could be a great optimization. There are some issues though, like the fact that we would need to mark objects as "dirty" somehow. For example, this wouldn't "work" anymore:

object.left = 100; // new value
canvas.renderAll(); // need to mark object dirty before rendering

although this could be "fixed" via set:

object.setLeft(100); // object is marked as dirty
canvas.renderAll(); // object is rendered at a new location

Would it be OK for objects to behave this way? Even though it's already recommended to use set/get methods to work with properties, I imagine that a lot of people still use plain properties.

On the other hand, we can always enable something like this via optional property on canvas! That way we could preserve backwards compatibility.

kingschnulli commented 11 years ago

For backwards compatibility this should be an opt-in option on the canvas object, and refactoring the code to use the set methods is no problem from what I see. I think this will give a big improvement on the performance of canvas that hold a lot of objects, very complex objects and also it will do a good job for path objects and all transformations done on them.

Maybe fabric.Object should have a method 'setDirty()' as well to mark objects for re-rendering if you are running into unusual usage somehow.

All of this reminds me of what flex is doing with their framework implementation which keeps rendering cycles and all other computations down to a minimum set needed:

http://weblog.mrinalwadhwa.com/wp-content/uploads/2009/06/flex-4-component-lifecycle.pdf

kangax commented 11 years ago

Feel free to take a stab at it! One problem I can think of is that we'll need to take care of clearContext, since we can't just stop rendering objects if they're dirty. They'll be erased forever since clearContext is called on every renderAll.

On Sat, Nov 24, 2012 at 6:46 PM, aggrosoft notifications@github.com wrote:

For backwards compatibility this should be an opt-in option on the canvas object, and refactoring the code to use the set methods is no problem from what I see. I think this will give a big improvement on the performance of canvas that hold a lot of objects, very complex objects and also it will do a good job for path objects and all transformations done on them.

Maybe fabric.Object should have a method 'setDirty()' as well to mark objects for re-rendering if you are running into unusual usage somehow.

— Reply to this email directly or view it on GitHubhttps://github.com/kangax/fabric.js/issues/318#issuecomment-10680531.

alesdotio commented 11 years ago

Any updates regarding this feature? Personally, I would really benefit from it, since I am using fabric for editing more 500+ objects.

It really starts to be a problem when you drag objects for example. Even though you are dragging only one object, fabric redraws all of them. I could easily live with manually marking objects that do not need re-drawing, and manually un-marking them when once ready.

kangax commented 11 years ago

@alesdotio Didn't have a chance to do this yet, but also can't wait to get it done already.

jamespacileo commented 11 years ago

@kangax this would be really useful.

I like EaselJS' implementation as it's abstracted away, KineticJS' implementation can be a bit tedious at times. I don't mind if the cache needs to be manually invalidated by calling a method e.g. updateCache/invalidateCache. I don't think auto invalidating of cache would be necessary to start with.

There are plenty of use cases where this would be useful, especially mobile games where performance is very important.

I really like Fabric's API, if it had caching as well it would definitely be my go to library. :)

kangax commented 11 years ago

@jamespacileo I just changed a tag on this ticket from "possible feature" to "feature" :) Can you tell me more about Easel's implementation, or at least point to their docs/source? I'd be curious to get familiar with it.

jamespacileo commented 11 years ago

@kangax thanks! I'll report back tomorrow! :)

jamespacileo commented 11 years ago

@kangax here is what EaselJS does

each Shape has the following methods cache uncache updateCache.

The cache method is used to mark a shape for caching, The content is rendered in an offscreen canvas. Created via createElement and not appended to DOM. The created canvas would fit the shape size times the scale.

uncache is used to invalidate and turn off caching.

updateCache is used to invalidate the cached data

Once a shape is marked for caching on the next redraw the shape will be drawn to it's own "offscreen" canvas object. On every subsequent redraw the shape will use the image drawn into the offscreen canvas object instead of drawing from scratch. If the cache needs updating updateCache would have to be called, on the following redraw the offscreen canvas is rewritten.

The only weakness is that the cache requires the user to manually supply x, y, width, height. FabricJS keeps track of these values so maybe they could be defaulted?

Quote from EaselJS docs:

Draws the display object into a new canvas, which is then used for subsequent draws. For complex content that does not change frequently (ex. a Container with many children that do not move, or a complex vector Shape), this can provide for much faster rendering because the content does not need to be re-rendered each tick. The cached display object can be moved, rotated, faded, etc freely, however if it's content changes, you must manually update the cache by calling updateCache() or cache() again. You must specify the cache area via the x, y, w, and h parameters. This defines the rectangle that will be rendered and cached using this display object's coordinates. For example if you defined a Shape that drew a circle at 0, 0 with a radius of 25, you could call myShape.cache(-25, -25, 50, 50) to cache the full shape.

Useful links: shape.cache(x, y, width, height, [scale=1]) shape.uncache() shape.updateCache()

Usage example

kangax commented 11 years ago

@jamespacileo Thanks for explanation! This makes perfect sense, of course. Fun fact: we had this functionality back in the days. You can see the "stub" rendering block in an older version of all.js, for example, https://github.com/kangax/fabric.js/blob/a535b004e697f57394d85157e92596fad773d010/dist/all.js#L6623-L6633

The difficulties were with updating cached image when resizing a (vector) shape. At one point, I decided to drop it.

This was back in the days when we didn't even have fabric.StaticCanvas, which is kind of perfect for this case, since there's no user interaction with shapes.

kangax commented 11 years ago

@jamespacileo this kind of caching, coupled with dirty-rectangle checking should provide tremendous speed improvements in Fabric

gregtap commented 11 years ago

Will this somehow, improves performance of pen tool on a large canvas ? > 3000px. I am looking for some technical trick, maybe having a dynamicly resizing canvas over topCanvas to receive and draw the pen mouvements.

kangax commented 11 years ago

Dirty rectangle checking should improve performance in this case.

Sent from my iPhone

On Jul 20, 2013, at 13:12, coulix notifications@github.com wrote:

Will this somehow, improves performance of pen tool on a large canvas ? > 3000px. I am looking for some technical trick, maybe having a dynamicly resizing canvas over topCanvas to receive and draw the pen mouvements.

— Reply to this email directly or view it on GitHub.

jamespacileo commented 11 years ago

@kangax awesome, great news! :)

Thanks for considering this. I'll be around, contribute more info if I can.

Samreay commented 10 years ago

Hey guys. I'm looking to use Fabricjs for in a scientific computing context, and would be rendering 20,000 items to screen (three layers of graphs, each displaying astronomical spectra). Has there been any update on caching or dirty-rectangle checking for Fabric?

I have been able to find this: http://fabricjs.com/svg-caching/, however this is not the dirty update method discussed for this feature request.

kangax commented 10 years ago

@Samreay Unfortunately, we haven't gotten to these optimization techniques yet. However, if you don't need interaction, it's fairly easy to create 3 fabric canvases overlayed on top of each other.

Samreay commented 10 years ago

Hi kangax. Thanks for the update mate.

matte00 commented 10 years ago

Thank you kangax for your great library! Have you got news for this request?

kangax commented 10 years ago

I will make sure to update this ticket once there's news

elliot-chart commented 9 years ago

Is there any news on this request?

If not, if you any notes/plans on how you were planning on implementing this then I would be happy to contribute to the project!

kangax commented 9 years ago

We'll be focusing on performance improvements in the upcoming release. Some things are already in effect — for example, we do itext cursor rendering on a separate canvas. Notes on implementing — hard to tell; these are the usual common techniques used in games, etc. so we just need to adapt them according to fabric's architecture. Any help is welcome of course!

jwmao commented 8 years ago

@kangax , any updates regarding the performance improvements using cache. My application is having performance issues when rendering objects on large canvas. Especially on Chrome with hardware acceleration turned on. There are not a lot of objects on my canvas, my canvas size is 3050px X 3050px. The dragging is very slow and same for mouse clicks. I'm just wondering with caching, it can improve the overall performance.

asturur commented 8 years ago

with a 9Mega pixel canvas your problem is probably the use of clearRect() for the full canvas. you would benefit nearly nothing from object caching, probably more from dirty rects (another possible feature)

asturur commented 8 years ago

I opened a PR to address this. It is in usable state on most shapes, need some testing. It does not scale yet. Is a planned feature for 1.7.0, cache will be enabled on by default if it does not affect performance on small quantity of objects.

asturur commented 7 years ago

closed with #3317