In my previous post, I talked about an important issue encountered with Qt3D 2.0 that would not allow Item3D in Repeater or Loader element to be loaded by the rendering engine.
Before entering into the details of this bug solving, I’ll quickly explained how Qt3D is integrated with the new QtQuick 2.0 scenegraph.
Basically, the QtQuick 2.0 scenegraph builds a tree composed of Qml Items aka QQuickItem from a C++ point of view. Each node in the this tree is the geometric shape of the corresponding Qml Item element. The goal is to optimize the tree so that rendering can be made as quickly as possible by grouping items that have the same color and rendering only items whose shapes have changed between the last rendering and so on. It is a very interesting subject but it deserves its own post and if you’d like to know more about it, you can read the content provided by the following links :
To come back to the point, the scenegraph only renders QQuickItem based classes that implement the virtual method updatePaintNode. If you look at the sources of the QQuickItem3D class, you’ll see that it inherits from QQuickItem. However, QQuickItem3D’s 3D content cannot be rendered by the same QtQuick 2.0 scenegraph as it has been made specifically for 2D content. Therefore, the updatePaintNode method has not been implemented in the QQuickItem3D class and its node in the scenegraph tree has actually no geometrical shape. This means that all QQuickItem3D instances are rendered as empty elements by the scenegraph.
Eventually the 3D content has to be rendered and that’s where the Qt signal beforeRendering() emitted by the scenegraph comes into play. In order to provide ways to integrate visual content under or over a Qml UI, the scenegraph provides two signals, beforeRendering and afterRendering that are emitted in that same order, before and after the Qml rendering takes place.
That way, the rendering of 3D elements can be made using the same OpenGL context as the QtQuick 2.0 UI. The choice of the beforeRendering rather than the afterRendering is only due to the fact that it is better to have 3D content rendered before standard Qml that would otherwise have been covered by it, if it had been rendered after. As Qml is mainly used for menus and elements that come above graphics, this is what makes the most sense.
To quickly sum up what has been said, here are the steps of the rendering process:
- beforeRendering signal is emitted
- Qt3D elements are rendered
- QQuickItem elements are rendered by the scenegraph
- afterRendering signal is emitted
If you have already used the QtQuick3D API, you’ll know that in order to have Item3D elements rendered, these need to be contained inside a Viewport element, although if you tried without, it might have worked but I’ll explain later why.
When the beforeRendering signal is emitted, Qt3D uses it to trigger the beforeRendering slot that will then render QtQuick3D items. In order for the rendering to work, it has to know where Item3D items are contained. By having a Viewport element, you can easily identify them.
The rendering process is straightforward and works as follow :
- Foreach Item3D in the viewport
- Call the draw method of the QQuickItem3D
- The QQuickItem3D draws its content
- The QQuickItem3D takes care of calling the draw method of all of its level 1 children
- All level 1 children call the drawing on level 2 children
- And so on ….
After that much reading you might be wondering why am I talking about scenegraph and Qt3D rendering for a Loader and Repeater bug that seems to have nothing to do with that.
Well actually it does ! When you create an Item3D within a parent Item3D, on the creation of that child item, Qt3D tries to find its nearest parent.
In the original code, what was done was that on insertion, there would be a check to see whether the first parent was actually an Item3D or not. If it was, then the parent would be assigned as the item’s parent. On the other hand, if it wasn’t, a Viewport element would be inserted as a direct parent, the reason why the rendering of Item3D not in a Viewport element might have worked, if you remember from above.
This takes place in the componentComplete method of qquickitem3d.cpp (around line 1400)
Unfortunately this does not take into account the case where the direct parent of an Item3D might be a QQuickItem aka a Loader or Repeater element.
The idea I came up with to solve that issue was to change the check made on insertion of a QQuickItem3D.
When an Item3D is inserted, I check if the direct parent is a QQuickItem3D item or the Viewport itself. If it isn’t I try to check the parent of the parent and so on until one is found that satisfy. If a parent is found, it is then set as the rendering parent of the Item3D. Otherwise if none was found, a Viewport element is created like it was in the original code.
That actually solved the issue. I tried to submit it to Qt but it was actually never approved as it did not pass the tests. I really don’t believe the tests failing comes from the fix but who knows. In the meantime this fix has been implemented in the tepee3d-qt3d fork available at http://gitorious.org/qt/tepee3d-qt3d
Feel free to use it, copy it, modify it and so on. When activity on the Qt3D module comes back once Sean Harmer and James Turner have redesigned it, I’ll try to submit the fix if still needed.