C

Qt Quick Ultralite Performance Guide

Before applying any of the optimizations described in this section, it is important to gather important performance metrics such as CPU usage, memory consumption, application ROM footprint and so on. See Qt Quick Ultralite Performance Logging for more information. This page describes different optimization techniques to improve performance and reduce memory footprint of the application. Besides these optimization techniques, you should also follow the best practices while writing QML code.

Memory optimization

Flash and RAM footprints can be reduced by using one or more of the following optimization features or techniques:

Enabling resource compression

PNG compression

If ImageFiles.MCU.resourceCompression is set, an image is stored with PNG compression and will be decompressed into the cache when needed. This reduces the size of the binary but adds decompression overhead. See ImageFiles.MCU.resourceCompression to learn more about enabling and using PNG compression in Qt Quick Ultralite applications.

RLE compression

Unlike images using ImageFiles.MCU.resourceCompression, images with pixel formats RGB888RLE, XRGB8888RLE, and ARGB888RLE are decoded on the fly during rendering, and thus don't require any additional storage. The limitation is that scales, rotations, shears, and perspective transforms are not supported for these RLE pixel formats.

See Lossless compressed image formats for more information.

Framebuffer size

Framebuffer size can be optimized by choosing lower color-depth and/or single buffering. Refer to the Framebuffer Requirements page for detailed information about the framebuffer size requirements.

You can also improve performance using the hardware layers if your platform supports it. Refer to Improving performance using hardware layers for more information.

Font quality

The font.quality property allows more fine-grained optimization, by providing hints to render low-quality glyphs. To save memory consumed by glyphs, set the font quality in your QML code to Font.QualityVeryLow.

Refer to MCU.Config.defaultFontQuality for more information.

Application performance

Selecting a framebuffer strategy

Qt Quick Ultralite supports both single and double buffering strategies, offering a choice of better performance over memory usage. The single buffer strategy uses less memory but may come with performance overhead, whereas the double buffer strategy uses more memory but offers better performance.

See Framebuffering strategies for more information.

Selecting a font engine

Qt Quick Ultralite provides two font engine options: Monotype Spark and Static font engines.

Monotype font engine

  • Better support for internationalized applications that use a wide range of characters.
  • Better support for languages that require text shaping to ensure correct layout.
  • Runtime support for text scaling, without compromising on performance and text quality.

Static font engine

  • Better support for applications that do not use complex text and has lower footprint.
  • Operates on static precomputed data, enabling very fast lookup operations.
  • Processes font at build time, resulting in no runtime overhead.

Refer to the Feature comparison table for detailed comparison between the two font engines.

Using cache

Application performance can be enhanced by caching images and text.

Image caching

QmlProject property ImageFiles.MCU.resourceCachePolicy can be used to set the caching policy for image resources.

Note: Source file properties must be set before adding image files to the application using the ImageFiles.files QmlProject property.

Font cache priming

If application contains a lot of text, it affects the application startup significantly. The font cache can be prepopulated at build time to improve startup time of the application.

Note: Cache priming is only supported by Monotype Spark font engine.

See Cache priming for more information.

Text caching

Text rendering performance can be improved further if text cache is enabled for the Qt Quick Ultralite application. On enabling the text cache, pixel data for each text element is cached, reducing the number of calls to the drawing engine.

Refer to Text caching section which explains in detail about text caching feature and how to enable it.

Using hardware layers

On the MCU platforms that support hardware layers, performance can be enhanced by using multiple framebuffers. Hardware layer capabilities of the MCU can be used to blend these framebuffers together and produce the final image to be displayed. Using hardware layers can help reduce footprint and rendering time.

See Improving performance using hardware layers for more information about hardware layer usage.

QML best practices

The way the QML code is written can have a significant impact on the flash and random access memory footprint of the application. In general, avoiding duplication in the QML code will also reduce the amount of C++ code that is generated and reduce the flash memory footprint. There are also some other things to watch out for to prevent high memory usage and improve performance.

Create reusable components

Instead of repeating the same code patterns in a lot of places in the code, try to encapsulate the pattern in a separate QML file whenever possible.

For example, the code below has a list of labels, each containing some text and an image:

Column {
    spacing: 15
    anchors.centerIn: parent

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 1"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img1.png"
            }
        }
    }

    Rectangle {
        width: 250
        height: 120
        color: "#e7e3e7"

        Row {
            anchors.centerIn: parent
            spacing: 10

            Text {
                anchors.verticalCenter: parent.verticalCenter
                text: "Entry 2"
                color: "#22322f"
                font.pixelSize: 22
            }

            Image {
                anchors.verticalCenter: parent.verticalCenter
                source: "img2.png"
            }
        }
    }
}

This code can be simplified by creating a separate QML file called Label.qml, with the adjustable parts exposed as properties or property aliases as shown here:

import QtQuick 2.15

