fabricjs / fabric.js

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

[Feature]: Selective rendering – Update only the changed object on the canvas. #9847

Open shashi-drishya opened 6 months ago

shashi-drishya commented 6 months ago

CheckList

Description

The selective rendering feature should enhance canvas performance by only updating the modified object. When an object is altered or manipulated, the canvas should efficiently re-render just that specific object rather than the entire canvas. This selective rendering should be smart enough to update only the necessary portions of the canvas that are impacted by the change.

The feature should accommodate scenarios like moving, scaling, rotating, or modifying object properties. The solution should intelligently track changes, optimize redrawing, and minimize unnecessary updates to improve performance, particularly with complex or crowded canvases.

Additionally, this feature should be intuitive and straightforward to integrate, allowing developers to enhance their applications' performance without extensive refactoring. By focusing on updating only the changed object, the canvas should operate more efficiently, providing smoother user interactions and enhanced application responsiveness.

Current State

In Fabric.js, the default behavior when an object is modified is to re-render the entire canvas. This approach ensures that the canvas's visual state remains consistent, especially when multiple overlapping objects are involved.

However, this can be inefficient in cases where only a single object is updated, as redrawing the whole canvas may involve unnecessary computation, particularly for large or complex canvases. The lack of selective rendering is a key reason why developers sometimes request more optimized rendering features for scenarios where frequent or incremental updates are necessary.

shashi-drishya commented 6 months ago

@asturur, can we have this

ShaMan123 commented 6 months ago

I am currently working on this feature for a private project and it is major when the canvas has 5000+ objects. Not only does rendering improves significantly (50ms dropped to 9ms for each render). The same amount of time is saved if the app tracks the result of isOnScreen (instead of running it from Object#render) and reused by findTarget that won't need to interate through all the objects but only the objects that are visible on screen.

ShaMan123 commented 6 months ago

Also I turned the renderer to be an infinite loop, bailing out the current tick if nothing has changed That seems to be a good design decision inspired by pixi.js

asturur commented 6 months ago

This is part of the roadmap since long and was never started. It will be done after v6 is out and the first fires of documentation and issues are settled.

I think this was in planning since 2018 at least.

asturur commented 6 months ago

The issue with approaching this as a rendering library and not as an app is that tracking is up to you. So when you change the color of an object you know you don't need to call canvas.renderAll but maybe something like canvas.renderObject(object).

When dragging an object that is one of the basic feature of fabric, is already harder because you have to track previous and next position for update, and if there are animations running on the canvas maybe you have to settle with renderAll at the end.

Is a balance between spending time deciding what to re-render and what intersect with it or just rendering everything.

You can expect this to go out as a method for the canvas and then slowly integrate supported actions.

theredsox commented 4 months ago

Just popping in to say something like canvas.renderObject(object) is exactly what I was searching for and would def be useful.

asturur commented 4 months ago

i think it will be something like canvas.renderObjects([objects]), and i will start to work on it as soon as i m done with fabricjs.github.io enough to swap fabricjs.com

zacol commented 4 months ago

@ShaMan123 Can you write more about how you implemented this in your project?

ShaMan123 commented 4 months ago

You need to track the changing objects depending on your business logic and compute the bounding box that contains them. Then you render the rect which is the union of the previous and current dirty rects onto a separate canvas by filtering the objects that overlap it. Finally, you clear the rect from the main canvas and draw the new rect over it. Be sure to round the rect values when you do that to integer values to avoid canvas anti aliasing.