summaryrefslogtreecommitdiffstats
path: root/src/charts/xychart
diff options
context:
space:
mode:
authorMiikka Heikkinen <miikka.heikkinen@theqtcompany.com>2015-09-15 17:39:54 +0300
committerMiikka Heikkinen <miikka.heikkinen@theqtcompany.com>2015-09-25 06:44:35 +0000
commit79a856530b6986ca6d6d7485b2e6cec810c3b7fe (patch)
tree6f3f8dfe76e64da9ec48909fef1d7cccb87c3fc0 /src/charts/xychart
parent3a89e3fee61a52927f836f3b3de4c922c6b954e4 (diff)
Accelerating lineseries with OpenGL
Added support for QAbstractSeries::useOpenGL property. When true, the series in question is drawn on a separate offscreen buffer using OpenGL and then superimposed on the chart. Currently this property is only supported for line and scatter series. Change-Id: I174fec541f9f3c23464270c1fe08f824af16a0fb Reviewed-by: Titta Heikkala <titta.heikkala@theqtcompany.com> Reviewed-by: Tomi Korpipää <tomi.korpipaa@theqtcompany.com>
Diffstat (limited to 'src/charts/xychart')
-rw-r--r--src/charts/xychart/glxyseriesdata.cpp147
-rw-r--r--src/charts/xychart/glxyseriesdata_p.h91
-rw-r--r--src/charts/xychart/qxyseries.cpp25
-rw-r--r--src/charts/xychart/qxyseries.h2
-rw-r--r--src/charts/xychart/xychart.cpp119
-rw-r--r--src/charts/xychart/xychart.pri6
-rw-r--r--src/charts/xychart/xychart_p.h1
7 files changed, 339 insertions, 52 deletions
diff --git a/src/charts/xychart/glxyseriesdata.cpp b/src/charts/xychart/glxyseriesdata.cpp
new file mode 100644
index 00000000..2dafafd2
--- /dev/null
+++ b/src/charts/xychart/glxyseriesdata.cpp
@@ -0,0 +1,147 @@
+/****************************************************************************
+ **
+ ** Copyright (C) 2015 The Qt Company Ltd
+ ** All rights reserved.
+ ** For any questions to The Qt Company, please use contact form at http://qt.io
+ **
+ ** This file is part of the Qt Charts module.
+ **
+ ** Licensees holding valid commercial license for Qt may use this file in
+ ** accordance with the Qt License Agreement provided with the Software
+ ** or, alternatively, in accordance with the terms contained in a written
+ ** agreement between you and The Qt Company.
+ **
+ ** If you have questions regarding the use of this file, please use
+ ** contact form at http://qt.io
+ **
+ ****************************************************************************/
+
+#include "private/glxyseriesdata_p.h"
+#include "private/abstractdomain_p.h"
+#include <QtCharts/QScatterSeries>
+
+QT_CHARTS_BEGIN_NAMESPACE
+
+GLXYSeriesDataManager::GLXYSeriesDataManager(QObject *parent)
+ : QObject(parent),
+ m_mapDirty(false)
+{
+}
+
+GLXYSeriesDataManager::~GLXYSeriesDataManager()
+{
+ cleanup();
+}
+
+void GLXYSeriesDataManager::setPoints(QXYSeries *series, const AbstractDomain *domain)
+{
+ GLXYSeriesData *data = m_seriesDataMap.value(series);
+ if (!data) {
+ data = new GLXYSeriesData;
+ data->type = series->type();
+ QColor sc;
+ if (data->type == QAbstractSeries::SeriesTypeScatter) {
+ QScatterSeries *scatter = static_cast<QScatterSeries *>(series);
+ data->width = float(scatter->markerSize());
+ sc = scatter->color(); // Scatter overwrites color property
+ } else {
+ data->width = float(series->pen().widthF());
+ sc = series->color();
+ }
+ data->color = QVector3D(float(sc.redF()), float(sc.greenF()), float(sc.blueF()));
+ connect(series, &QXYSeries::penChanged, this,
+ &GLXYSeriesDataManager::handleSeriesPenChange);
+ connect(series, &QXYSeries::useOpenGLChanged, this,
+ &GLXYSeriesDataManager::handleSeriesOpenGLChange);
+ m_seriesDataMap.insert(series, data);
+ m_mapDirty = true;
+ }
+ QVector<float> &array = data->array;
+
+ bool logAxis = false;
+ foreach (QAbstractAxis* axis, series->attachedAxes()) {
+ if (axis->type() == QAbstractAxis::AxisTypeLogValue) {
+ logAxis = true;
+ break;
+ }
+ }
+
+ int count = series->count();
+ int index = 0;
+ array.resize(count * 2);
+ if (logAxis) {
+ // Use domain to resolve geometry points. Not as fast as shaders, but simpler that way
+ QVector<QPointF> geometryPoints = domain->calculateGeometryPoints(series->pointsVector());
+ const float height = domain->size().height();
+ if (geometryPoints.size()) {
+ for (int i = 0; i < count; i++) {
+ const QPointF &point = geometryPoints.at(i);
+ array[index++] = float(point.x());
+ array[index++] = float(height - point.y());
+ }
+ } else {
+ // If there are invalid log values, geometry points generation fails
+ for (int i = 0; i < count; i++) {
+ array[index++] = 0.0f;
+ array[index++] = 0.0f;
+ }
+ }
+ data->min = QVector2D(0, 0);
+ data->delta = QVector2D(domain->size().width() / 2.0f, domain->size().height() / 2.0f);
+ } else {
+ // Regular value axes, so we can do the math easily on shaders.
+ QVector<QPointF> seriesPoints = series->pointsVector();
+ for (int i = 0; i < count; i++) {
+ const QPointF &point = seriesPoints.at(i);
+ array[index++] = float(point.x());
+ array[index++] = float(point.y());
+ }
+ data->min = QVector2D(domain->minX(), domain->minY());
+ data->delta = QVector2D((domain->maxX() - domain->minX()) / 2.0f,
+ (domain->maxY() - domain->minY()) / 2.0f);
+ }
+ data->dirty = true;
+}
+
+void GLXYSeriesDataManager::removeSeries(const QXYSeries *series)
+{
+ GLXYSeriesData *data = m_seriesDataMap.take(series);
+ if (data) {
+ disconnect(series, 0, this, 0);
+ delete data;
+ emit seriesRemoved(series);
+ m_mapDirty = true;
+ }
+}
+
+void GLXYSeriesDataManager::cleanup()
+{
+ foreach (GLXYSeriesData *data, m_seriesDataMap.values())
+ delete data;
+ m_seriesDataMap.clear();
+ m_mapDirty = true;
+ // Signal all series removal by using zero as parameter
+ emit seriesRemoved(0);
+}
+
+void GLXYSeriesDataManager::handleSeriesPenChange()
+{
+ QXYSeries *series = qobject_cast<QXYSeries *>(sender());
+ if (series) {
+ GLXYSeriesData *data = m_seriesDataMap.value(series);
+ if (data) {
+ QColor sc = series->color();
+ data->color = QVector3D(float(sc.redF()), float(sc.greenF()), float(sc.blueF()));
+ data->width = float(series->pen().widthF());
+ }
+ }
+}
+
+void GLXYSeriesDataManager::handleSeriesOpenGLChange()
+{
+ QXYSeries *series = qobject_cast<QXYSeries *>(sender());
+ if (!series->useOpenGL())
+ removeSeries(series);
+}
+
+QT_CHARTS_END_NAMESPACE
diff --git a/src/charts/xychart/glxyseriesdata_p.h b/src/charts/xychart/glxyseriesdata_p.h
new file mode 100644
index 00000000..0f59effe
--- /dev/null
+++ b/src/charts/xychart/glxyseriesdata_p.h
@@ -0,0 +1,91 @@
+/****************************************************************************
+**
+** Copyright (C) 2015 The Qt Company Ltd
+** All rights reserved.
+** For any questions to The Qt Company, please use contact form at http://qt.io
+**
+** This file is part of the Qt Charts module.
+**
+** Licensees holding valid commercial license for Qt may use this file in
+** accordance with the Qt License Agreement provided with the Software
+** or, alternatively, in accordance with the terms contained in a written
+** agreement between you and The Qt Company.
+**
+** If you have questions regarding the use of this file, please use
+** contact form at http://qt.io
+**
+****************************************************************************/
+
+// W A R N I N G
+// -------------
+//
+// This file is not part of the Qt Chart API. It exists purely as an
+// implementation detail. This header file may change from version to
+// version without notice, or even be removed.
+//
+// We mean it.
+
+#ifndef GLXYSERIESDATA_H
+#define GLXYSERIESDATA_H
+
+#include <QtCore/QMap>
+#include <QtCharts/QAbstractSeries>
+#include <QtCharts/QXYSeries>
+#include <QtGui/QVector3D>
+#include <QtGui/QVector2D>
+
+QT_CHARTS_BEGIN_NAMESPACE
+
+class AbstractDomain;
+
+struct GLXYSeriesData {
+ QVector<float> array;
+ bool dirty;
+ QVector3D color;
+ float width;
+ QAbstractSeries::SeriesType type;
+ QVector2D min;
+ QVector2D delta;
+};
+
+typedef QMap<const QXYSeries *, GLXYSeriesData *> GLXYDataMap;
+typedef QMapIterator<const QXYSeries *, GLXYSeriesData *> GLXYDataMapIterator;
+
+class GLXYSeriesDataManager : public QObject
+{
+ Q_OBJECT
+
+public:
+ GLXYSeriesDataManager(QObject *parent = 0);
+ ~GLXYSeriesDataManager();
+
+ void setPoints(QXYSeries *series, const AbstractDomain *domain);
+
+ void removeSeries(const QXYSeries *series);
+
+ GLXYDataMap &dataMap() { return m_seriesDataMap; }
+
+ // These functions are needed by qml side, so they must be inline
+ bool mapDirty() const { return m_mapDirty; }
+ void clearAllDirty() {
+ m_mapDirty = false;
+ foreach (GLXYSeriesData *data, m_seriesDataMap.values())
+ data->dirty = false;
+ }
+
+public Q_SLOTS:
+ void cleanup();
+ void handleSeriesPenChange();
+ void handleSeriesOpenGLChange();
+
+Q_SIGNALS:
+ void seriesRemoved(const QXYSeries *series);
+
+private:
+ GLXYDataMap m_seriesDataMap;
+ bool m_mapDirty;
+};
+
+QT_CHARTS_END_NAMESPACE
+
+#endif
diff --git a/src/charts/xychart/qxyseries.cpp b/src/charts/xychart/qxyseries.cpp
index a88c5f85..681781ec 100644
--- a/src/charts/xychart/qxyseries.cpp
+++ b/src/charts/xychart/qxyseries.cpp
@@ -238,7 +238,7 @@ QT_CHARTS_BEGIN_NAMESPACE
\sa pointLabelsVisible
*/
/*!
- \fn void QXYSeries::pointLabelsClippintChanged(bool clipping)
+ \fn void QXYSeries::pointLabelsClippingChanged(bool clipping)
The clipping of the data point labels is changed to \a clipping.
*/
/*!
@@ -398,6 +398,11 @@ QT_CHARTS_BEGIN_NAMESPACE
*/
/*!
+ \fn void QXYSeries::penChanged(const QPen &pen)
+ \brief Signal is emitted when the line pen has changed to \a pen.
+*/
+
+/*!
\fn void QXYSeriesPrivate::updated()
\brief \internal
*/
@@ -539,7 +544,7 @@ void QXYSeries::replace(int index, const QPointF &newPoint)
\note This is much faster than replacing data points one by one,
or first clearing all data, and then appending the new data. Emits QXYSeries::pointsReplaced()
when the points have been replaced. However, note that using the overload that takes
- \c{QVector<QPointF>} as parameter is slightly faster than using this overload.
+ \c{QVector<QPointF>} as parameter is faster than using this overload.
\sa pointsReplaced()
*/
void QXYSeries::replace(QList<QPointF> points)
@@ -634,7 +639,8 @@ void QXYSeries::clear()
}
/*!
- Returns list of points in the series.
+ Returns the points in the series as a list.
+ Use QXYSeries::pointsVector() for better performance.
*/
QList<QPointF> QXYSeries::points() const
{
@@ -643,6 +649,16 @@ QList<QPointF> QXYSeries::points() const
}
/*!
+ Returns the points in the series as a vector.
+ This is more efficient that calling QXYSeries::points();
+*/
+QVector<QPointF> QXYSeries::pointsVector() const
+{
+ Q_D(const QXYSeries);
+ return d->m_points;
+}
+
+/*!
Returns point at \a index in internal points vector.
*/
const QPointF &QXYSeries::at(int index) const
@@ -675,6 +691,7 @@ void QXYSeries::setPen(const QPen &pen)
emit d->updated();
if (emitColorChanged)
emit colorChanged(pen.color());
+ emit penChanged(pen);
}
}
@@ -864,7 +881,7 @@ void QXYSeriesPrivate::initializeDomain()
Q_Q(QXYSeries);
- const QList<QPointF>& points = q->points();
+ const QVector<QPointF> &points = q->pointsVector();
if (!points.isEmpty()) {
minX = points[0].x();
diff --git a/src/charts/xychart/qxyseries.h b/src/charts/xychart/qxyseries.h
index 6a211fbf..25f52ead 100644
--- a/src/charts/xychart/qxyseries.h
+++ b/src/charts/xychart/qxyseries.h
@@ -65,6 +65,7 @@ public:
int count() const;
QList<QPointF> points() const;
+ QVector<QPointF> pointsVector() const;
const QPointF &at(int index) const;
QXYSeries &operator << (const QPointF &point);
@@ -117,6 +118,7 @@ Q_SIGNALS:
void pointLabelsColorChanged(const QColor &color);
void pointLabelsClippingChanged(bool clipping);
void pointsRemoved(int index, int count);
+ void penChanged(const QPen &pen);
private:
Q_DECLARE_PRIVATE(QXYSeries)
diff --git a/src/charts/xychart/xychart.cpp b/src/charts/xychart/xychart.cpp
index 16dd707f..e7d3fddc 100644
--- a/src/charts/xychart/xychart.cpp
+++ b/src/charts/xychart/xychart.cpp
@@ -21,6 +21,8 @@
#include <private/qxyseries_p.h>
#include <private/chartpresenter_p.h>
#include <private/abstractdomain_p.h>
+#include <private/chartdataset_p.h>
+#include <private/glxyseriesdata_p.h>
#include <QtCharts/QXYModelMapper>
#include <private/qabstractaxis_p.h>
#include <QtGui/QPainter>
@@ -106,6 +108,13 @@ void XYChart::updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoin
}
}
+void XYChart::updateGlChart()
+{
+ presenter()->ensureGLWidget();
+ dataSet()->glXYSeriesDataManager()->setPoints(m_series, domain());
+ updateGeometry();
+}
+
//handlers
void XYChart::handlePointAdded(int index)
@@ -113,20 +122,23 @@ void XYChart::handlePointAdded(int index)
Q_ASSERT(index < m_series->count());
Q_ASSERT(index >= 0);
- QVector<QPointF> points;
-
- if (m_dirty || m_points.isEmpty()) {
- points = domain()->calculateGeometryPoints(m_series->points());
+ if (m_series->useOpenGL()) {
+ updateGlChart();
} else {
- points = m_points;
- QPointF point = domain()->calculateGeometryPoint(m_series->points()[index], m_validData);
- if (!m_validData)
- m_points.clear();
- else
- points.insert(index, point);
+ QVector<QPointF> points;
+ if (m_dirty || m_points.isEmpty()) {
+ points = domain()->calculateGeometryPoints(m_series->pointsVector());
+ } else {
+ points = m_points;
+ QPointF point = domain()->calculateGeometryPoint(m_series->pointsVector().at(index),
+ m_validData);
+ if (!m_validData)
+ m_points.clear();
+ else
+ points.insert(index, point);
+ }
+ updateChart(m_points, points, index);
}
-
- updateChart(m_points, points, index);
}
void XYChart::handlePointRemoved(int index)
@@ -134,16 +146,18 @@ void XYChart::handlePointRemoved(int index)
Q_ASSERT(index <= m_series->count());
Q_ASSERT(index >= 0);
- QVector<QPointF> points;
-
- if (m_dirty || m_points.isEmpty()) {
- points = domain()->calculateGeometryPoints(m_series->points());
+ if (m_series->useOpenGL()) {
+ updateGlChart();
} else {
- points = m_points;
- points.remove(index);
+ QVector<QPointF> points;
+ if (m_dirty || m_points.isEmpty()) {
+ points = domain()->calculateGeometryPoints(m_series->pointsVector());
+ } else {
+ points = m_points;
+ points.remove(index);
+ }
+ updateChart(m_points, points, index);
}
-
- updateChart(m_points, points, index);
}
void XYChart::handlePointsRemoved(int index, int count)
@@ -151,16 +165,18 @@ void XYChart::handlePointsRemoved(int index, int count)
Q_ASSERT(index <= m_series->count());
Q_ASSERT(index >= 0);
- QVector<QPointF> points;
-
- if (m_dirty || m_points.isEmpty()) {
- points = domain()->calculateGeometryPoints(m_series->points());
+ if (m_series->useOpenGL()) {
+ updateGlChart();
} else {
- points = m_points;
- points.remove(index, count);
+ QVector<QPointF> points;
+ if (m_dirty || m_points.isEmpty()) {
+ points = domain()->calculateGeometryPoints(m_series->pointsVector());
+ } else {
+ points = m_points;
+ points.remove(index, count);
+ }
+ updateChart(m_points, points, index);
}
-
- updateChart(m_points, points, index);
}
void XYChart::handlePointReplaced(int index)
@@ -168,34 +184,45 @@ void XYChart::handlePointReplaced(int index)
Q_ASSERT(index < m_series->count());
Q_ASSERT(index >= 0);
- QVector<QPointF> points;
-
- if (m_dirty || m_points.isEmpty()) {
- points = domain()->calculateGeometryPoints(m_series->points());
+ if (m_series->useOpenGL()) {
+ updateGlChart();
} else {
- QPointF point = domain()->calculateGeometryPoint(m_series->points()[index], m_validData);
- if (!m_validData)
- m_points.clear();
- points = m_points;
- if (m_validData)
- points.replace(index, point);
+ QVector<QPointF> points;
+ if (m_dirty || m_points.isEmpty()) {
+ points = domain()->calculateGeometryPoints(m_series->pointsVector());
+ } else {
+ QPointF point = domain()->calculateGeometryPoint(m_series->pointsVector().at(index),
+ m_validData);
+ if (!m_validData)
+ m_points.clear();
+ points = m_points;
+ if (m_validData)
+ points.replace(index, point);
+ }
+ updateChart(m_points, points, index);
}
-
- updateChart(m_points, points, index);
}
void XYChart::handlePointsReplaced()
{
- // All the points were replaced -> recalculate
- QVector<QPointF> points = domain()->calculateGeometryPoints(m_series->points());
- updateChart(m_points, points, -1);
+ if (m_series->useOpenGL()) {
+ updateGlChart();
+ } else {
+ // All the points were replaced -> recalculate
+ QVector<QPointF> points = domain()->calculateGeometryPoints(m_series->pointsVector());
+ updateChart(m_points, points, -1);
+ }
}
void XYChart::handleDomainUpdated()
{
- if (isEmpty()) return;
- QVector<QPointF> points = domain()->calculateGeometryPoints(m_series->points());
- updateChart(m_points, points);
+ if (m_series->useOpenGL()) {
+ updateGlChart();
+ } else {
+ if (isEmpty()) return;
+ QVector<QPointF> points = domain()->calculateGeometryPoints(m_series->pointsVector());
+ updateChart(m_points, points);
+ }
}
bool XYChart::isEmpty()
diff --git a/src/charts/xychart/xychart.pri b/src/charts/xychart/xychart.pri
index e7af66e5..1c5efb90 100644
--- a/src/charts/xychart/xychart.pri
+++ b/src/charts/xychart/xychart.pri
@@ -6,12 +6,14 @@ SOURCES += \
$$PWD/qxyseries.cpp \
$$PWD/qxymodelmapper.cpp \
$$PWD/qvxymodelmapper.cpp \
- $$PWD/qhxymodelmapper.cpp
+ $$PWD/qhxymodelmapper.cpp \
+ $$PWD/glxyseriesdata.cpp
PRIVATE_HEADERS += \
$$PWD/xychart_p.h \
$$PWD/qxyseries_p.h \
- $$PWD/qxymodelmapper_p.h
+ $$PWD/qxymodelmapper_p.h \
+ $$PWD/glxyseriesdata_p.h
PUBLIC_HEADERS += \
$$PWD/qxyseries.h \
diff --git a/src/charts/xychart/xychart_p.h b/src/charts/xychart/xychart_p.h
index f45d9c6d..e72dcd38 100644
--- a/src/charts/xychart/xychart_p.h
+++ b/src/charts/xychart/xychart_p.h
@@ -76,6 +76,7 @@ Q_SIGNALS:
protected:
virtual void updateChart(QVector<QPointF> &oldPoints, QVector<QPointF> &newPoints, int index = -1);
+ virtual void updateGlChart();
private:
inline bool isEmpty();