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 | |
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')
37 files changed, 913 insertions, 75 deletions
diff --git a/src/charts/areachart/qareaseries.cpp b/src/charts/areachart/qareaseries.cpp index a33db927..c6dd9f41 100644 --- a/src/charts/areachart/qareaseries.cpp +++ b/src/charts/areachart/qareaseries.cpp @@ -345,7 +345,7 @@ QT_CHARTS_BEGIN_NAMESPACE \sa pointLabelsVisible */ /*! - \fn void QAreaSeries::pointLabelsClippintChanged(bool clipping) + \fn void QAreaSeries::pointLabelsClippingChanged(bool clipping) The clipping of the data point labels is changed to \a clipping. */ /*! @@ -395,8 +395,10 @@ QAbstractSeries::SeriesType QAreaSeries::type() const void QAreaSeries::setUpperSeries(QLineSeries *series) { Q_D(QAreaSeries); - if (d->m_upperSeries != series) + if (d->m_upperSeries != series) { + series->d_ptr->setBlockOpenGL(true); d->m_upperSeries = series; + } } QLineSeries *QAreaSeries::upperSeries() const @@ -411,6 +413,7 @@ QLineSeries *QAreaSeries::upperSeries() const void QAreaSeries::setLowerSeries(QLineSeries *series) { Q_D(QAreaSeries); + series->d_ptr->setBlockOpenGL(true); d->m_lowerSeries = series; } @@ -624,7 +627,7 @@ void QAreaSeriesPrivate::initializeDomain() QLineSeries *lowerSeries = q->lowerSeries(); if (upperSeries) { - const QList<QPointF>& points = upperSeries->points(); + const QVector<QPointF> &points = upperSeries->pointsVector(); for (int i = 0; i < points.count(); i++) { qreal x = points[i].x(); @@ -636,8 +639,7 @@ void QAreaSeriesPrivate::initializeDomain() } } if (lowerSeries) { - - const QList<QPointF>& points = lowerSeries->points(); + const QVector<QPointF> &points = lowerSeries->pointsVector(); for (int i = 0; i < points.count(); i++) { qreal x = points[i].x(); diff --git a/src/charts/axis/qabstractaxis.cpp b/src/charts/axis/qabstractaxis.cpp index e286ba51..1bbe63cb 100644 --- a/src/charts/axis/qabstractaxis.cpp +++ b/src/charts/axis/qabstractaxis.cpp @@ -883,7 +883,7 @@ bool QAbstractAxis::isVisible() const } /*! - Sets axis, shades, labels and grid lines to be visible. + Sets axis, shades, labels and grid lines visibility to \a visible. */ void QAbstractAxis::setVisible(bool visible) { diff --git a/src/charts/barchart/qbarset.cpp b/src/charts/barchart/qbarset.cpp index 4eb1cdbe..0ff859d8 100644 --- a/src/charts/barchart/qbarset.cpp +++ b/src/charts/barchart/qbarset.cpp @@ -546,7 +546,7 @@ QColor QBarSet::borderColor() } /*! - Sets the color of pen for this bar set. + Sets the \a color of pen for this bar set. */ void QBarSet::setBorderColor(QColor color) { @@ -567,7 +567,7 @@ QColor QBarSet::labelColor() } /*! - Sets the color of labels for this bar set. + Sets the \a color of labels for this bar set. */ void QBarSet::setLabelColor(QColor color) { diff --git a/src/charts/chartdataset.cpp b/src/charts/chartdataset.cpp index 35361ff6..1da28b95 100644 --- a/src/charts/chartdataset.cpp +++ b/src/charts/chartdataset.cpp @@ -38,6 +38,7 @@ #include <private/xlogypolardomain_p.h> #include <private/logxypolardomain_p.h> #include <private/logxlogypolardomain_p.h> +#include <private/glxyseriesdata_p.h> #ifndef QT_ON_ARM #include <QtCharts/QDateTimeAxis> @@ -47,7 +48,8 @@ QT_CHARTS_BEGIN_NAMESPACE ChartDataSet::ChartDataSet(QChart *chart) : QObject(chart), - m_chart(chart) + m_chart(chart), + m_glXYSeriesDataManager(new GLXYSeriesDataManager(this)) { } @@ -77,6 +79,8 @@ void ChartDataSet::addSeries(QAbstractSeries *series) qWarning() << QObject::tr("Can not add series. Series type is not supported by a polar chart."); return; } + // Disable OpenGL for series in polar charts + series->setUseOpenGL(false); series->d_ptr->setDomain(new XYPolarDomain()); // Set the correct domain for upper and lower series too if (series->type() == QAbstractSeries::SeriesTypeArea) { @@ -157,6 +161,10 @@ void ChartDataSet::removeSeries(QAbstractSeries *series) series->d_ptr->setDomain(new XYDomain()); series->setParent(0); series->d_ptr->m_chart = 0; + + QXYSeries *xySeries = qobject_cast<QXYSeries *>(series); + if (xySeries) + m_glXYSeriesDataManager->removeSeries(xySeries); } /* diff --git a/src/charts/chartdataset_p.h b/src/charts/chartdataset_p.h index 3fd71a79..2cc8c2d0 100644 --- a/src/charts/chartdataset_p.h +++ b/src/charts/chartdataset_p.h @@ -37,6 +37,7 @@ QT_CHARTS_BEGIN_NAMESPACE class QAbstractAxis; class ChartPresenter; +class GLXYSeriesDataManager; class QT_CHARTS_AUTOTEST_EXPORT ChartDataSet : public QObject { @@ -67,6 +68,8 @@ public: QPointF mapToValue(const QPointF &position, QAbstractSeries *series = 0); QPointF mapToPosition(const QPointF &value, QAbstractSeries *series = 0); + GLXYSeriesDataManager *glXYSeriesDataManager() { return m_glXYSeriesDataManager; } + Q_SIGNALS: void axisAdded(QAbstractAxis* axis); void axisRemoved(QAbstractAxis* axis); @@ -85,6 +88,7 @@ private: QList<QAbstractSeries *> m_seriesList; QList<QAbstractAxis *> m_axisList; QChart* m_chart; + GLXYSeriesDataManager *m_glXYSeriesDataManager; }; QT_CHARTS_END_NAMESPACE diff --git a/src/charts/chartelement.cpp b/src/charts/chartelement.cpp index 5ccf8d4e..9e540cf0 100644 --- a/src/charts/chartelement.cpp +++ b/src/charts/chartelement.cpp @@ -19,13 +19,15 @@ #include <private/chartelement_p.h> #include <private/chartpresenter_p.h> #include <private/abstractdomain_p.h> +#include <private/chartdataset_p.h> QT_CHARTS_BEGIN_NAMESPACE ChartElement::ChartElement(QGraphicsItem* item): QGraphicsObject(item), m_presenter(0), - m_themeManager(0) + m_themeManager(0), + m_dataSet(0) { } @@ -50,4 +52,14 @@ ChartThemeManager* ChartElement::themeManager() const return m_themeManager; } +void ChartElement::setDataSet(ChartDataSet *dataSet) +{ + m_dataSet = dataSet; +} + +ChartDataSet *ChartElement::dataSet() const +{ + return m_dataSet; +} + QT_CHARTS_END_NAMESPACE diff --git a/src/charts/chartelement_p.h b/src/charts/chartelement_p.h index 60b2552c..72b55e78 100644 --- a/src/charts/chartelement_p.h +++ b/src/charts/chartelement_p.h @@ -40,6 +40,7 @@ class ChartPresenter; class ChartAnimation; class ChartThemeManager; class AbstractDomain; +class ChartDataSet; class ChartElement: public QGraphicsObject { @@ -52,10 +53,13 @@ public: ChartPresenter *presenter() const; virtual void setThemeManager(ChartThemeManager *manager); ChartThemeManager* themeManager() const; + virtual void setDataSet(ChartDataSet *dataSet); + ChartDataSet *dataSet() const; private: ChartPresenter *m_presenter; ChartThemeManager *m_themeManager; + ChartDataSet *m_dataSet; }; QT_CHARTS_END_NAMESPACE diff --git a/src/charts/chartpresenter.cpp b/src/charts/chartpresenter.cpp index df66c01b..6fc35033 100644 --- a/src/charts/chartpresenter.cpp +++ b/src/charts/chartpresenter.cpp @@ -46,6 +46,10 @@ ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type) m_plotAreaBackground(0), m_title(0), m_localizeNumbers(false) +#ifndef QT_NO_OPENGL + , m_glWidget(0) + , m_glUseWidget(true) +#endif { if (type == QChart::ChartTypeCartesian) m_layout = new CartesianChartLayout(this); @@ -56,7 +60,9 @@ ChartPresenter::ChartPresenter(QChart *chart, QChart::ChartType type) ChartPresenter::~ChartPresenter() { - +#ifndef QT_NO_OPENGL + delete m_glWidget.data(); +#endif } void ChartPresenter::setGeometry(const QRectF rect) @@ -67,6 +73,10 @@ void ChartPresenter::setGeometry(const QRectF rect) chart->domain()->setSize(rect.size()); chart->setPos(rect.topLeft()); } +#ifndef QT_NO_OPENGL + if (!m_glWidget.isNull()) + m_glWidget->setGeometry(m_rect.toRect()); +#endif emit plotAreaChanged(m_rect); } } @@ -108,6 +118,7 @@ void ChartPresenter::handleSeriesAdded(QAbstractSeries *series) ChartItem *chart = series->d_ptr->chartItem(); chart->setPresenter(this); chart->setThemeManager(m_chart->d_ptr->m_themeManager); + chart->setDataSet(m_chart->d_ptr->m_dataset); chart->domain()->setSize(m_rect.size()); chart->setPos(m_rect.topLeft()); chart->handleDomainUpdated(); //this could be moved to intializeGraphics when animator is refactored @@ -531,6 +542,28 @@ QString ChartPresenter::numberToString(int value) return QString::number(value); } +void ChartPresenter::ensureGLWidget() +{ +#ifndef QT_NO_OPENGL + // GLWidget pointer is wrapped in QPointer as its parent is not in our control, and therefore + // can potentially get deleted unexpectedly. + if (m_glWidget.isNull() && m_glUseWidget && m_chart->scene()) { + QObject *parent = m_chart->scene()->parent(); + while (parent) { + QWidget *parentWidget = qobject_cast<QWidget *>(parent); + if (parentWidget) { + m_glWidget = new GLWidget(m_chart->d_ptr->m_dataset->glXYSeriesDataManager(), + parentWidget); + m_glWidget->setGeometry(m_rect.toRect()); + m_glWidget->show(); + break; + } + parent = parent->parent(); + } + } +#endif +} + #include "moc_chartpresenter_p.cpp" QT_CHARTS_END_NAMESPACE diff --git a/src/charts/chartpresenter_p.h b/src/charts/chartpresenter_p.h index bf9d5a90..8dadbbc3 100644 --- a/src/charts/chartpresenter_p.h +++ b/src/charts/chartpresenter_p.h @@ -30,9 +30,11 @@ #include <QtCharts/QChartGlobal> #include <QtCharts/QChart> //because of QChart::ChartThemeId +#include <private/glwidget_p.h> #include <QtCore/QRectF> #include <QtCore/QMargins> #include <QtCore/QLocale> +#include <QtCore/QPointer> QT_CHARTS_BEGIN_NAMESPACE @@ -160,6 +162,9 @@ public: QString numberToString(double value, char f = 'g', int prec = 6); QString numberToString(int value); + void ensureGLWidget(); + void glSetUseWidget(bool enable) { m_glUseWidget = enable; } + private: void createBackgroundItem(); void createPlotAreaBackgroundItem(); @@ -192,6 +197,10 @@ private: QRectF m_rect; bool m_localizeNumbers; QLocale m_locale; +#ifndef QT_NO_OPENGL + QPointer<GLWidget> m_glWidget; +#endif + bool m_glUseWidget; }; QT_CHARTS_END_NAMESPACE diff --git a/src/charts/charts.pro b/src/charts/charts.pro index a89b1dd1..2ec6ce01 100644 --- a/src/charts/charts.pro +++ b/src/charts/charts.pro @@ -32,6 +32,9 @@ SOURCES += \ $$PWD/scroller.cpp \ $$PWD/charttitle.cpp \ $$PWD/qpolarchart.cpp + +contains(QT_CONFIG, opengl): SOURCES += $$PWD/glwidget.cpp + PRIVATE_HEADERS += \ $$PWD/chartdataset_p.h \ $$PWD/chartitem_p.h \ @@ -46,6 +49,9 @@ PRIVATE_HEADERS += \ $$PWD/qabstractseries_p.h \ $$PWD/charttitle_p.h \ $$PWD/charthelpers_p.h + +contains(QT_CONFIG, opengl): PRIVATE_HEADERS += $$PWD/glwidget_p.h + PUBLIC_HEADERS += \ $$PWD/qchart.h \ $$PWD/qchartglobal.h \ diff --git a/src/charts/doc/images/examples_openglseries.png b/src/charts/doc/images/examples_openglseries.png Binary files differnew file mode 100644 index 00000000..56719a97 --- /dev/null +++ b/src/charts/doc/images/examples_openglseries.png diff --git a/src/charts/doc/src/examples-openglseries.qdoc b/src/charts/doc/src/examples-openglseries.qdoc new file mode 100644 index 00000000..9c28af3c --- /dev/null +++ b/src/charts/doc/src/examples-openglseries.qdoc @@ -0,0 +1,37 @@ +/**************************************************************************** +** +** 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 +** +****************************************************************************/ + +/*! + \example openglseries + \title OpenGL Accelerated Series Example + \ingroup qtcharts_examples + + \brief The example shows how to enable OpenGL acceleration for QLineSeries and QScatterSeries. + + \image examples_openglseries.png + + To create an OpenGL accelerated series, all you have to do compared to a regular + series is to set QAbstractSeries::useOpenGL property to \c{true}: + + \snippet openglseries/main.cpp 1 + + This makes the chart to instantiate a transparent QOpenGLWidget that is used to draw + the accelerated series on top of the chart. + + \note The OpenGL acceleration is only supported for QLineSeries and QScatterSeries. +*/ diff --git a/src/charts/domain/abstractdomain_p.h b/src/charts/domain/abstractdomain_p.h index 50a629fd..f8c0e942 100644 --- a/src/charts/domain/abstractdomain_p.h +++ b/src/charts/domain/abstractdomain_p.h @@ -92,7 +92,7 @@ public: virtual QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const = 0; virtual QPointF calculateDomainPoint(const QPointF &point) const = 0; - virtual QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const = 0; + virtual QVector<QPointF> calculateGeometryPoints(const QVector<QPointF> &vector) const = 0; virtual bool attachAxis(QAbstractAxis *axis); virtual bool detachAxis(QAbstractAxis *axis); diff --git a/src/charts/domain/logxlogydomain.cpp b/src/charts/domain/logxlogydomain.cpp index 292b259c..665a13f1 100644 --- a/src/charts/domain/logxlogydomain.cpp +++ b/src/charts/domain/logxlogydomain.cpp @@ -162,7 +162,7 @@ QPointF LogXLogYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) c return QPointF(x, y); } -QVector<QPointF> LogXLogYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const +QVector<QPointF> LogXLogYDomain::calculateGeometryPoints(const QVector<QPointF> &vector) const { const qreal deltaX = m_size.width() / qAbs(m_logRightX - m_logLeftX); const qreal deltaY = m_size.height() / qAbs(m_logRightY - m_logLeftY); diff --git a/src/charts/domain/logxlogydomain_p.h b/src/charts/domain/logxlogydomain_p.h index ed8fb265..11bc6010 100644 --- a/src/charts/domain/logxlogydomain_p.h +++ b/src/charts/domain/logxlogydomain_p.h @@ -54,7 +54,7 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; + QVector<QPointF> calculateGeometryPoints(const QVector<QPointF> &vector) const; bool attachAxis(QAbstractAxis *axis); bool detachAxis(QAbstractAxis *axis); diff --git a/src/charts/domain/logxydomain.cpp b/src/charts/domain/logxydomain.cpp index 00e8b1ce..1c306070 100644 --- a/src/charts/domain/logxydomain.cpp +++ b/src/charts/domain/logxydomain.cpp @@ -146,7 +146,7 @@ QPointF LogXYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) cons return QPointF(x, y); } -QVector<QPointF> LogXYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const +QVector<QPointF> LogXYDomain::calculateGeometryPoints(const QVector<QPointF> &vector) const { const qreal deltaX = m_size.width() / (m_logRightX - m_logLeftX); const qreal deltaY = m_size.height() / (m_maxY - m_minY); diff --git a/src/charts/domain/logxydomain_p.h b/src/charts/domain/logxydomain_p.h index 72c89b18..5ff8b0c3 100644 --- a/src/charts/domain/logxydomain_p.h +++ b/src/charts/domain/logxydomain_p.h @@ -54,7 +54,7 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; + QVector<QPointF> calculateGeometryPoints(const QVector<QPointF> &vector) const; bool attachAxis(QAbstractAxis *axis); bool detachAxis(QAbstractAxis *axis); diff --git a/src/charts/domain/polardomain.cpp b/src/charts/domain/polardomain.cpp index 4681f6c4..55fedf06 100644 --- a/src/charts/domain/polardomain.cpp +++ b/src/charts/domain/polardomain.cpp @@ -53,7 +53,7 @@ QPointF PolarDomain::calculateGeometryPoint(const QPointF &point, bool &ok) cons } } -QVector<QPointF> PolarDomain::calculateGeometryPoints(const QList<QPointF> &vector) const +QVector<QPointF> PolarDomain::calculateGeometryPoints(const QVector<QPointF> &vector) const { QVector<QPointF> result; result.resize(vector.count()); diff --git a/src/charts/domain/polardomain_p.h b/src/charts/domain/polardomain_p.h index 6ed5b85d..e179a8a7 100644 --- a/src/charts/domain/polardomain_p.h +++ b/src/charts/domain/polardomain_p.h @@ -43,7 +43,7 @@ public: void setSize(const QSizeF &size); QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; + QVector<QPointF> calculateGeometryPoints(const QVector<QPointF> &vector) const; virtual qreal toAngularCoordinate(qreal value, bool &ok) const = 0; virtual qreal toRadialCoordinate(qreal value, bool &ok) const = 0; diff --git a/src/charts/domain/xlogydomain.cpp b/src/charts/domain/xlogydomain.cpp index bc9d367b..ca4986d8 100644 --- a/src/charts/domain/xlogydomain.cpp +++ b/src/charts/domain/xlogydomain.cpp @@ -146,7 +146,7 @@ QPointF XLogYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) cons return QPointF(x, y); } -QVector<QPointF> XLogYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const +QVector<QPointF> XLogYDomain::calculateGeometryPoints(const QVector<QPointF> &vector) const { const qreal deltaX = m_size.width() / (m_maxX - m_minX); const qreal deltaY = m_size.height() / qAbs(m_logRightY - m_logLeftY); diff --git a/src/charts/domain/xlogydomain_p.h b/src/charts/domain/xlogydomain_p.h index 5dbd2a4c..c49f7bb1 100644 --- a/src/charts/domain/xlogydomain_p.h +++ b/src/charts/domain/xlogydomain_p.h @@ -54,7 +54,7 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; + QVector<QPointF> calculateGeometryPoints(const QVector<QPointF> &vector) const; bool attachAxis(QAbstractAxis *axis); bool detachAxis(QAbstractAxis *axis); diff --git a/src/charts/domain/xydomain.cpp b/src/charts/domain/xydomain.cpp index cad9dcea..da205907 100644 --- a/src/charts/domain/xydomain.cpp +++ b/src/charts/domain/xydomain.cpp @@ -144,7 +144,7 @@ QPointF XYDomain::calculateGeometryPoint(const QPointF &point, bool &ok) const return QPointF(x, y); } -QVector<QPointF> XYDomain::calculateGeometryPoints(const QList<QPointF> &vector) const +QVector<QPointF> XYDomain::calculateGeometryPoints(const QVector<QPointF> &vector) const { const qreal deltaX = m_size.width() / (m_maxX - m_minX); const qreal deltaY = m_size.height() / (m_maxY - m_minY); diff --git a/src/charts/domain/xydomain_p.h b/src/charts/domain/xydomain_p.h index 735fc0f0..3ba8e1f3 100644 --- a/src/charts/domain/xydomain_p.h +++ b/src/charts/domain/xydomain_p.h @@ -54,7 +54,7 @@ public: QPointF calculateGeometryPoint(const QPointF &point, bool &ok) const; QPointF calculateDomainPoint(const QPointF &point) const; - QVector<QPointF> calculateGeometryPoints(const QList<QPointF> &vector) const; + QVector<QPointF> calculateGeometryPoints(const QVector<QPointF> &vector) const; }; QT_CHARTS_END_NAMESPACE diff --git a/src/charts/glwidget.cpp b/src/charts/glwidget.cpp new file mode 100644 index 00000000..a4470489 --- /dev/null +++ b/src/charts/glwidget.cpp @@ -0,0 +1,222 @@ +/**************************************************************************** + ** + ** 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 + ** + ****************************************************************************/ + +#ifndef QT_NO_OPENGL + +#include "private/glwidget_p.h" +#include "private/glxyseriesdata_p.h" +#include <QtGui/QOpenGLShaderProgram> +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLBuffer> + +//#define QDEBUG_TRACE_GL_FPS +#ifdef QDEBUG_TRACE_GL_FPS +# include <QElapsedTimer> +#endif + +QT_CHARTS_BEGIN_NAMESPACE + +GLWidget::GLWidget(GLXYSeriesDataManager *xyDataManager, QWidget *parent) + : QOpenGLWidget(parent), + m_program(0), + m_shaderAttribLoc(-1), + m_colorUniformLoc(-1), + m_minUniformLoc(-1), + m_deltaUniformLoc(-1), + m_pointSizeUniformLoc(-1), + m_xyDataManager(xyDataManager) +{ + setAttribute(Qt::WA_TranslucentBackground); + setAttribute(Qt::WA_AlwaysStackOnTop); + setAttribute(Qt::WA_TransparentForMouseEvents); + + QSurfaceFormat surfaceFormat; + surfaceFormat.setDepthBufferSize(0); + surfaceFormat.setStencilBufferSize(0); + surfaceFormat.setRedBufferSize(8); + surfaceFormat.setGreenBufferSize(8); + surfaceFormat.setBlueBufferSize(8); + surfaceFormat.setAlphaBufferSize(8); + surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer); + surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType); + setFormat(surfaceFormat); + + connect(xyDataManager, &GLXYSeriesDataManager::seriesRemoved, + this, &GLWidget::cleanXYSeriesResources); +} + +GLWidget::~GLWidget() +{ + cleanup(); +} + +void GLWidget::cleanup() +{ + makeCurrent(); + + delete m_program; + m_program = 0; + + foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values()) + delete buffer; + m_seriesBufferMap.clear(); + + doneCurrent(); +} + +void GLWidget::cleanXYSeriesResources(const QXYSeries *series) +{ + makeCurrent(); + if (series) { + delete m_seriesBufferMap.take(series); + } else { + // Null series means all series were removed + foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values()) + delete buffer; + m_seriesBufferMap.clear(); + } + doneCurrent(); +} + +static const char *vertexSource = + "attribute highp vec2 points;\n" + "uniform highp vec2 min;\n" + "uniform highp vec2 delta;\n" + "uniform highp float pointSize;\n" + "void main() {\n" + " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n" + " gl_Position = vec4(normalPoint, 0, 1);\n" + " gl_PointSize = pointSize;\n" + "}"; +static const char *fragmentSource = + "uniform highp vec3 color;\n" + "void main() {\n" + " gl_FragColor = vec4(color,1);\n" + "}\n"; + +void GLWidget::initializeGL() +{ + connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &GLWidget::cleanup); + + initializeOpenGLFunctions(); + glClearColor(0, 0, 0, 0); + + m_program = new QOpenGLShaderProgram; + m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexSource); + m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentSource); + m_program->bindAttributeLocation("points", 0); + m_program->link(); + + m_program->bind(); + m_colorUniformLoc = m_program->uniformLocation("color"); + m_minUniformLoc = m_program->uniformLocation("min"); + m_deltaUniformLoc = m_program->uniformLocation("delta"); + m_pointSizeUniformLoc = m_program->uniformLocation("pointSize"); + + + // Create a vertex array object. In OpenGL ES 2.0 and OpenGL 2.x + // implementations this is optional and support may not be present + // at all. Nonetheless the below code works in all cases and makes + // sure there is a VAO when one is needed. + m_vao.create(); + QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); + + glEnableVertexAttribArray(0); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_STENCIL_TEST); + +#if !defined(QT_OPENGL_ES_2) + if (!QOpenGLContext::currentContext()->isOpenGLES()) { + // Make it possible to change point primitive size and use textures with them in + // the shaders. These are implicitly enabled in ES2. + glEnable(GL_PROGRAM_POINT_SIZE); + } +#endif + + m_program->release(); +} + +void GLWidget::paintGL() +{ + glClear(GL_COLOR_BUFFER_BIT); + + QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); + m_program->bind(); + + GLXYDataMapIterator i(m_xyDataManager->dataMap()); + while (i.hasNext()) { + i.next(); + QOpenGLBuffer *vbo = m_seriesBufferMap.value(i.key()); + GLXYSeriesData *data = i.value(); + + m_program->setUniformValue(m_colorUniformLoc, data->color); + m_program->setUniformValue(m_minUniformLoc, data->min); + m_program->setUniformValue(m_deltaUniformLoc, data->delta); + + if (!vbo) { + vbo = new QOpenGLBuffer; + m_seriesBufferMap.insert(i.key(), vbo); + vbo->create(); + } + vbo->bind(); + if (data->dirty) { + vbo->allocate(data->array.constData(), data->array.count() * sizeof(GLfloat)); + data->dirty = false; + } + + glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0); + if (data->type == QAbstractSeries::SeriesTypeLine) { + glLineWidth(data->width); + glDrawArrays(GL_LINE_STRIP, 0, data->array.size() / 2); + } else { // Scatter + m_program->setUniformValue(m_pointSizeUniformLoc, data->width); + glDrawArrays(GL_POINTS, 0, data->array.size() / 2); + } + vbo->release(); + } + +#ifdef QDEBUG_TRACE_GL_FPS + static QElapsedTimer stopWatch; + static int frameCount = -1; + if (frameCount == -1) { + stopWatch.start(); + frameCount = 0; + } + frameCount++; + int elapsed = stopWatch.elapsed(); + if (elapsed >= 1000) { + elapsed = stopWatch.restart(); + qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed)))); + qDebug() << "FPS:" << fps; + frameCount = 0; + } +#endif + + m_program->release(); +} + +void GLWidget::resizeGL(int w, int h) +{ + Q_UNUSED(w) + Q_UNUSED(h) +} + +QT_CHARTS_END_NAMESPACE + +#endif diff --git a/src/charts/glwidget_p.h b/src/charts/glwidget_p.h new file mode 100644 index 00000000..209c634f --- /dev/null +++ b/src/charts/glwidget_p.h @@ -0,0 +1,80 @@ +/**************************************************************************** +** +** 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 GLWIDGET_H +#define GLWIDGET_H + +#ifndef QT_NO_OPENGL + +#include <QtWidgets/QOpenGLWidget> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QOpenGLVertexArrayObject> +#include <QtCore/QHash> +#include <QtCharts/QAbstractSeries> +#include <QtCharts/QXYSeries> + +QT_FORWARD_DECLARE_CLASS(QOpenGLShaderProgram) + +class QOpenGLBuffer; + +QT_CHARTS_BEGIN_NAMESPACE + +class GLXYSeriesDataManager; + +class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + GLWidget(GLXYSeriesDataManager *xyDataManager, QWidget *parent = 0); + ~GLWidget(); + +public Q_SLOTS: + void cleanup(); + void cleanXYSeriesResources(const QXYSeries *series); + +protected: + void initializeGL() Q_DECL_OVERRIDE; + void paintGL() Q_DECL_OVERRIDE; + void resizeGL(int width, int height) Q_DECL_OVERRIDE; + +private: + QOpenGLShaderProgram *m_program; + int m_shaderAttribLoc; + int m_colorUniformLoc; + int m_minUniformLoc; + int m_deltaUniformLoc; + int m_pointSizeUniformLoc; + QOpenGLVertexArrayObject m_vao; + + QHash<const QAbstractSeries *, QOpenGLBuffer *> m_seriesBufferMap; + GLXYSeriesDataManager *m_xyDataManager; +}; + +QT_CHARTS_END_NAMESPACE +#endif +#endif diff --git a/src/charts/linechart/linechartitem.cpp b/src/charts/linechart/linechartitem.cpp index 84d9e1ef..f0b0a577 100644 --- a/src/charts/linechart/linechartitem.cpp +++ b/src/charts/linechart/linechartitem.cpp @@ -70,6 +70,17 @@ QPainterPath LineChartItem::shape() const void LineChartItem::updateGeometry() { + static const QRectF dummyRect = QRectF(0.0, 0.0, 0.001, 0.001); + if (m_series->useOpenGL()) { + // Fake a miniscule region, so we trigger changed signal. + if (m_rect.width() != dummyRect.width()) { + prepareGeometryChange(); + m_rect = dummyRect; + } + update(); + return; + } + // Store the points to a local variable so that the old line gets properly cleared // when animation starts. m_linePoints = geometryPoints(); @@ -345,6 +356,9 @@ void LineChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *opt Q_UNUSED(widget) Q_UNUSED(option) + if (m_series->useOpenGL()) + return; + QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); painter->save(); diff --git a/src/charts/qabstractseries.cpp b/src/charts/qabstractseries.cpp index cb3c7c5a..8ce0c9dd 100644 --- a/src/charts/qabstractseries.cpp +++ b/src/charts/qabstractseries.cpp @@ -135,6 +135,69 @@ QT_CHARTS_BEGIN_NAMESPACE */ /*! + \property QAbstractSeries::useOpenGL + \brief Specifies whether or not the series drawing is accelerated with OpenGL. + + Drawing series with OpenGL is supported only for QLineSeries and QScatterSeries. + Line series used as edge series for a QAreaSeries cannot use OpenGL acceleration. + When a chart contains any series that are drawn with OpenGL, a transparent QOpenGLWidget + is created on top of the chart plot area. Specified series are not drawn on the underlying + QGraphicsView, but are instead drawn on the created QOpenGLWidget. + + Performance gained from using OpenGL to accelerate series drawing depends on the underlying + hardware, but in most cases it is significant. For example, on a standard desktop computer, + enabling OpenGL acceleration for a series typically allows rendering at least hundred times + more points without reduction on the frame rate. + Chart size also has less effect on the frame rate. + + The OpenGL acceleration of series drawing is meant for use cases that need fast drawing of + large numbers of points. It is optimized for efficiency, and therefore the series using + it lack support for some features available to non-accelerated series. + + There are the following restrictions imposed on charts and series when using OpenGL + acceleration: + + \list + \li Series animations are not supported for accelerated series. + \li Antialiasing is not supported for accelerated series. + \li Pen styles and marker shapes are ignored for accelerated series. + Only solid lines and plain scatter dots are supported. + The scatter dots may be circular or rectangular, depending on the underlying graphics + hardware and drivers. + \li Polar charts are not supported for accelerated series. + \li Since the accelerated series are drawn on top of the entire graphics view, they get drawn + on top of any other graphics items that you may have on top chart in the same scene. + \li To enable QOpenGLWidget to be partially transparent, it needs to be stacked on top of + all other widgets. This means you cannot have other widgets partially covering the + chart. + \endlist + + The default value is \c{false}. +*/ +/*! + \qmlproperty bool AbstractSeries::useOpenGL + Specifies whether or not the series is drawn with OpenGL. + + Drawing series with OpenGL is supported only for LineSeries and ScatterSeries. + + For more details, see QAbstractSeries::useOpenGL documentation. QML applications have similar + restrictions as those listed in QAbstractSeries::useOpenGL documentation, + except there is no restriction about covering the ChartView partially with other + items due to different rendering mechanism. + + The default value is \c{false}. +*/ + +/*! + \fn void QAbstractSeries::useOpenGLChanged() + Emitted when the useOpenGL property value changes. +*/ +/*! + \qmlsignal AbstractSeries::onUseOpenGLChanged() + Emitted when the useOpenGL property value changes. +*/ + +/*! \internal \brief Constructs QAbstractSeries object with \a parent. */ @@ -192,6 +255,28 @@ void QAbstractSeries::setOpacity(qreal opacity) } } +void QAbstractSeries::setUseOpenGL(bool enable) +{ +#ifdef QT_NO_OPENGL + Q_UNUSED(enable) +#else + bool polarChart = d_ptr->m_chart && d_ptr->m_chart->chartType() == QChart::ChartTypePolar; + bool supportedSeries = (type() == SeriesTypeLine || type() == SeriesTypeScatter); + if ((!enable || !d_ptr->m_blockOpenGL) + && supportedSeries + && enable != d_ptr->m_useOpenGL + && (!enable || !polarChart)) { + d_ptr->m_useOpenGL = enable; + emit useOpenGLChanged(); + } +#endif +} + +bool QAbstractSeries::useOpenGL() const +{ + return d_ptr->m_useOpenGL; +} + /*! \brief Returns the chart where series belongs to. @@ -274,7 +359,9 @@ QAbstractSeriesPrivate::QAbstractSeriesPrivate(QAbstractSeries *q) m_item(0), m_domain(new XYDomain()), m_visible(true), - m_opacity(1.0) + m_opacity(1.0), + m_useOpenGL(false), + m_blockOpenGL(false) { } @@ -354,6 +441,15 @@ bool QAbstractSeriesPrivate::reverseYAxis() return reverseYAxis; } +// This function can be used to explicitly block OpenGL use from some otherwise supported series, +// such as the line series used as edge series of an area series. +void QAbstractSeriesPrivate::setBlockOpenGL(bool enable) +{ + m_blockOpenGL = enable; + if (enable) + q_ptr->setUseOpenGL(false); +} + #include "moc_qabstractseries.cpp" #include "moc_qabstractseries_p.cpp" diff --git a/src/charts/qabstractseries.h b/src/charts/qabstractseries.h index c861068c..3e34591b 100644 --- a/src/charts/qabstractseries.h +++ b/src/charts/qabstractseries.h @@ -36,6 +36,7 @@ class QT_CHARTS_EXPORT QAbstractSeries : public QObject Q_PROPERTY(bool visible READ isVisible WRITE setVisible NOTIFY visibleChanged) Q_PROPERTY(qreal opacity READ opacity WRITE setOpacity NOTIFY opacityChanged) Q_PROPERTY(SeriesType type READ type) + Q_PROPERTY(bool useOpenGL READ useOpenGL WRITE setUseOpenGL NOTIFY useOpenGLChanged) Q_ENUMS(SeriesType) public: @@ -67,6 +68,8 @@ public: bool isVisible() const; qreal opacity() const; void setOpacity(qreal opacity); + void setUseOpenGL(bool enable = true); + bool useOpenGL() const; QChart *chart() const; @@ -81,6 +84,7 @@ Q_SIGNALS: void nameChanged(); void visibleChanged(); void opacityChanged(); + void useOpenGLChanged(); protected: QScopedPointer<QAbstractSeriesPrivate> d_ptr; @@ -89,6 +93,7 @@ protected: friend class ChartThemeManager; friend class QLegendPrivate; friend class DeclarativeChart; + friend class QAreaSeries; }; QT_CHARTS_END_NAMESPACE diff --git a/src/charts/qabstractseries_p.h b/src/charts/qabstractseries_p.h index 7e481f4e..7120c0d2 100644 --- a/src/charts/qabstractseries_p.h +++ b/src/charts/qabstractseries_p.h @@ -81,6 +81,8 @@ public: bool reverseXAxis(); bool reverseYAxis(); + void setBlockOpenGL(bool enable); + Q_SIGNALS: void countChanged(); @@ -96,6 +98,8 @@ private: bool m_visible; qreal m_opacity; ChartPresenter *m_presenter; + bool m_useOpenGL; + bool m_blockOpenGL; friend class QAbstractSeries; friend class ChartDataSet; diff --git a/src/charts/scatterchart/scatterchartitem.cpp b/src/charts/scatterchart/scatterchartitem.cpp index ac0611e0..e8e4fe43 100644 --- a/src/charts/scatterchart/scatterchartitem.cpp +++ b/src/charts/scatterchart/scatterchartitem.cpp @@ -130,6 +130,18 @@ void ScatterChartItem::markerDoubleClicked(QGraphicsItem *marker) void ScatterChartItem::updateGeometry() { + static const QRectF dummyRect = QRectF(0.0, 0.0, 0.001, 0.001); + if (m_series->useOpenGL()) { + if (m_items.childItems().count()) + deletePoints(m_items.childItems().count()); + // Fake a miniscule region, so we trigger changed signal. + if (m_rect.width() != dummyRect.width()) { + prepareGeometryChange(); + m_rect = dummyRect; + } + update(); + return; + } const QVector<QPointF>& points = geometryPoints(); @@ -199,6 +211,9 @@ void ScatterChartItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * Q_UNUSED(option) Q_UNUSED(widget) + if (m_series->useOpenGL()) + return; + QRectF clipRect = QRectF(QPointF(0, 0), domain()->size()); painter->save(); 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(); |