diff options
author | Miikka Heikkinen <miikka.heikkinen@theqtcompany.com> | 2015-09-15 17:39:54 +0300 |
---|---|---|
committer | Miikka Heikkinen <miikka.heikkinen@theqtcompany.com> | 2015-09-25 06:44:35 +0000 |
commit | 79a856530b6986ca6d6d7485b2e6cec810c3b7fe (patch) | |
tree | 6f3f8dfe76e64da9ec48909fef1d7cccb87c3fc0 /src/charts/xychart | |
parent | 3a89e3fee61a52927f836f3b3de4c922c6b954e4 (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.cpp | 147 | ||||
-rw-r--r-- | src/charts/xychart/glxyseriesdata_p.h | 91 | ||||
-rw-r--r-- | src/charts/xychart/qxyseries.cpp | 25 | ||||
-rw-r--r-- | src/charts/xychart/qxyseries.h | 2 | ||||
-rw-r--r-- | src/charts/xychart/xychart.cpp | 119 | ||||
-rw-r--r-- | src/charts/xychart/xychart.pri | 6 | ||||
-rw-r--r-- | src/charts/xychart/xychart_p.h | 1 |
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(); |