Drawing a graph in Qt Quick 2.0
I'm trying to build a graph visualization tool (i.e. like the git network view in github). I would like to make a small discussion on the performance issues I have found and a possible way to solve them. I'm still using 5.0.0 on a Desktop PC.
Suppose I have a (big) list of nodes , connected via line-strips. Only a small parts of the nodes and lines will be visible at once.
I have tryed to add an Item for each commit , using a rounded rectangle. The performance of QML 2 is much better thanks to the scene graph, as the invisible items get culled.
The bottleneck here was the item creation. I was creating a QmlComponent , and then creating all the items with createObject() and setParentItem(). Even for a relatively small number of items , I was getting a big load time.
Now I ask : is this expected? This wastes the scene graph benefit , because while the rendering is fast, the scene creation is still too slow.
Anyway, I ended up by dynamically creating/destrying the items in the visible area (as suggested by the performance guidelines), and I get a very good performance, and low memory usage.
Now the problem, which I'm stuck on: connecting lines
as first naive attempt (just to get a starting point), I created a Canvas for each connection line. It kinda works, but it takes a lot of memory, because the Canvas allocates a buffer large as the item bounding box.
Before continuing, I would like to ask an opinion, between these three options, keeping in mind that I could have to draw around 100/200 connections per frame, and that the position of the lines is likely to change at every frame...
The canvas is contained in a flickable element, and I update the visible items while scrolling (each 100ms). So, the lines position will change very often.
Use the same approach with a QSGPaintedItem. The drawback is that, like the canvas, I have to use a shared item for all the lines, but I could use a QPainter in C++ and gain some performance
Use directly an QSGItem, but then I have to handle all the buffers and materials by myself, I don't know if it is worth. Even with this approach, I guess I would have to use a "shared" node, I don't think that creating many small geometry nodes is good from a performance point of view, but I don't know so I'm here asking... Also, I will need to reimplement some polygon tessellation code to get a decent visualization. The good of this approach could be that it doesn't use a backing buffer, so less memory will be used.
Also, I wanted to add some animations and transition to each connection line, but by sharing an item for visualization purposes I probably loose the chance to do these.
What would you suggest?
Thank you for any hint!
Nice detailed question!
Unfortunately I won't be much help here, except to say:
You're absolutely right that object instantiation is a large bottleneck. A few improvements I can suggest: instead of using createObject() and then setParentItem(), use the beginCreateObject() then setParentItem() then completeCreate() - that way the bindings which involve the parent will only be evaluated once. Secondly, depending on the use case, it may be worthwhile using an explicit pool allocator for your type, to ensure fast allocation and good alignment etc.
Creating a Canvas for each connection line is a bad idea, in my opinion. The allocation time is insane, as is the memory usage.
Using a single canvas and drawing all of the visible connections on it would be my first option to try, to be honest. The rendering is pretty fast. See some of the SVG / other Canvas demos (the tree drawer etc) for examples of that. It's worth giving it a try, and benchmarking it, at least, as it should be the least effort of the various options.
I don't know much about QSG classes, so I can't comment on those options.
Regarding animations of each line, well, they're back by a datastructure with position information etc, so you can just repaint each line each frame with the required animation. I guess it depends on performance.
While a single canvas is probably the easiest option, I would recommend taking a look at the scenegraph/customgeometry example. It demonstrates how you can draw a proper bezier line using the scenegraph/opengl API and should explain a lot for the scenegraph magic. This solution would probably be a lot more scalable than the canvas.
thanks for the answers.
chrisadams , I didn't know about beginCreateObject, I'll try it!
Maybe I could use the C++ side of the animation engine, or maybe use a QtObject for each line just to handle state and position, and do the drawing in a shared item...
Jens, this was "option three": using an QSGItem directly. Unfortunately this approach works, but requires to generate a low level tessellation of the geometry. This means that you cannot use all the QPainter goods, and you need to generate the vertexs, color coordinates, textures (line caps, joins,effects,gradients, etc...). This will be much faster but requires a good amout of work. And even in this case I "suspect" that I'll have to share vertex buffers, I don't think creating a number of small nodes/buffers will be so good in terms of performance...