Scatter data changing optimization.

No longer reset the entire render item array if single item
changes, significantly speeding up this operation.

Task-number: QTRD-2190
Change-Id: Ia3de833b761dc6f24acff581ad79668f51c3e9c5
Reviewed-by: Titta Heikkala <titta.heikkala@digia.com>
Reviewed-by: Mika Salmela <mika.salmela@digia.com>
This commit is contained in:
Miikka Heikkinen 2014-04-30 15:36:19 +03:00
parent 44b410f080
commit 13d1117087
9 changed files with 311 additions and 39 deletions

View File

@ -342,7 +342,7 @@ void Bars3DRenderer::updateRows(const QVector<Bars3DController::ChangeRow> &rows
}
}
void Bars3DRenderer::updateItems(const QVector<Bars3DController::ChangeItem> &points)
void Bars3DRenderer::updateItems(const QVector<Bars3DController::ChangeItem> &items)
{
int minRow = m_axisCacheZ.min();
int maxRow = m_axisCacheZ.max();
@ -352,7 +352,7 @@ void Bars3DRenderer::updateItems(const QVector<Bars3DController::ChangeItem> &po
const QBar3DSeries *prevSeries = 0;
const QBarDataArray *dataArray = 0;
foreach (Bars3DController::ChangeItem item, points) {
foreach (Bars3DController::ChangeItem item, items) {
const int row = item.point.x();
const int col = item.point.y();
if (row < minRow || row > maxRow || col < minCol || col > maxCol)

View File

@ -118,7 +118,7 @@ public:
void updateSeries(const QList<QAbstract3DSeries *> &seriesList);
SeriesRenderCache *createNewCache(QAbstract3DSeries *series);
void updateRows(const QVector<Bars3DController::ChangeRow> &rows);
void updateItems(const QVector<Bars3DController::ChangeItem> &points);
void updateItems(const QVector<Bars3DController::ChangeItem> &items);
void updateScene(Q3DScene *scene);
void render(GLuint defaultFboHandle = 0);

View File

@ -71,6 +71,12 @@ void Scatter3DController::synchDataToRenderer()
Abstract3DController::synchDataToRenderer();
// Notify changes to renderer
if (m_changeTracker.itemChanged) {
m_renderer->updateItems(m_changedItems);
m_changeTracker.itemChanged = false;
m_changedItems.clear();
}
if (m_changeTracker.selectedItemChanged) {
m_renderer->updateSelectedItem(m_selectedItem, m_selectedItemSeries);
m_changeTracker.selectedItemChanged = false;
@ -144,17 +150,35 @@ void Scatter3DController::handleItemsAdded(int startIndex, int count)
void Scatter3DController::handleItemsChanged(int startIndex, int count)
{
Q_UNUSED(startIndex)
Q_UNUSED(count)
QScatter3DSeries *series = static_cast<QScatterDataProxy *>(sender())->series();
if (series->isVisible()) {
adjustAxisRanges();
m_isDataDirty = true;
series->d_ptr->markItemLabelDirty();
int oldChangeCount = m_changedItems.size();
if (!oldChangeCount)
m_changedItems.reserve(count);
for (int i = 0; i < count; i++) {
bool newItem = true;
int candidate = startIndex + i;
for (int j = 0; j < oldChangeCount; j++) {
const ChangeItem &oldChangeItem = m_changedItems.at(j);
if (oldChangeItem.index == candidate && series == oldChangeItem.series) {
newItem = false;
break;
}
}
if (newItem) {
ChangeItem newChangeItem = {series, candidate};
m_changedItems.append(newChangeItem);
if (series == m_selectedItemSeries && m_selectedItem == candidate)
series->d_ptr->markItemLabelDirty();
}
}
if (count) {
m_changeTracker.itemChanged = true;
if (series->isVisible())
adjustAxisRanges();
emitNeedRender();
}
if (!m_changedSeriesList.contains(series))
m_changedSeriesList.append(series);
emitNeedRender();
}
void Scatter3DController::handleItemsRemoved(int startIndex, int count)

View File

@ -40,9 +40,11 @@ class QScatter3DSeries;
struct Scatter3DChangeBitField {
bool selectedItemChanged : 1;
bool itemChanged : 1;
Scatter3DChangeBitField() :
selectedItemChanged(true)
selectedItemChanged(true),
itemChanged(false)
{
}
};
@ -51,8 +53,14 @@ class QT_DATAVISUALIZATION_EXPORT Scatter3DController : public Abstract3DControl
{
Q_OBJECT
public:
struct ChangeItem {
QScatter3DSeries *series;
int index;
};
private:
Scatter3DChangeBitField m_changeTracker;
QVector<ChangeItem> m_changedItems;
// Rendering
Scatter3DRenderer *m_renderer;

View File

@ -165,12 +165,6 @@ void Scatter3DRenderer::initializeOpenGL()
void Scatter3DRenderer::updateData()
{
calculateSceneScalingFactors();
float minX = float(m_axisCacheX.min());
float maxX = float(m_axisCacheX.max());
float minY = float(m_axisCacheY.min());
float maxY = float(m_axisCacheY.max());
float minZ = float(m_axisCacheZ.min());
float maxZ = float(m_axisCacheZ.max());
int totalDataSize = 0;
foreach (SeriesRenderCache *baseCache, m_renderCacheList) {
@ -185,23 +179,8 @@ void Scatter3DRenderer::updateData()
if (dataSize != renderArray.size())
renderArray.resize(dataSize);
for (int i = 0; i < dataSize; i++) {
QVector3D dotPos = dataArray.at(i).position();
ScatterRenderItem &renderItem = renderArray[i];
if ((dotPos.x() >= minX && dotPos.x() <= maxX )
&& (dotPos.y() >= minY && dotPos.y() <= maxY)
&& (dotPos.z() >= minZ && dotPos.z() <= maxZ)) {
renderItem.setPosition(dotPos);
renderItem.setVisible(true);
if (!dataArray.at(i).rotation().isIdentity())
renderItem.setRotation(dataArray.at(i).rotation().normalized());
else
renderItem.setRotation(identityQuaternion);
calculateTranslation(renderItem);
} else {
renderItem.setVisible(false);
}
}
for (int i = 0; i < dataSize; i++)
updateRenderItem(dataArray.at(i), renderArray[i]);
cache->setDataDirty(false);
}
}
@ -272,6 +251,30 @@ SeriesRenderCache *Scatter3DRenderer::createNewCache(QAbstract3DSeries *series)
return new ScatterSeriesRenderCache(series, this);
}
void Scatter3DRenderer::updateItems(const QVector<Scatter3DController::ChangeItem> &items)
{
ScatterSeriesRenderCache *cache = 0;
const QScatter3DSeries *prevSeries = 0;
const QScatterDataArray *dataArray = 0;
foreach (Scatter3DController::ChangeItem item, items) {
QScatter3DSeries *currentSeries = item.series;
if (currentSeries != prevSeries) {
cache = static_cast<ScatterSeriesRenderCache *>(m_renderCacheList.value(currentSeries));
prevSeries = currentSeries;
dataArray = item.series->dataProxy()->array();
// Invisible series render caches are not updated, but instead just marked dirty, so that
// they can be completely recalculated when they are turned visible.
if (!cache->isVisible() && !cache->dataDirty())
cache->setDataDirty(true);
}
if (cache->isVisible()) {
const int index = item.index;
updateRenderItem(dataArray->at(index), cache->renderArray()[index]);
}
}
}
void Scatter3DRenderer::updateScene(Q3DScene *scene)
{
scene->activeCamera()->d_ptr->setMinYRotation(-90.0f);
@ -1802,6 +1805,24 @@ void Scatter3DRenderer::selectionColorToSeriesAndIndex(const QVector4D &color,
series = 0;
}
void Scatter3DRenderer::updateRenderItem(const QScatterDataItem &dataItem, ScatterRenderItem &renderItem)
{
QVector3D dotPos = dataItem.position();
if ((dotPos.x() >= m_axisCacheX.min() && dotPos.x() <= m_axisCacheX.max() )
&& (dotPos.y() >= m_axisCacheY.min() && dotPos.y() <= m_axisCacheY.max())
&& (dotPos.z() >= m_axisCacheZ.min() && dotPos.z() <= m_axisCacheZ.max())) {
renderItem.setPosition(dotPos);
renderItem.setVisible(true);
if (!dataItem.rotation().isIdentity())
renderItem.setRotation(dataItem.rotation().normalized());
else
renderItem.setRotation(identityQuaternion);
calculateTranslation(renderItem);
} else {
renderItem.setVisible(false);
}
}
QVector3D Scatter3DRenderer::convertPositionToTranslation(const QVector3D &position) {
float xTrans = m_axisCacheX.positionAt(position.x());
float yTrans = m_axisCacheY.positionAt(position.y());

View File

@ -105,6 +105,7 @@ public:
void updateData();
void updateSeries(const QList<QAbstract3DSeries *> &seriesList);
SeriesRenderCache *createNewCache(QAbstract3DSeries *series);
void updateItems(const QVector<Scatter3DController::ChangeItem> &items);
void updateScene(Q3DScene *scene);
QVector3D convertPositionToTranslation(const QVector3D &position);
@ -154,6 +155,7 @@ public slots:
private:
void selectionColorToSeriesAndIndex(const QVector4D &color, int &index,
QAbstract3DSeries *&series);
inline void updateRenderItem(const QScatterDataItem &dataItem, ScatterRenderItem &renderItem);
};
QT_END_NAMESPACE_DATAVISUALIZATION

View File

@ -123,6 +123,9 @@ int main(int argc, char **argv)
QPushButton *massiveDataTestButton = new QPushButton(widget);
massiveDataTestButton->setText(QStringLiteral("Massive data test"));
QPushButton *testItemChangesButton = new QPushButton(widget);
testItemChangesButton->setText(QStringLiteral("Test Item changing"));
QLinearGradient grBtoY(0, 0, 100, 0);
grBtoY.setColorAt(1.0, Qt::black);
grBtoY.setColorAt(0.67, Qt::blue);
@ -137,6 +140,12 @@ int main(int argc, char **argv)
gradientBtoYPB->setIcon(QIcon(pm));
gradientBtoYPB->setIconSize(QSize(100, 24));
QLabel *fpsLabel = new QLabel(QStringLiteral(""));
QCheckBox *fpsCheckBox = new QCheckBox(widget);
fpsCheckBox->setText(QStringLiteral("Measure Fps"));
fpsCheckBox->setChecked(false);
QCheckBox *backgroundCheckBox = new QCheckBox(widget);
backgroundCheckBox->setText(QStringLiteral("Show background"));
backgroundCheckBox->setChecked(true);
@ -232,9 +241,12 @@ int main(int argc, char **argv)
vLayout->addWidget(toggleSeriesVisibilityButton, 0, Qt::AlignTop);
vLayout->addWidget(changeSeriesNameButton, 0, Qt::AlignTop);
vLayout->addWidget(startTimerButton, 0, Qt::AlignTop);
vLayout->addWidget(massiveDataTestButton, 1, Qt::AlignTop);
vLayout->addWidget(massiveDataTestButton, 0, Qt::AlignTop);
vLayout->addWidget(testItemChangesButton, 1, Qt::AlignTop);
vLayout2->addWidget(gradientBtoYPB, 0, Qt::AlignTop);
vLayout2->addWidget(fpsLabel, 0, Qt::AlignTop);
vLayout2->addWidget(fpsCheckBox, 0, Qt::AlignTop);
vLayout2->addWidget(backgroundCheckBox);
vLayout2->addWidget(gridCheckBox);
vLayout2->addWidget(new QLabel(QStringLiteral("Adjust shadow quality")));
@ -302,6 +314,8 @@ int main(int argc, char **argv)
&ScatterDataModifier::startStopTimer);
QObject::connect(massiveDataTestButton, &QPushButton::clicked, modifier,
&ScatterDataModifier::massiveDataTest);
QObject::connect(testItemChangesButton, &QPushButton::clicked, modifier,
&ScatterDataModifier::testItemChanges);
QObject::connect(gradientBtoYPB, &QPushButton::clicked, modifier,
&ScatterDataModifier::setGradient);
QObject::connect(themeButton, &QPushButton::clicked, modifier,
@ -316,6 +330,8 @@ int main(int argc, char **argv)
QObject::connect(fontList, &QFontComboBox::currentFontChanged, modifier,
&ScatterDataModifier::changeFont);
QObject::connect(fpsCheckBox, &QCheckBox::stateChanged, modifier,
&ScatterDataModifier::setFpsMeasurement);
QObject::connect(backgroundCheckBox, &QCheckBox::stateChanged, modifier,
&ScatterDataModifier::setBackgroundEnabled);
QObject::connect(gridCheckBox, &QCheckBox::stateChanged, modifier,
@ -335,6 +351,8 @@ int main(int argc, char **argv)
&ScatterDataModifier::setMaxZ);
modifier->setFpsLabel(fpsLabel);
modifier->start();
return app.exec();

View File

@ -134,7 +134,6 @@ void ScatterDataModifier::massiveDataTest()
xAxis->setSegmentCount(1);
yAxis->setSegmentCount(1);
zAxis->setSegmentCount(1);
m_chart->setMeasureFps(true);
m_chart->setAxisX(xAxis);
m_chart->setAxisY(yAxis);
m_chart->setAxisZ(zAxis);
@ -231,6 +230,187 @@ void ScatterDataModifier::massiveTestAppendAndScroll()
m_chart->axisZ()->setRange(min, max);
}
void ScatterDataModifier::setFpsMeasurement(bool enable)
{
m_chart->setMeasureFps(enable);
}
void ScatterDataModifier::testItemChanges()
{
static int counter = 0;
const int rowCount = 12;
const int colCount = 10;
static QScatter3DSeries *series0 = 0;
static QScatter3DSeries *series1 = 0;
static QScatter3DSeries *series2 = 0;
switch (counter) {
case 0: {
qDebug() << __FUNCTION__ << counter << "Setup test";
foreach (QScatter3DSeries *series, m_chart->seriesList())
m_chart->removeSeries(series);
foreach (QValue3DAxis *axis, m_chart->axes())
m_chart->releaseAxis(axis);
delete series0;
delete series1;
delete series2;
series0 = new QScatter3DSeries;
series1 = new QScatter3DSeries;
series2 = new QScatter3DSeries;
populateFlatSeries(series0, rowCount, colCount, 10.0f);
populateFlatSeries(series1, rowCount, colCount, 30.0f);
populateFlatSeries(series2, rowCount, colCount, 50.0f);
m_chart->axisX()->setRange(3.0f, 6.0f);
m_chart->axisY()->setRange(0.0f, 100.0f);
m_chart->axisZ()->setRange(4.0f, 8.0f);
m_chart->addSeries(series0);
m_chart->addSeries(series1);
m_chart->addSeries(series2);
}
break;
case 1: {
qDebug() << __FUNCTION__ << counter << "Change single item, unselected";
int itemIndex = 3 * colCount + 5;
QScatterDataItem item = *series0->dataProxy()->itemAt(itemIndex);
item.setY(75.0f);
series0->dataProxy()->setItem(itemIndex, item);
}
break;
case 2: {
qDebug() << __FUNCTION__ << counter << "Change single item, selected";
int itemIndex = 4 * colCount + 4;
series1->setSelectedItem(itemIndex);
QScatterDataItem item = *series1->dataProxy()->itemAt(itemIndex);
item.setY(75.0f);
series1->dataProxy()->setItem(itemIndex, item);
}
break;
case 3: {
qDebug() << __FUNCTION__ << counter << "Change item outside visible area";
int itemIndex = 2;
QScatterDataItem item = *series1->dataProxy()->itemAt(itemIndex);
item.setY(75.0f);
series1->dataProxy()->setItem(itemIndex, item);
}
break;
case 4: {
qDebug() << __FUNCTION__ << counter << "Change single item from two series, unselected";
int itemIndex = 4 * colCount + 6;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex);
QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex);
item0.setY(65.0f);
item1.setY(85.0f);
series0->dataProxy()->setItem(itemIndex, item0);
series1->dataProxy()->setItem(itemIndex, item1);
}
break;
case 5: {
qDebug() << __FUNCTION__ << counter << "Change single item from two series, one selected";
int itemIndex0 = 5 * colCount + 5;
int itemIndex1 = 4 * colCount + 4;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
item0.setY(65.0f);
item1.setY(85.0f);
series0->dataProxy()->setItem(itemIndex0, item0);
series1->dataProxy()->setItem(itemIndex1, item1);
}
break;
case 6: {
qDebug() << __FUNCTION__ << counter << "Change single item from two series, one outside range";
int itemIndex0 = 6 * colCount + 6;
int itemIndex1 = 9 * colCount + 2;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
item0.setY(65.0f);
item1.setY(85.0f);
series0->dataProxy()->setItem(itemIndex0, item0);
series1->dataProxy()->setItem(itemIndex1, item1);
}
break;
case 7: {
qDebug() << __FUNCTION__ << counter << "Change single item from two series, both outside range";
int itemIndex0 = 1 * colCount + 3;
int itemIndex1 = 9 * colCount + 2;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
item0.setY(65.0f);
item1.setY(85.0f);
series0->dataProxy()->setItem(itemIndex0, item0);
series1->dataProxy()->setItem(itemIndex1, item1);
}
break;
case 8: {
qDebug() << __FUNCTION__ << counter << "Change item to same value as previously";
int itemIndex0 = 5 * colCount + 7;
int itemIndex1 = 4 * colCount + 7;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex1);
series0->dataProxy()->setItem(itemIndex0, item0);
series1->dataProxy()->setItem(itemIndex1, item1);
}
break;
case 9: {
qDebug() << __FUNCTION__ << counter << "Change 3 items on each series";
int itemIndex0 = 5 * colCount + 6;
int itemIndex1 = 4 * colCount + 6;
QScatterDataItem item00 = *series0->dataProxy()->itemAt(itemIndex0);
QScatterDataItem item01 = *series0->dataProxy()->itemAt(itemIndex0 + 1);
QScatterDataItem item02 = *series0->dataProxy()->itemAt(itemIndex0 + 2);
QScatterDataItem item10 = *series1->dataProxy()->itemAt(itemIndex1);
QScatterDataItem item11 = *series1->dataProxy()->itemAt(itemIndex1 + 1);
QScatterDataItem item12 = *series1->dataProxy()->itemAt(itemIndex1 + 2);
item00.setY(65.0f);
item01.setY(70.0f);
item02.setY(75.0f);
item10.setY(80.0f);
item11.setY(85.0f);
item12.setY(90.0f);
series0->dataProxy()->setItem(itemIndex0, item00);
series0->dataProxy()->setItem(itemIndex0 + 1, item01);
series0->dataProxy()->setItem(itemIndex0 + 2, item02);
series1->dataProxy()->setItem(itemIndex1, item10);
series1->dataProxy()->setItem(itemIndex1 + 1, item11);
series1->dataProxy()->setItem(itemIndex1 + 2, item12);
}
break;
case 10: {
qDebug() << __FUNCTION__ << counter << "Level the field single item at a time";
QScatterDataItem item;
for (int i = 0; i < rowCount; i++) {
for (int j = 0; j < colCount; j++) {
int itemIndex = i * colCount + j;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex);
QScatterDataItem item1 = *series1->dataProxy()->itemAt(itemIndex);
QScatterDataItem item2 = *series2->dataProxy()->itemAt(itemIndex);
item0.setY(10.0f);
item1.setY(15.0f);
item2.setY(20.0f);
series0->dataProxy()->setItem(itemIndex, item0);
series1->dataProxy()->setItem(itemIndex, item1);
series2->dataProxy()->setItem(itemIndex, item2);
}
}
}
break;
case 11: {
qDebug() << __FUNCTION__ << counter << "Change same items multiple times";
int itemIndex0 = 6 * colCount + 6;
QScatterDataItem item0 = *series0->dataProxy()->itemAt(itemIndex0);
item0.setY(90.0f);
series0->dataProxy()->setItem(itemIndex0, item0);
series0->dataProxy()->setItem(itemIndex0, item0);
series0->dataProxy()->setItem(itemIndex0, item0);
series0->dataProxy()->setItem(itemIndex0, item0);
}
break;
default:
qDebug() << __FUNCTION__ << "Resetting test";
counter = -1;
}
counter++;
}
void ScatterDataModifier::addData()
{
// Add labels
@ -673,7 +853,8 @@ void ScatterDataModifier::handleAxisZChanged(QValue3DAxis *axis)
void ScatterDataModifier::handleFpsChange(qreal fps)
{
qDebug() << "FPS:" << fps;
static const QString fpsPrefix(QStringLiteral("FPS: "));
m_fpsLabel->setText(fpsPrefix + QString::number(qRound(fps)));
}
void ScatterDataModifier::changeShadowQuality(int quality)
@ -757,3 +938,15 @@ QScatter3DSeries *ScatterDataModifier::createAndAddSeries()
return series;
}
void ScatterDataModifier::populateFlatSeries(QScatter3DSeries *series, int rows, int columns,
float value)
{
QScatterDataArray *dataArray = new QScatterDataArray;
dataArray->resize(rows * columns);
for (int i = 0; i < rows; i++) {
for (int j = 0; j < columns; j++)
(*dataArray)[i * columns + j].setPosition(QVector3D(float(i), value, float(j)));
}
series->dataProxy()->resetArray(dataArray);
}

View File

@ -25,6 +25,7 @@
#include <QFont>
#include <QDebug>
#include <QTimer>
#include <QLabel>
using namespace QtDataVisualization;
@ -55,6 +56,9 @@ public:
void massiveDataTest();
void massiveTestScroll();
void massiveTestAppendAndScroll();
void setFpsMeasurement(bool enable);
void setFpsLabel(QLabel *fpsLabel) { m_fpsLabel = fpsLabel; }
void testItemChanges();
public slots:
void changeShadowQuality(int quality);
@ -91,6 +95,7 @@ signals:
private:
QVector3D randVector();
QScatter3DSeries *createAndAddSeries();
void populateFlatSeries(QScatter3DSeries *series, int rows, int columns, float value);
Q3DScatter *m_chart;
int m_fontSize;
@ -99,6 +104,7 @@ private:
int m_selectedItem;
QScatter3DSeries *m_targetSeries;
QScatterDataArray m_massiveTestCacheArray;
QLabel *m_fpsLabel;
};
#endif