Qt Quick Batching

Investiagte render performance issues due to unmerged render batches.

This examples shows GammaRay's capabilities for analyzing Qt Quick batch rendering issues.

Problem

The example application shows two custom Qt Quick sliders consisting of flat colored rectangles forming a gradient from green to blue. Inactive rectangles (that is rectangles above the handle) are drawn smaller and slightly grayed out.

Slider {
    id: leftSlider
    anchors.top: parent.top
    anchors.bottom: parent.bottom
    width: myWindow.width / 2 - 1.5*myRow.spacing
}

Slider {
    id: rightSlider
    anchors.top: parent.top
    anchors.bottom: parent.bottom
    width: myWindow.width / 2 - 1.5*myRow.spacing
    mirrorSlider: true
}

Observed in a OpenGL tracing tool or profiler, it can be seen however that the two sliders take several hundred OpenGL calls to be rendered. Closer investigation shows that each rectangle results in a separate OpenGL draw call.

Investigation

There are several aspects of this problem that can be analyzed with GammaRay.

Scene graph render diagnostic messages

The Qt Quick renderer has some built-in diagnostic messages that can sometimes be helpful in such a scenario. Turning them on unconditionally can be inconvenient though, as they are usually triggered by frame. It is therefore useful to only enable them for a short amount of time while triggering the relevant operation in the target application.

This can be done using the GammaRay Messages tool. Open the Logging tab, find debug categories you are interested in (qt.scenegraph.* or qt.quick.* for example), and enable the checkbox in the Debug column.

Scene graph render visualziation

The Qt Quick renderer also has a number of built-in diagnostic visualizations. Those can be usually only enabled at application startup. This is inconvenient however if you first need to navigate to the relevant part of your application to investigate a specific issue.

The GammaRay Qt Quick 2 Inspector allows to enable these diagnostic render modes at runtime too (only available in some Qt versions). The toolbar above the scene view in the lower left part of the Qt Quick inspector view has toggle actions for them.

In our example, the batch rendering visualization is particularly interesting. Areas of the same color indicate batches of elements that are rendered in a single draw call. This is the optimal scenario we are trying to achieve. Areas with diagonal lines represent sets of items that have some Common state but are rendered by individual draw calls. The scene graph renderer calls this case "unmerged batch". In our example one observes a colorful output with all inactive elements in the sliders in their own colors.

Scene graph inspection

After having established that our problem comes from rectangles not being batched in the scene graph renderer, we need to investigate what prevents batching in our case. Items for example can't be batched when they use clipping or differ in their opacity. For Qt versions < 5.8 batching is also disabled if non-opaque items overlap, or when Items have a non-trivial transformation matrix.

For this we use the scene graph view of the Qt Quick 2 Inspector view in GammaRay. It shows the internal scene graph items, which is what the renderer uses as input for the batching.

Comparing the subtrees belonging to active and inactive rectangles scenegraph-node tree view, we notice that the inactive ones have an Opacity node, while the active ones don't. The opacity node indicates transparency.

Now we can check the opacity property in the Properties tab of the Item view, so we switch back to "Items". The Item corresponding to the previously selected Opacity node is already pre-selected, so we only need to look for the property. In fact we find it to have value < 1.

In order to match this to the actual code causing the problem, use the context menu action "Go to declaration", which will open a code editor around the following piece of code:

Rectangle {
    property bool active: view.currentIndex <= index
    anchors.right: parent.right
    width: parent.width
    height: parent.height - 3
    color: Qt.hsva((index/view.count)/3, active ? 1.0 : 0.5, active ? 1.0 : 0.75)
    opacity: active ? 1.0 : 1.0 - (view.currentIndex - index) / view.model
    scale: active ? 1.0 : 0.9
}

In fact we see that the opacity value is set to a value depending on the index. That means that all rectangles get a different opacity value and that rules out batching. In order to verify that this is actually our problem you don't need to restart the application, you can also use the editing capabilities of the Properties tab in the item tree view to adjust the opacity live. If you still have the batch rendering visualization activated, after setting the opacity to 1, you'll observe how the selected rectangle gets the same color as all the active rectangles.

Just removing the opacity is of course not a proper fix, it merely verifies that it's these opacities causing our performance problem. To retain the same visual appearance as with opacity set, you could e.g. use the alpha component of the color property to obtain the transparency, without impairing the batching.

Files: