Qt Weekly #7: Qt Data Visualization tips & tricks

A while ago we released 1.0 version of Qt Enterprise Data Visualization add-on and on this blog we show how to measure frames per second (FPS) on Qt Data Visualization applications. We also give few tips how to avoid pitfalls in the performance.

The Qt Data Visualization has a built-in FPS counter that can be enabled by setting the precompile directive. Normally the visualization engine renders only on demand, i.e. when there are changes to be displayed, but to get the FPS reading we need to trigger the engine to render constantly. To enable the FPS counter remove the comments from the following line in the Qt Data Visualization source code package (/src/datavisualization/engine/abstract3drenderer_p.h), recompile and install:

//#define DISPLAY_RENDER_SPEED

After this modification the FPS will be written to the debug output. A nice application to study the FPS on your device is for instance qmloscilloscope. With that application you can see for instance how sample count and constant data update affects the performance.

Meshes and performance

On performance critical cases, and especially on embedded devices, it is better to utilize simpler mesh types on the scatter and bar graphs. For instance on the scatter graph, favor the cube mesh type or you can even go to the point type, which is basically GL_POINT. For instance on iPad (4th Generation) with 900 items, the FPS doubled when changed the sphere mesh type to the point type. On the bar graph it’s better to use the basic bar mesh.

On fill rate limited devices, a good trick is to make the item size smaller on the scatter graph. For instance on iPad the FPS rate increased around 25% when the item size was halved. On all graph types 10% increase on the FPS can be gained if the background is disabled. The item size and mesh types are series specific parameters and can be changed as shown in the QML code sample below.

// Set the basic mesh for bar series
Bar3DSeries {
    mesh: Abstract3DSeries.MeshBar
    ...

// Set the item size and cube mesh for the scatter series Scatter3DSeries { itemSize: 0.1 mesh: Abstract3DSeries.MeshCube ...

Even though the shadows are impressive and they do emphasize the 3D effect, they come with a cost. The best shadow quality nearly halves the FPS. If the FPS rate is inadequate, try first with the lower shadow quality and if still not good enough, it’s better to leave the shadows off.

Initializing data arrays 

The data arrays on the surface and bar graphs are generalizations of QList. The row items on the surface and bar graphs and the data array on scatter graph are generalizations of QVector. QVector provides a tempting operator<< and append methods to add items to the vector. However using these causes the QVector resize itself each time you call the methods. A better way is to first allocate the arrays and then fill them. Let’s take a look at a bad example.

// Bad code
QSurfaceDataArray *dataArray = new QSurfaceDataArray;
for (int i = 0 ; i < 1000 ; i++) {
    QSurfaceDataRow *newRow = new QSurfaceDataRow();
    for (int j = 0; j < 1000; j++)
        *newRow << QSurfaceDataItem(QVector3D(xValue, yValue, zValue));

*dataArray << newRow; }

In this example we append a new QSurfaceDataItem at the end of QSurfaceDataRow (newRow) using the operator<<. Initializing 1000 x 1000 data array in this way takes on a desktop (Intel Core i5 CPU M520 2.40GHz) around 450 ms. Let’s compare this with a better approach shown below.

// Good code
QSurfaceDataArray *dataArray = new QSurfaceDataArray;
dataArray->reserve(1000);
for (int i = 0 ; i < 1000 ; i++) {
    QSurfaceDataRow *newRow = new QSurfaceDataRow(1000);
    for (int j = 0; j < 1000; j++)
        (*newRow)[j].setPosition(QVector3D(xValue, yValue, zValue));

*dataArray << newRow; }

In this example the data array is pre-allocated and the size for each new QSurfaceDataRow is defined at the time of creation. Instead of using the append method; the values for the QSurfaceDataItem are set using the reference ([j]) and setPosition method. This approach took around 120 ms to initialize the same 1000 x 1000 data array, which makes it nearly four times faster than the bad example..

For the other graph types, take a look at the bars and scatter examples on how the data arrays are initialized.


Comments