My Profile Photo

Welcome


This is the little corner of Dinesh, where you can find his thoughts, work and anything else he wants to share.


Little QML/QtQuick Performance Wins

Over the years, I worked on quite a few Qt/QML projects. Here are some common pitfalls and fixes we used to get better performance and buttery smooth UIs from those apps.


Minimize onPaint events for your custom Canvas elements.

This one should be obvious. I have seen a lot of HTML5/JS canvas code copied into QML Canvas elements. Usually they copy the whole code into a single Canvas element that redraws everything on every update. There are better ways to do this.

For example, Take the case of this basic speedometer. Speedometer

A naive way to implement this would be:

Canvas {
  id: speedometer
  // Value goes from 0 -> 1
  property real value: 0.5
  onPaint: drawEverything()
  onValueChanged: drawEverything()
}

The problem with this approach is that, every time you want to update the speedometer’s value, it redraws everything onto a texture and then uploads it to the GPU. That puts a lot of load on the CPU. Especially on already underpowered ARM CPUs.

The same code can be rewritten as:

Canvas {
  id: speedometerBackground
  onPaint: drawBackground()
}

Canvas {
  id: speedometerNeedle
  // Value goes from 0 -> 1
  property real value: 0.5
  rotation: 360*value
  onPaint: drawNeedle()
}

This allows you to draw the background and the needle textures and upload it to the GPU once. Then, You spend no extra CPU / GPU resources to move the needle, whenever the value is updated.

This little trick saved us so much CPU that it made a lot of other problems disappear.

GLSL Shaders are your friend

QtQuick comes with a rich set of graphical effects that run completely on the GPU. There will be a lot of times when using the GPU to draw/modify your Items is more efficient. It also frees your CPU for other tasks.

For eg. take the following simplified battery widget, made using something like:

Canvas {
  id: batteryItem
  // value goes from 0 -> 1
  property real value: 0.5
  onValueChanged: drawBattery()
  onPaint: drawBattery()
}

Battery Item

You can draw it entirely on the GPU using something like:

Canvas {
  id: batteryItemTemplate
  onPaint: drawBatteryTemplate()
}

Which paints a template like:

Battery Template

and the following ShaderEffect to use the template to draw your actual Battery Item:

ShaderEffect {
  id: batteryItem

  // value goes from 0 -> 1
  property real value: 0.9
  
  property var base: batteryItemTemplate
  property color frontColor: "#08F074"

  fragmentShader: "
        varying highp vec2 qt_TexCoord0;
        uniform lowp float qt_Opacity;

        uniform sampler2D base;
        uniform lowp vec4 frontColor;

        void main() {
            lowp float a = texture2D(base,qt_TexCoord0).a;
            lowp float v = step(value, qt_TexCoord0.y);
            gl_FragColor = frontColor * qt_Opacity * a * v;
        }"
}

Be mindful of putting too much logic into your shaders

Keep the fragment shaders very lean by precomputing as much as you can. As the fragment shaders run once per fragment/pixel, any computation you can avoid per pixel is a lot of savings!

Examples:

  1. Using an opacity mask to check the boundaries of a pixel inside a shader, is a lot more flexible and faster than manually checking if each pixel lies in between 2 circles with an equation. You compute the mask once and reuse it every frame.
  2. Converting ~20 line color balance modifier shader into a 3 line shader let us go from a choppy 360p video to a smooth 720p video. You can read more about it here

Here are more GLSL performance tips: https://www.khronos.org/opengl/wiki/GLSL_Optimizations

Use SortFilterProxyModel for filtering and sorting your models

I have seen this happen a lot. People want to filter out items in a list view, and for this they tend to set the ListView’s delegate’s visible: false upon some condition. On top of this I have also seen people using Repeater + Column instead of ListView or TableView.

Both of these “mistakes” absolutely blow up the memory usage of a program and make the whole UI very sluggish because of all the invisible elements that are created. Please avoid this and use SortFilterProxyModel for all your model sorting / filtering needs. Using proper Models also makes your code cleaner.

All these little fixes let us go from 10-15fps at 100% CPU usage to smooth 60fps with < 20% CPU usage on our underpowered embedded systems.

Happy Coding!