Optimising data process for multiple QML plots
-
I'm building a demonstration application for my device, which essentially uses a pre-recorded data stream (read into memory from a .csv file on program start) to populate the GUI. I've primarily used my desktop during the development and testing of this demonstration, which hasn't had any issues with performance whatsoever. I'm now looking to deploy the solution to my embedded device, and I'm having issues with performance. This wasn't unexpected, as my desktop has 24 logical cores and a current generation GPU, but I've done my best to follow best practise when it comes to designing/writing for embedded application.
I have several QML files which are loaded into a main QML file. Some of the QML files make use of only basic shapes, text and colour overlays; these QML files running perfectly on the embedded device. One of the QML files is a series of plots however, which is where I'm having some issues. The file has 12 BarSeries plots and 4 LineSeries plots. Importantly, each BarSeries plot only shows a single value at a time. I.e. just a vertical bar which changes height on each data point change.
The LineSeries plots each have 200 points, but I've tried varying this value from 50 - 600 with seemingly little impact. OpenGL is enabled on all plots.
What I've tried:
- Initially I had a QML timer which would trigger a c++ update function at 60Hz for the LineSeries plots only. The BarSeries plots (i.e. the BarSet within each BarSeries) was updated via a signal-slot connection to c++. I figured this wasn't optimal as the timer was running on the main UI thread, so I removed it.
- I replaced the QML timer with a Connections function in QML, one for each LineSeries plot, which would update the LineSeries data when the corresponding c++ signal was emitted. I dropped the refresh rate of the LineSeries signals considerably, but this didn't seem to make much difference. I was getting well under 1Hz update across the whole GUI (more like one screen update every ~5 seconds), so changing the emit frequency from 60Hz to 2Hz (for example) obviously didn't make any difference.
With the above point in mind, I must be missing something here. The main c++ function (which is on a different thread to the UI/QML thread) executes very quickly, which I've tested on both my desktop and the embedded device. I use counters to artificially execute the function at 60Hz which works nicely. However, if the QML rendering side of the equation is taking longer than the ~16ms (60Hz) between c++ function executions, perhaps the UI is locking up because it's being bombarded with signals at a rate faster than it's capable of rendering.
I've used the QML Profiler in QtCreator but nothing stands out as being the cause. So here are my initial questions:
- How is the QML rendering actually completed? If I have these 12 BarSeries plots, 4 LineSeries plots and another 12 images, each with a color overlay, all being updated by their own c++ signal, are these all aggregated into the one QML render/update?
- Depending on the answer to 1 above, would there be any benefit in aggregating all the plot (both Bar and Line) data into a single container (perhaps a QAbstractTableModel) so that a single signal can be sent from c++?
If there really isn't much to be gained from trying the above, I'd be happy to accept that I'm beyond the capability of my embedded hardware and redesign this particular QML file accordingly, but I wanted to make absolutely certain before doing so.
-
How do you update your plots?
Have you tried updating your data with a QAbstractItemModel and using that with a
VXYModelMapper
? -
Hi Grecko,
I haven't tried QAbstractItemModel, I think that's probably the next step. At the moment I update the plots as follows:
BarSeries:
In QML, each BarSeries plot has the following basic structure.
ChartView { id: chartViewPlot1 //Anchoring, margins, etc. ValueAxis { id: axisX1 color: "red" } ValueAxis { id: axisY1 min: 0 max: 100 //Other parameters } BarSeries { id: barSeries1 useOpenGL: true axisX: axisX1 axisY: axisY1 BarSet { values: myClass.barValues[0] ? [myClass.barValues[0]] : [] } }
In my myClass c++ file, I have a QVariantList barValues which is continuously updated in the 60Hz c++ function. At the end of the 60Hz c++ function I emit the barValuesChanged signal, which causes QML to update the BarSet value. This is the approach I use for all the BarSeries plots.
As for the LineSeries, I've played with a couple of methods. Firstly I had a timer in QML which would call a function in my myClass c++ file.
QML:
Timer { interval: (1 / 60) * 1000 running: true repeat: true onTriggered: { myClass.update1(lineSeries1) } }
myClass.cpp:
void myClass::update1(QtCharts::QAbstractSeries *lineSeries1) { if (lineSeries1) { QtCharts::QXYSeries *xySeries = static_cast<QtCharts::QXYSeries *>(lineSeries1); xySeries->replace(m_lineSeries1); } }
In this approach I'd replace the full set of data on each update. I've also played with appending each new data point and removing the first data point, so that the size of LineSeries remains the same. As I've got 4 LineSeries plots, I initially had 4 myClass::update[1, 2, 3, 4] functions, which I then consolidated to a single function. However, I'm still unsure about this approach as each LineSeries' data is replace()'d individually/sequentially. This is why I'm wondering whether something like a QAbstractItemModel might be better, as I might be able to update/repaint all the plots in fewer clock cycles?
-
Yeah I guess using a model would allow updates to be done incrementally instead of replacing every data points.
Not sure it will solve your problem but it will at least be better.