Qt Quick Batching

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.

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", this is what we are hitting in our example for the elements in the sliders.

Scene graph inspection

After having established that our problem comes from unmerged batched in the scene graph renderer we need to investigate what prevents merging in our case. Batches can't be merged when non-opaque items overlap, or when they have a non-trivial transformation matrix. As our code does neither overlap nor use transparency we can look at the transformation matrices.

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

Looking at the combinedMatrix property of any node belonging to a slider in the Properties view we notice that at least one value of the diagonal is negative, indicating a rotation. This is preventing the batch merging. In order to identify the culprit, we can move up the hierarchy to the first node introducing this transformation.

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

ListView {
    id: view
    anchors.fill: parent
    anchors.rightMargin: handle.width

    rotation: 180 // for bottom -> top layouting
    orientation: Qt.Vertical
    interactive: false

In order to verify that this is 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 rotation live. After doing this we see that the left slider is no having a trivial diagonal in its combined transformation matrix, and thus is rendered with a single OpenGL draw call. For the right slider we have to repeat this procedure to identify a second transformation:

transform: Rotation {
    angle: mirrorSlider ? 180 : 0
    axis { x: 0; y: 1; z: 0 }
    origin { x: root.width/2; y: root.height/2 }
}

Just removing the transformations is of course not a proper fix (as it changes the visual appearance of the application), it merely verifies that it's these rotations causing our performance problem. You would now need to refactor the code in a way that it achieves the same behavior and visual appearance without the use of rotational transformations.

Files:

© 2016-2018 Klarälvdalens Datakonsult AB (KDAB). Documentation contributions included herein are the copyrights of their respective owners.
This work is licensed under a Creative Commons Attribution-ShareAlike 4.0 International License.