Rectangle {
    property alias imageSource: imageItem.source
    property alias text: textItem.text

    width: 250
    height: 120
    color: "#e7e3e7"

    Row {
        anchors.centerIn: parent
        spacing: 10

        Text {
            id: textItem
            anchors.verticalCenter: parent.verticalCenter
            color: "#22322f"
            font.pixelSize: 22
        }

        Image {
            id: imageItem
            anchors.verticalCenter: parent.verticalCenter
        }
    }
}

This QML component can then be reused in the original QML code, avoiding duplication:

Column {
    spacing: 15
    anchors.centerIn: parent

    Label {
        text: "Entry 1"
        imageSource: "img1.png"
    }

    Label {
        text: "Entry 2"
        imageSource: "img2.png"
    }
}

Limiting PropertyChanges

A QML file with a lot of states and many properties that are affected by those states through PropertyChanges, leads to generating large and complex C++ code. The size of the generated code will be N x M, where N is the number of states and M is the number of unique properties updated by PropertyChanges in those states.

Here's an example with just two states and two properties, but imagine that there would be a lot more similar states to choose between a variety of views in the same QML component:

Item {
    state: "0"
    states: [
        State {
            name: "0"
            PropertyChanges { target: viewA; visible: true }
        },
        State {
            name: "1"
            PropertyChanges { target: viewB; visible: true }
        }
    ]
    ViewA {
        id: viewA
        visible: false
    }
    ViewB {
        id: viewB
        visible: false
    }
}

This can be optimized by setting the visible property of the views directly based on the state:

Item {
    id: root
    state: "0"
    states: [
        State { name: "0" },
        State { name: "1" }
    ]
    ViewA {
        id: viewA
        visible: root.state == "0"
    }
    ViewB {
        id: viewB
        visible: root.state == "1"
    }
}

Avoid empty container items

The Item type can be useful for grouping other items, making it possible to set their visibility and position in a combined way. Limit the use of container items as they increase the memory usage. For example, the outer Item in the code snippet below is unnecessary:

Item {
    Image {
        anchors.fill: parent
        source: "img.png"
    }
}

It serves no good purpose in this case, as the contained Image item can be used directly instead:

Image {
    anchors.fill: parent
    source: "img.png"
}

Save RAM by loading components dynamically

Your application might contain QML components with multiple complex items that consist of many sub-items, which are visible at different times. You can reduce RAM usage of such items by dynamically loading them, using Loader API.

Unload existing hidden items explicitly before loading new items, to avoid memory peaks. Depending on the applications UI design and memory constraints, ensure that only a select no. of items are loaded at any given time. The following example demonstrates how you can ensure only one page in the SwipeView is loaded into memory at any given time:

SwipeView {
    id: theSwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    function updateLoaderStates() {
        console.log("updating loader states ...")
        if (theSwipe.currentIndex === 0) {
            loader1.source = ""
            loader0.source = "FirstPage.qml"
        } else if (theSwipe.currentIndex === 1) {
            loader0.source = ""
            loader1.source = "SecondPage.qml"
        }
    }

    Component.onCompleted: updateLoaderStates()
    onCurrentIndexChanged: updateLoaderStates()

    Loader {
        id: loader0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader {
        id: loader1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

As a general rule, do not rely on the evaluation order of bindings. In the following example, you have no control on the order in which the items are loaded and unloaded. This may result in temporarily allocating memory for both pages of the application:

SwipeView {
    id: mySwipe
    width: parent.width * 0.5
    height: parent.height * 0.5
    anchors.centerIn: parent
    clip: true

    onCurrentIndexChanged: {
        console.log("index changed ...")
    }

    Loader
    {
        source: "FirstPage.qml"
        active: mySwipe.currentIndex === 0
        onItemChanged: {
            if (item) {
                console.log("loader0 loaded")
            } else {
                console.log("loader0 free")
            }
        }
    }

    Loader
    {
        source: "SecondPage.qml"
        active: mySwipe.currentIndex === 1
        onItemChanged: {
            if (item) {
                console.log("loader1 loaded")
            } else {
                console.log("loader1 free")
            }
        }
    }
}

Reduce the number of visual components

Each visual item typically carries some processing and rendering overhead at runtime. When possible, try reduce the number of visual items required to compose the UI. Below are some examples of how this can be done.

Reduce overlapping images

If two images are always overlapping in the UI, it might be better to combine them into a single image. A lot of overlapping images can reduce performance and will consume more memory. For example, the inner.png in the following code snippet is smaller than the outer.png image:

Image {
    id: outer
    source: "outer.png"
}
Image {
    anchors.centerIn: outer
    source: "inner.png"
}

Instead, combine inner.png and outer.png into a single image:

Image {
    source: "combined.png"
}

If some static text is also overlapping the image, it is worth combining that into the image, instead of using separate Text or StaticText items.

Reduce the Text items

Avoid arranging multiple Text items in a row if they can be combined into a single Text item. For example, the following code snippet shows two Text items arranged in a row:

Row {
    Text {
        text: "Temperature: "
    }
    Text {
        text: root.temperature
    }
}

They can be combined into a single Text item:

Text {
    text: "Temperature: " + root.temperature
}

Available under certain Qt licenses.
Find out more.