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 | |
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>
58 files changed, 1959 insertions, 152 deletions
diff --git a/examples/charts/audio/xyseriesiodevice.cpp b/examples/charts/audio/xyseriesiodevice.cpp index ef9789cd..4b7d2f52 100644 --- a/examples/charts/audio/xyseriesiodevice.cpp +++ b/examples/charts/audio/xyseriesiodevice.cpp @@ -35,12 +35,12 @@ qint64 XYSeriesIODevice::readData(char * data, qint64 maxSize) qint64 XYSeriesIODevice::writeData(const char * data, qint64 maxSize) { qint64 range = 2000; - QList<QPointF> oldPoints = m_series->points(); - QList<QPointF> points; + QVector<QPointF> oldPoints = m_series->pointsVector(); + QVector<QPointF> points; int resolution = 4; if (oldPoints.count() < range) { - points = m_series->points(); + points = m_series->pointsVector(); } else { for (int i = maxSize/resolution; i < oldPoints.count(); i++) points.append(QPointF(i - maxSize/resolution, oldPoints.at(i).y())); diff --git a/examples/charts/charts.pro b/examples/charts/charts.pro index a104fcdd..3f463e46 100644 --- a/examples/charts/charts.pro +++ b/examples/charts/charts.pro @@ -50,7 +50,13 @@ qtHaveModule(quick) { qtHaveModule(multimedia) { SUBDIRS += audio } else { - message("QtMultimedia library not available. Some examples are disabled") + message("QtMultimedia library not available. Some examples are disabled.") +} + +contains(QT_CONFIG, opengl) { + SUBDIRS += openglseries +} else { + message("OpenGL not available. Some examples are disabled.") } !linux-arm*: { diff --git a/examples/charts/chartthemes/themewidget.cpp b/examples/charts/chartthemes/themewidget.cpp index 797356b7..a78ab9b4 100644 --- a/examples/charts/chartthemes/themewidget.cpp +++ b/examples/charts/chartthemes/themewidget.cpp @@ -189,7 +189,7 @@ QChart *ThemeWidget::createAreaChart() const for (int j(0); j < m_dataTable[i].count(); j++) { Data data = m_dataTable[i].at(j); if (lowerSeries) { - const QList<QPointF>& points = lowerSeries->points(); + const QVector<QPointF>& points = lowerSeries->pointsVector(); upperSeries->append(QPointF(j, points[i].y() + data.first.y())); } else { upperSeries->append(QPointF(j, data.first.y())); diff --git a/examples/charts/openglseries/datasource.cpp b/examples/charts/openglseries/datasource.cpp new file mode 100644 index 00000000..64b45405 --- /dev/null +++ b/examples/charts/openglseries/datasource.cpp @@ -0,0 +1,118 @@ +/**************************************************************************** +** +** 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 "datasource.h" +#include <QtCore/QtMath> + +QT_CHARTS_USE_NAMESPACE + +DataSource::DataSource(QObject *parent) : + QObject(parent), + m_index(-1) +{ + generateData(0, 0, 0); +} + +void DataSource::update(QAbstractSeries *series, int seriesIndex) +{ + if (series) { + QXYSeries *xySeries = static_cast<QXYSeries *>(series); + const QVector<QVector<QPointF> > &seriesData = m_data.at(seriesIndex); + if (seriesIndex == 0) + m_index++; + if (m_index > seriesData.count() - 1) + m_index = 0; + + QVector<QPointF> points = seriesData.at(m_index); + // Use replace instead of clear + append, it's optimized for performance + xySeries->replace(points); + } +} + +void DataSource::handleSceneChanged() +{ + m_dataUpdater.start(); +} + +void DataSource::updateAllSeries() +{ + static int frameCount = 0; + static QString labelText = QStringLiteral("FPS: %1"); + + for (int i = 0; i < m_seriesList.size(); i++) + update(m_seriesList[i], i); + + frameCount++; + int elapsed = m_fpsTimer.elapsed(); + if (elapsed >= 1000) { + elapsed = m_fpsTimer.restart(); + qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed)))); + m_fpsLabel->setText(labelText.arg(QString::number(fps, 'f', 1))); + frameCount = 0; + } +} + +void DataSource::startUpdates(const QList<QXYSeries *> &seriesList, QLabel *fpsLabel) +{ + m_seriesList = seriesList; + m_fpsLabel = fpsLabel; + + m_dataUpdater.setInterval(0); + m_dataUpdater.setSingleShot(true); + QObject::connect(&m_dataUpdater, &QTimer::timeout, + this, &DataSource::updateAllSeries); + + m_fpsTimer.start(); + updateAllSeries(); +} + +void DataSource::generateData(int seriesCount, int rowCount, int colCount) +{ + // Remove previous data + foreach (QVector<QVector<QPointF> > seriesData, m_data) { + foreach (QVector<QPointF> row, seriesData) + row.clear(); + } + + m_data.clear(); + + qreal xAdjustment = 20.0 / (colCount * rowCount); + qreal yMultiplier = 3.0 / qreal(seriesCount); + + // Append the new data depending on the type + for (int k(0); k < seriesCount; k++) { + QVector<QVector<QPointF> > seriesData; + qreal height = qreal(k) * (10.0 / qreal(seriesCount)) + 0.3; + for (int i(0); i < rowCount; i++) { + QVector<QPointF> points; + points.reserve(colCount); + for (int j(0); j < colCount; j++) { + qreal x(0); + qreal y(0); + // data with sin + random component + y = height + (yMultiplier * qSin(3.14159265358979 / 50 * j) + + (yMultiplier * (qreal) rand() / (qreal) RAND_MAX)); + // 0.000001 added to make values logaxis compatible + x = 0.000001 + 20.0 * (qreal(j) / qreal(colCount)) + (xAdjustment * qreal(i)); + points.append(QPointF(x, y)); + } + seriesData.append(points); + } + m_data.append(seriesData); + } +} diff --git a/examples/charts/openglseries/datasource.h b/examples/charts/openglseries/datasource.h new file mode 100644 index 00000000..295d93a3 --- /dev/null +++ b/examples/charts/openglseries/datasource.h @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** 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 DATASOURCE_H +#define DATASOURCE_H + +#include <QtCore/QObject> +#include <QtCharts/QXYSeries> +#include <QtWidgets/QLabel> +#include <QtCore/QElapsedTimer> +#include <QtCore/QTimer> + +QT_CHARTS_USE_NAMESPACE + +class DataSource : public QObject +{ + Q_OBJECT +public: + explicit DataSource(QObject *parent = 0); + + void startUpdates(const QList<QXYSeries *> &seriesList, QLabel *fpsLabel); + +public slots: + void generateData(int seriesCount, int rowCount, int colCount); + void update(QAbstractSeries *series, int seriesIndex); + void handleSceneChanged(); + void updateAllSeries(); + +private: + QVector<QVector<QVector<QPointF> > > m_data; + int m_index; + QList<QXYSeries *> m_seriesList; + QLabel *m_fpsLabel; + QElapsedTimer m_fpsTimer; + QTimer m_dataUpdater; +}; + +#endif // DATASOURCE_H diff --git a/examples/charts/openglseries/main.cpp b/examples/charts/openglseries/main.cpp new file mode 100644 index 00000000..7b654a5d --- /dev/null +++ b/examples/charts/openglseries/main.cpp @@ -0,0 +1,167 @@ +/**************************************************************************** +** +** 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 "datasource.h" +#include <QtWidgets/QApplication> +#include <QtWidgets/QMainWindow> +#include <QtCharts/QChartView> +#include <QtCharts/QLineSeries> +#include <QtCharts/QScatterSeries> +#include <QtCharts/QValueAxis> +#include <QtCharts/QLogValueAxis> +#include <QtWidgets/QLabel> + +// Uncomment to use logarithmic axes instead of regular value axes +//#define USE_LOG_AXIS + +// Uncomment to use regular series instead of OpenGL accelerated series +//#define DONT_USE_GL_SERIES + +// Uncomment to add a simple regular series (thick line) and a matching OpenGL series (thinner line) +// to verify the series have same visible geometry. +//#define ADD_SIMPLE_SERIES + +QT_CHARTS_USE_NAMESPACE + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + QStringList colors; + colors << "red" << "blue" << "green" << "black"; + + QChart *chart = new QChart(); + chart->legend()->hide(); + +#ifdef USE_LOG_AXIS + QLogValueAxis *axisX = new QLogValueAxis; + QLogValueAxis *axisY = new QLogValueAxis; +#else + QValueAxis *axisX = new QValueAxis; + QValueAxis *axisY = new QValueAxis; +#endif + + chart->addAxis(axisX, Qt::AlignBottom); + chart->addAxis(axisY, Qt::AlignLeft); + + const int seriesCount = 10; +#ifdef DONT_USE_GL_SERIES + const int pointCount = 100; + chart->setTitle("Unaccelerated Series"); +#else + const int pointCount = 10000; + chart->setTitle("OpenGL Accelerated Series"); +#endif + + QList<QXYSeries *> seriesList; + for (int i = 0; i < seriesCount; i++) { + QXYSeries *series = 0; + int colorIndex = i % colors.size(); + if (i % 2) { + series = new QScatterSeries; + QScatterSeries *scatter = static_cast<QScatterSeries *>(series); + scatter->setColor(QColor(colors.at(colorIndex))); + scatter->setMarkerSize(qreal(colorIndex + 2) / 2.0); + // Scatter pen doesn't have affect in OpenGL drawing, but if you disable OpenGL drawing + // this makes the marker border visible and gives comparable marker size to OpenGL + // scatter points. + scatter->setPen(QPen("black")); + } else { + series = new QLineSeries; + series->setPen(QPen(QBrush(QColor(colors.at(colorIndex))), + qreal(colorIndex + 2) / 2.0)); + } + seriesList.append(series); +#ifdef DONT_USE_GL_SERIES + series->setUseOpenGL(false); +#else + //![1] + series->setUseOpenGL(true); + //![1] +#endif + chart->addSeries(series); + series->attachAxis(axisX); + series->attachAxis(axisY); + } + + if (axisX->type() == QAbstractAxis::AxisTypeLogValue) + axisX->setRange(0.1, 20.0); + else + axisX->setRange(0, 20.0); + + if (axisY->type() == QAbstractAxis::AxisTypeLogValue) + axisY->setRange(0.1, 10.0); + else + axisY->setRange(0, 10.0); + +#ifdef ADD_SIMPLE_SERIES + QLineSeries *simpleRasterSeries = new QLineSeries; + *simpleRasterSeries << QPointF(0.001, 0.001) + << QPointF(2.5, 8.0) + << QPointF(5.0, 4.0) + << QPointF(7.5, 9.0) + << QPointF(10.0, 0.001) + << QPointF(12.5, 2.0) + << QPointF(15.0, 1.0) + << QPointF(17.5, 6.0) + << QPointF(20.0, 10.0); + simpleRasterSeries->setUseOpenGL(false); + simpleRasterSeries->setPen(QPen(QBrush("magenta"), 8)); + chart->addSeries(simpleRasterSeries); + simpleRasterSeries->attachAxis(axisX); + simpleRasterSeries->attachAxis(axisY); + + QLineSeries *simpleGLSeries = new QLineSeries; + simpleGLSeries->setUseOpenGL(true); + simpleGLSeries->setPen(QPen(QBrush("black"), 2)); + simpleGLSeries->replace(simpleRasterSeries->points()); + chart->addSeries(simpleGLSeries); + simpleGLSeries->attachAxis(axisX); + simpleGLSeries->attachAxis(axisY); +#endif + + QChartView *chartView = new QChartView(chart); + + QMainWindow window; + window.setCentralWidget(chartView); + window.resize(600, 400); + window.show(); + + DataSource dataSource; + dataSource.generateData(seriesCount, 10, pointCount); + + QLabel *fpsLabel = new QLabel(&window); + QLabel *countLabel = new QLabel(&window); + QString countText = QStringLiteral("Total point count: %1"); + countLabel->setText(countText.arg(pointCount * seriesCount)); + countLabel->resize(window.width(), countLabel->height()); + fpsLabel->move(10,2); + fpsLabel->raise(); + fpsLabel->show(); + countLabel->move(10, 14); + fpsLabel->raise(); + countLabel->show(); + + // We can get more than one changed event per frame, so do async update. + // This also allows the application to be responsive. + QObject::connect(chart->scene(), &QGraphicsScene::changed, + &dataSource, &DataSource::handleSceneChanged); + + dataSource.startUpdates(seriesList, fpsLabel); + + return a.exec(); +} diff --git a/examples/charts/openglseries/openglseries.pro b/examples/charts/openglseries/openglseries.pro new file mode 100644 index 00000000..0ae11595 --- /dev/null +++ b/examples/charts/openglseries/openglseries.pro @@ -0,0 +1,9 @@ +!include( ../examples.pri ) { + error( "Couldn't find the examples.pri file!" ) +} + +TARGET = openglseries +SOURCES += main.cpp \ + datasource.cpp +HEADERS += datasource.h + diff --git a/examples/charts/qmloscilloscope/qml/qmloscilloscope/ControlPanel.qml b/examples/charts/qmloscilloscope/qml/qmloscilloscope/ControlPanel.qml index 478a2cc3..ccf53fa1 100644 --- a/examples/charts/qmloscilloscope/qml/qmloscilloscope/ControlPanel.qml +++ b/examples/charts/qmloscilloscope/qml/qmloscilloscope/ControlPanel.qml @@ -16,10 +16,11 @@ ** ****************************************************************************/ -import QtQuick 2.0 +import QtQuick 2.1 import QtQuick.Layouts 1.0 ColumnLayout { + property alias openGLButton: openGLButton spacing: 8 Layout.fillHeight: true signal animationsEnabled(bool enabled) @@ -27,6 +28,7 @@ ColumnLayout { signal refreshRateChanged(variant rate); signal signalSourceChanged(string source, int signalCount, int sampleCount); signal antialiasingEnabled(bool enabled) + signal openGlChanged(bool enabled) Text { text: "Scope" @@ -35,6 +37,14 @@ ColumnLayout { } MultiButton { + id: openGLButton + text: "OpenGL: " + items: ["false", "true"] + currentSelection: 0 + onSelectionChanged: openGlChanged(currentSelection == 1); + } + + MultiButton { text: "Graph: " items: ["line", "spline", "scatter"] currentSelection: 0 diff --git a/examples/charts/qmloscilloscope/qml/qmloscilloscope/ScopeView.qml b/examples/charts/qmloscilloscope/qml/qmloscilloscope/ScopeView.qml index e422d056..443e515a 100644 --- a/examples/charts/qmloscilloscope/qml/qmloscilloscope/ScopeView.qml +++ b/examples/charts/qmloscilloscope/qml/qmloscilloscope/ScopeView.qml @@ -17,13 +17,18 @@ ****************************************************************************/ import QtQuick 2.0 -import QtCharts 2.0 +import QtCharts 2.1 //![1] ChartView { id: chartView animationOptions: ChartView.NoAnimation theme: ChartView.ChartThemeDark + property bool openGL: false + onOpenGLChanged: { + series("signal 1").useOpenGL = openGL; + series("signal 2").useOpenGL = openGL; + } ValueAxis { id: axisY1 @@ -40,7 +45,7 @@ ChartView { ValueAxis { id: axisX min: 0 - max: 1000 + max: 1024 } LineSeries { @@ -48,12 +53,14 @@ ChartView { name: "signal 1" axisX: axisX axisY: axisY1 + useOpenGL: chartView.openGL } LineSeries { id: lineSeries2 name: "signal 2" axisX: axisX axisYRight: axisY2 + useOpenGL: chartView.openGL } //![1] @@ -78,18 +85,28 @@ ChartView { // but the series have their own y-axes to make it possible to control the y-offset // of the "signal sources". if (type == "line") { - chartView.createSeries(ChartView.SeriesTypeLine, "signal 1", axisX, axisY1); - chartView.createSeries(ChartView.SeriesTypeLine, "signal 2", axisX, axisY2); + var series1 = chartView.createSeries(ChartView.SeriesTypeLine, "signal 1", + axisX, axisY1); + series1.useOpenGL = chartView.openGL + + var series2 = chartView.createSeries(ChartView.SeriesTypeLine, "signal 2", + axisX, axisY2); + series2.useOpenGL = chartView.openGL } else if (type == "spline") { chartView.createSeries(ChartView.SeriesTypeSpline, "signal 1", axisX, axisY1); chartView.createSeries(ChartView.SeriesTypeSpline, "signal 2", axisX, axisY2); } else { - var series1 = chartView.createSeries(ChartView.SeriesTypeScatter, "signal 1", axisX, axisY1); + var series1 = chartView.createSeries(ChartView.SeriesTypeScatter, "signal 1", + axisX, axisY1); series1.markerSize = 3; series1.borderColor = "transparent"; - var series2 = chartView.createSeries(ChartView.SeriesTypeScatter, "signal 2", axisX, axisY2); + series1.useOpenGL = chartView.openGL + + var series2 = chartView.createSeries(ChartView.SeriesTypeScatter, "signal 2", + axisX, axisY2); series2.markerSize = 3; series2.borderColor = "transparent"; + series2.useOpenGL = chartView.openGL } } diff --git a/examples/charts/qmloscilloscope/qml/qmloscilloscope/main.qml b/examples/charts/qmloscilloscope/qml/qmloscilloscope/main.qml index 0ff40c21..5340177e 100644 --- a/examples/charts/qmloscilloscope/qml/qmloscilloscope/main.qml +++ b/examples/charts/qmloscilloscope/qml/qmloscilloscope/main.qml @@ -21,8 +21,8 @@ import QtQuick 2.0 //![1] Rectangle { id: main - width: 400 - height: 300 + width: 600 + height: 400 color: "#404040" ControlPanel { @@ -39,11 +39,22 @@ Rectangle { dataSource.generateData(0, signalCount, sampleCount); else dataSource.generateData(1, signalCount, sampleCount); + scopeView.axisX().max = sampleCount; } onAnimationsEnabled: scopeView.setAnimations(enabled); - onSeriesTypeChanged: scopeView.changeSeriesType(type); + onSeriesTypeChanged: { + scopeView.changeSeriesType(type); + if (type === "spline") { + controlPanel.openGLButton.currentSelection = 0; + controlPanel.openGLButton.enabled = false; + scopeView.openGL = false; + } else { + controlPanel.openGLButton.enabled = true; + } + } onRefreshRateChanged: scopeView.changeRefreshRate(rate); onAntialiasingEnabled: scopeView.antialiasing = enabled; + onOpenGlChanged: scopeView.openGL = enabled; } //![2] @@ -56,4 +67,5 @@ Rectangle { height: main.height } //![2] + } 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(); diff --git a/src/chartsqml2/chartsqml2.pro b/src/chartsqml2/chartsqml2.pro index cc544d3a..4dd82950 100644 --- a/src/chartsqml2/chartsqml2.pro +++ b/src/chartsqml2/chartsqml2.pro @@ -33,7 +33,9 @@ SOURCES += \ declarativemargins.cpp \ declarativeaxes.cpp \ declarativepolarchart.cpp \ - declarativeboxplotseries.cpp + declarativeboxplotseries.cpp \ + declarativechartnode.cpp \ + declarativerendernode.cpp HEADERS += \ declarativechart.h \ @@ -49,7 +51,9 @@ HEADERS += \ declarativemargins.h \ declarativeaxes.h \ declarativepolarchart.h \ - declarativeboxplotseries.h + declarativeboxplotseries.h \ + declarativechartnode.h \ + declarativerendernode.h OTHER_FILES = qmldir diff --git a/src/chartsqml2/declarativechart.cpp b/src/chartsqml2/declarativechart.cpp index a8910f43..c028b3d8 100644 --- a/src/chartsqml2/declarativechart.cpp +++ b/src/chartsqml2/declarativechart.cpp @@ -25,6 +25,8 @@ #include "declarativesplineseries.h" #include "declarativeboxplotseries.h" #include "declarativescatterseries.h" +#include "declarativechartnode.h" +#include "declarativerendernode.h" #include <QtCharts/QBarCategoryAxis> #include <QtCharts/QValueAxis> #include <QtCharts/QLogValueAxis> @@ -34,6 +36,7 @@ #include <private/chartdataset_p.h> #include "declarativeaxes.h" #include <private/qchart_p.h> +#include <private/chartpresenter_p.h> #include <QtCharts/QPolarChart> #ifndef QT_ON_ARM @@ -88,6 +91,7 @@ QT_CHARTS_BEGIN_NAMESPACE /*! \qmlproperty easing ChartView::animationEasingCurve The easing curve of the animation for the chart. +*/ /*! \qmlproperty Font ChartView::titleFont @@ -312,34 +316,42 @@ QT_CHARTS_BEGIN_NAMESPACE */ DeclarativeChart::DeclarativeChart(QQuickItem *parent) - : QQuickPaintedItem(parent) + : QQuickItem(parent) { initChart(QChart::ChartTypeCartesian); } DeclarativeChart::DeclarativeChart(QChart::ChartType type, QQuickItem *parent) - : QQuickPaintedItem(parent) + : QQuickItem(parent) { initChart(type); } void DeclarativeChart::initChart(QChart::ChartType type) { - m_currentSceneImage = 0; + m_sceneImage = 0; + m_sceneImageDirty = false; m_guiThreadId = QThread::currentThreadId(); m_paintThreadId = 0; m_updatePending = false; + setFlag(ItemHasContents, true); + if (type == QChart::ChartTypePolar) m_chart = new QPolarChart(); else m_chart = new QChart(); + m_chart->d_ptr->m_presenter->glSetUseWidget(false); + m_glXYDataManager = m_chart->d_ptr->m_dataset->glXYSeriesDataManager(); + m_scene = new QGraphicsScene(this); m_scene->addItem(m_chart); setAntialiasing(QQuickItem::antialiasing()); - connect(m_scene, SIGNAL(changed(QList<QRectF>)), this, SLOT(sceneChanged(QList<QRectF>))); + connect(m_scene, &QGraphicsScene::changed, this, &DeclarativeChart::sceneChanged); + connect(this, &DeclarativeChart::needRender, this, &DeclarativeChart::renderScene, + Qt::QueuedConnection); connect(this, SIGNAL(antialiasingChanged(bool)), this, SLOT(handleAntialiasingChanged(bool))); setAcceptedMouseButtons(Qt::AllButtons); @@ -377,10 +389,7 @@ void DeclarativeChart::changeMargins(int top, int bottom, int left, int right) DeclarativeChart::~DeclarativeChart() { delete m_chart; - m_sceneImageLock.lock(); - delete m_currentSceneImage; - m_currentSceneImage = 0; - m_sceneImageLock.unlock(); + delete m_sceneImage; } void DeclarativeChart::childEvent(QChildEvent *event) @@ -493,19 +502,79 @@ void DeclarativeChart::geometryChanged(const QRectF &newGeometry, const QRectF & QQuickItem::geometryChanged(newGeometry, oldGeometry); } -void DeclarativeChart::sceneChanged(QList<QRectF> region) +QSGNode *DeclarativeChart::updatePaintNode(QSGNode *oldNode, QQuickItem::UpdatePaintNodeData *) { - Q_UNUSED(region); + DeclarativeChartNode *node = static_cast<DeclarativeChartNode *>(oldNode); - if (m_guiThreadId == m_paintThreadId) { - // Rendering in gui thread, no need for shenannigans, just update - update(); - } else { - // Multi-threaded rendering, need to ensure scene is actually rendered in gui thread - if (!m_updatePending) { + if (!node) { + node = new DeclarativeChartNode(window()); + connect(window(), &QQuickWindow::beforeRendering, + node->glRenderNode(), &DeclarativeRenderNode::render); + } + + const QRectF &bRect = boundingRect(); + + // Update GL data + if (m_glXYDataManager->dataMap().size() || m_glXYDataManager->mapDirty()) { + const QRectF &plotArea = m_chart->plotArea(); + const QSizeF &chartAreaSize = m_chart->size(); + + // We can't use chart's plot area directly, as graphicscene has some internal minimum size + const qreal normalizedX = plotArea.x() / chartAreaSize.width(); + const qreal normalizedY = plotArea.y() / chartAreaSize.height(); + const qreal normalizedWidth = plotArea.width() / chartAreaSize.width(); + const qreal normalizedHeight = plotArea.height() / chartAreaSize.height(); + + QRectF adjustedPlotArea(normalizedX * bRect.width(), + normalizedY * bRect.height(), + normalizedWidth * bRect.width(), + normalizedHeight * bRect.height()); + + const QSize &adjustedPlotSize = adjustedPlotArea.size().toSize(); + if (adjustedPlotSize != node->glRenderNode()->textureSize()) + node->glRenderNode()->setTextureSize(adjustedPlotSize); + + node->glRenderNode()->setRect(adjustedPlotArea); + node->glRenderNode()->setSeriesData(m_glXYDataManager->mapDirty(), + m_glXYDataManager->dataMap()); + + // Clear dirty flags from original xy data + m_glXYDataManager->clearAllDirty(); + } + + // Copy chart (if dirty) to chart node + if (m_sceneImageDirty) { + node->createTextureFromImage(*m_sceneImage); + m_sceneImageDirty = false; + } + + node->setRect(bRect); + + return node; +} + +void DeclarativeChart::sceneChanged(QList<QRectF> region) +{ + const int count = region.size(); + const qreal limitSize = 0.01; + if (count && !m_updatePending) { + qreal totalSize = 0.0; + for (int i = 0; i < count; i++) { + const QRectF ® = region.at(i); + totalSize += (reg.height() * reg.width()); + if (totalSize >= limitSize) + break; + } + // Ignore region updates that change less than small fraction of a pixel, as there is + // little point regenerating the image in these cases. These are typically cases + // where OpenGL series are drawn to otherwise static chart. + if (totalSize >= limitSize) { m_updatePending = true; // Do async render to avoid some unnecessary renders. - QTimer::singleShot(0, this, SLOT(renderScene())); + emit needRender(); + } else { + // We do want to call update to trigger possible gl series updates. + update(); } } } @@ -513,45 +582,22 @@ void DeclarativeChart::sceneChanged(QList<QRectF> region) void DeclarativeChart::renderScene() { m_updatePending = false; - m_sceneImageLock.lock(); - delete m_currentSceneImage; - m_currentSceneImage = new QImage(m_chart->size().toSize(), QImage::Format_ARGB32); - m_currentSceneImage->fill(Qt::transparent); - QPainter painter(m_currentSceneImage); - if (antialiasing()) - painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing | QPainter::SmoothPixmapTransform); - QRect renderRect(QPoint(0, 0), m_chart->size().toSize()); - m_scene->render(&painter, renderRect, renderRect); - m_sceneImageLock.unlock(); - - update(); -} - -void DeclarativeChart::paint(QPainter *painter) -{ - if (!m_paintThreadId) { - m_paintThreadId = QThread::currentThreadId(); - if (m_guiThreadId == m_paintThreadId) { - // No need for scene image in single threaded rendering, so delete - // the one that got made by default before the rendering type was - // detected. - delete m_currentSceneImage; - m_currentSceneImage = 0; - } + m_sceneImageDirty = true; + QSize chartSize = m_chart->size().toSize(); + if (!m_sceneImage || chartSize != m_sceneImage->size()) { + delete m_sceneImage; + m_sceneImage = new QImage(chartSize, QImage::Format_ARGB32); + m_sceneImage->fill(Qt::transparent); } - if (m_guiThreadId == m_paintThreadId) { - QRectF renderRect(QPointF(0, 0), m_chart->size()); - m_scene->render(painter, renderRect, renderRect); - } else { - m_sceneImageLock.lock(); - if (m_currentSceneImage) { - QRect imageRect(QPoint(0, 0), m_currentSceneImage->size()); - QRect itemRect(QPoint(0, 0), QSize(width(), height())); - painter->drawImage(itemRect, *m_currentSceneImage, imageRect); - } - m_sceneImageLock.unlock(); + QPainter painter(m_sceneImage); + if (antialiasing()) { + painter.setRenderHints(QPainter::Antialiasing | QPainter::TextAntialiasing + | QPainter::SmoothPixmapTransform); } + QRect renderRect(QPoint(0, 0), chartSize); + m_scene->render(&painter, renderRect, renderRect); + update(); } void DeclarativeChart::mousePressEvent(QMouseEvent *event) diff --git a/src/chartsqml2/declarativechart.h b/src/chartsqml2/declarativechart.h index 80505d4d..c5d06b8d 100644 --- a/src/chartsqml2/declarativechart.h +++ b/src/chartsqml2/declarativechart.h @@ -19,11 +19,11 @@ #ifndef DECLARATIVECHART_H #define DECLARATIVECHART_H +#include <private/glxyseriesdata_p.h> + #include <QtCore/QtGlobal> #include <QtQuick/QQuickItem> -#include <QtQuick/QQuickPaintedItem> #include <QtWidgets/QGraphicsScene> -#include <QtCore/QMutex> #include <QtCharts/QChart> #include <QtCore/QLocale> @@ -34,7 +34,7 @@ class DeclarativeMargins; class Domain; class DeclarativeAxes; -class DeclarativeChart : public QQuickPaintedItem +class DeclarativeChart : public QQuickItem { Q_OBJECT Q_PROPERTY(Theme theme READ theme WRITE setTheme) @@ -102,7 +102,7 @@ public: // From parent classes void childEvent(QChildEvent *event); void componentComplete(); void geometryChanged(const QRectF &newGeometry, const QRectF &oldGeometry); - void paint(QPainter *painter); + QSGNode *updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *); protected: void mousePressEvent(QMouseEvent *event); void mouseReleaseEvent(QMouseEvent *event); @@ -199,6 +199,7 @@ Q_SIGNALS: Q_REVISION(4) void localeChanged(); Q_REVISION(5) void animationDurationChanged(int msecs); Q_REVISION(5) void animationEasingCurveChanged(QEasingCurve curve); + void needRender(); private Q_SLOTS: void changeMargins(int top, int bottom, int left, int right); @@ -227,12 +228,13 @@ private: QPoint m_lastMouseMoveScreenPoint; Qt::MouseButton m_mousePressButton; Qt::MouseButtons m_mousePressButtons; - QMutex m_sceneImageLock; - QImage *m_currentSceneImage; + QImage *m_sceneImage; + bool m_sceneImageDirty; bool m_updatePending; Qt::HANDLE m_paintThreadId; Qt::HANDLE m_guiThreadId; DeclarativeMargins *m_margins; + GLXYSeriesDataManager *m_glXYDataManager; }; QT_CHARTS_END_NAMESPACE diff --git a/src/chartsqml2/declarativechartnode.cpp b/src/chartsqml2/declarativechartnode.cpp new file mode 100644 index 00000000..4e32344b --- /dev/null +++ b/src/chartsqml2/declarativechartnode.cpp @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 "declarativechartnode.h" +#include "declarativerendernode.h" +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QOpenGLFramebufferObjectFormat> +#include <QtGui/QOpenGLFramebufferObject> +#include <QOpenGLShaderProgram> +#include <QtGui/QOpenGLBuffer> + +QT_CHARTS_BEGIN_NAMESPACE + +// This node handles displaying of the chart itself +DeclarativeChartNode::DeclarativeChartNode(QQuickWindow *window) : + QSGSimpleTextureNode(), + m_texture(0), + m_window(window), + m_textureOptions(0), + m_textureSize(1, 1), + m_glRenderNode(0) +{ + initializeOpenGLFunctions(); + + // Our texture node must have a texture, so use a default one pixel texture + GLuint defaultTexture = 0; + glGenTextures(1, &defaultTexture); + glBindTexture(GL_TEXTURE_2D, defaultTexture); + uchar buf[4] = { 0, 0, 0, 0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &buf); + + QQuickWindow::CreateTextureOptions defaultTextureOptions = QQuickWindow::CreateTextureOptions( + QQuickWindow::TextureHasAlphaChannel | QQuickWindow::TextureOwnsGLTexture); + m_texture = m_window->createTextureFromId(defaultTexture, QSize(1, 1), defaultTextureOptions); + + setTexture(m_texture); + setFiltering(QSGTexture::Linear); + + // Create child node for rendering GL graphics + m_glRenderNode = new DeclarativeRenderNode(m_window); + m_glRenderNode->setFlag(OwnedByParent); + appendChildNode(m_glRenderNode); + m_glRenderNode->setRect(0, 0, 0, 0); // Hide child node by default +} + +DeclarativeChartNode::~DeclarativeChartNode() +{ + delete m_texture; +} + +// Must be called on render thread and in context +void DeclarativeChartNode::createTextureFromImage(const QImage &chartImage) +{ + if (chartImage.size() != m_textureSize) + m_textureSize = chartImage.size(); + + delete m_texture; + m_texture = m_window->createTextureFromImage(chartImage, m_textureOptions); + setTexture(m_texture); +} + +QT_CHARTS_END_NAMESPACE diff --git a/src/chartsqml2/declarativechartnode.h b/src/chartsqml2/declarativechartnode.h new file mode 100644 index 00000000..46936c8c --- /dev/null +++ b/src/chartsqml2/declarativechartnode.h @@ -0,0 +1,50 @@ +/**************************************************************************** +** +** 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 DECLARATIVECHARTNODE_P_H +#define DECLARATIVECHARTNODE_P_H + +#include <QtCharts/QChartGlobal> +#include <QtQuick/QSGSimpleTextureNode> +#include <QtQuick/QQuickWindow> +#include <QtGui/QOpenGLFunctions> + +QT_CHARTS_BEGIN_NAMESPACE + +class DeclarativeRenderNode; + +class DeclarativeChartNode : public QSGSimpleTextureNode, QOpenGLFunctions +{ +public: + DeclarativeChartNode(QQuickWindow *window); + ~DeclarativeChartNode(); + + void createTextureFromImage(const QImage &chartImage); + DeclarativeRenderNode *glRenderNode() const { return m_glRenderNode; } + +private: + QSGTexture *m_texture; + QQuickWindow *m_window; + QQuickWindow::CreateTextureOptions m_textureOptions; + QSize m_textureSize; + DeclarativeRenderNode *m_glRenderNode; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // DECLARATIVECHARTNODE_P_H diff --git a/src/chartsqml2/declarativerendernode.cpp b/src/chartsqml2/declarativerendernode.cpp new file mode 100644 index 00000000..b271dcc4 --- /dev/null +++ b/src/chartsqml2/declarativerendernode.cpp @@ -0,0 +1,317 @@ +/**************************************************************************** +** +** 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 "declarativerendernode.h" + +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QOpenGLFramebufferObjectFormat> +#include <QtGui/QOpenGLFramebufferObject> +#include <QOpenGLShaderProgram> +#include <QtGui/QOpenGLBuffer> + +//#define QDEBUG_TRACE_GL_FPS +#ifdef QDEBUG_TRACE_GL_FPS +# include <QElapsedTimer> +#endif + +QT_CHARTS_BEGIN_NAMESPACE + +// This node draws the xy series data on a transparent background using OpenGL. +// It is used as a child node of the chart node. +DeclarativeRenderNode::DeclarativeRenderNode(QQuickWindow *window) : + QObject(), + QSGSimpleTextureNode(), + m_texture(0), + m_window(window), + m_textureOptions(QQuickWindow::TextureHasAlphaChannel), + m_textureSize(1, 1), + m_recreateFbo(false), + m_fbo(0), + m_program(0), + m_shaderAttribLoc(-1), + m_colorUniformLoc(-1), + m_minUniformLoc(-1), + m_deltaUniformLoc(-1), + m_pointSizeUniformLoc(-1), + m_renderNeeded(true) +{ + initializeOpenGLFunctions(); + + // Our texture node must have a texture, so use a default one pixel texture + GLuint defaultTexture = 0; + glGenTextures(1, &defaultTexture); + glBindTexture(GL_TEXTURE_2D, defaultTexture); + uchar buf[4] = { 0, 0, 0, 0 }; + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1, 1, 0, GL_RGBA, GL_UNSIGNED_BYTE, &buf); + + QQuickWindow::CreateTextureOptions defaultTextureOptions = QQuickWindow::CreateTextureOptions( + QQuickWindow::TextureHasAlphaChannel | QQuickWindow::TextureOwnsGLTexture); + m_texture = m_window->createTextureFromId(defaultTexture, QSize(1, 1), defaultTextureOptions); + + setTexture(m_texture); + setFiltering(QSGTexture::Linear); + setTextureCoordinatesTransform(QSGSimpleTextureNode::MirrorVertically); +} + +DeclarativeRenderNode::~DeclarativeRenderNode() +{ + delete m_texture; + delete m_fbo; + + delete m_program; + m_program = 0; + + cleanXYSeriesResources(0); +} + +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"; + +// Must be called on render thread and in context +void DeclarativeRenderNode::initGL() +{ + recreateFBO(); + + 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); + +#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. + // Qt Quick doesn't change these flags, so it should be safe to just enable them + // at initialization. + glEnable(GL_PROGRAM_POINT_SIZE); + } +#endif + + m_program->release(); +} + +void DeclarativeRenderNode::recreateFBO() +{ + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment); + delete m_fbo; + m_fbo = new QOpenGLFramebufferObject(m_textureSize.width(), + m_textureSize.height(), + fboFormat); + + delete m_texture; + m_texture = m_window->createTextureFromId(m_fbo->texture(), m_textureSize, m_textureOptions); + setTexture(m_texture); + + m_recreateFbo = false; +} + +// Must be called on render thread and in context +void DeclarativeRenderNode::setTextureSize(const QSize &size) +{ + m_textureSize = size; + m_recreateFbo = true; + m_renderNeeded = true; +} + +// Must be called on render thread while gui thread is blocked, and in context +void DeclarativeRenderNode::setSeriesData(bool mapDirty, const GLXYDataMap &dataMap) +{ + if (mapDirty) { + // Series have changed, recreate map, but utilize old data where feasible + GLXYDataMap oldMap = m_xyDataMap; + m_xyDataMap.clear(); + + GLXYDataMapIterator i(dataMap); + while (i.hasNext()) { + i.next(); + GLXYSeriesData *data = oldMap.take(i.key()); + const GLXYSeriesData *newData = i.value(); + if (!data || newData->dirty) { + data = new GLXYSeriesData; + data->array = newData->array; + data->color = newData->color; + data->dirty = newData->dirty; + data->width = newData->width; + data->type = newData->type; + data->min = newData->min; + data->delta = newData->delta; + } + m_xyDataMap.insert(i.key(), data); + } + // Delete remaining old data + i = oldMap; + while (i.hasNext()) { + i.next(); + delete i.value(); + cleanXYSeriesResources(i.key()); + } + } else { + // Series have not changed, so just copy dirty data over + GLXYDataMapIterator i(dataMap); + while (i.hasNext()) { + i.next(); + const GLXYSeriesData *newData = i.value(); + if (i.value()->dirty) { + GLXYSeriesData *data = m_xyDataMap.value(i.key()); + if (data) { + data->array = newData->array; + data->color = newData->color; + data->dirty = newData->dirty; + data->width = newData->width; + data->type = newData->type; + data->min = newData->min; + data->delta = newData->delta; + } + } + } + } + markDirty(DirtyMaterial); + m_renderNeeded = true; +} + +void DeclarativeRenderNode::renderGL() +{ + glClearColor(0, 0, 0, 0); + + QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao); + m_program->bind(); + m_fbo->bind(); + + glClear(GL_COLOR_BUFFER_BIT); + glEnableVertexAttribArray(0); + + glViewport(0, 0, m_textureSize.width(), m_textureSize.height()); + + GLXYDataMapIterator i(m_xyDataMap); + 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 + + markDirty(DirtyMaterial); + m_window->resetOpenGLState(); +} + +// Must be called on render thread as response to beforeRendering signal +void DeclarativeRenderNode::render() +{ + if (m_renderNeeded) { + if (m_xyDataMap.size()) { + if (!m_program) + initGL(); + if (m_recreateFbo) + recreateFBO(); + renderGL(); + } else { + if (rect() != QRectF()) { + glClearColor(0, 0, 0, 0); + m_fbo->bind(); + glClear(GL_COLOR_BUFFER_BIT); + + // If last series was removed, zero out the node rect + setRect(QRectF()); + } + } + m_renderNeeded = false; + } +} + +void DeclarativeRenderNode::cleanXYSeriesResources(const QXYSeries *series) +{ + if (series) { + delete m_seriesBufferMap.take(series); + } else { + foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values()) + delete buffer; + m_seriesBufferMap.clear(); + } +} + +QT_CHARTS_END_NAMESPACE diff --git a/src/chartsqml2/declarativerendernode.h b/src/chartsqml2/declarativerendernode.h new file mode 100644 index 00000000..f00f5c44 --- /dev/null +++ b/src/chartsqml2/declarativerendernode.h @@ -0,0 +1,74 @@ +/**************************************************************************** +** +** 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 DECLARATIVERENDERNODE_P_H +#define DECLARATIVERENDERNODE_P_H + +#include <QtCharts/QChartGlobal> +#include <private/glxyseriesdata_p.h> +#include <QtQuick/QSGSimpleTextureNode> +#include <QtQuick/QQuickWindow> +#include <QtGui/QOpenGLVertexArrayObject> +#include <QtGui/QOpenGLFunctions> + +class QOpenGLFramebufferObject; +class QOpenGLBuffer; + +QT_CHARTS_BEGIN_NAMESPACE + +class DeclarativeRenderNode : public QObject, public QSGSimpleTextureNode, QOpenGLFunctions +{ + Q_OBJECT +public: + DeclarativeRenderNode(QQuickWindow *window); + ~DeclarativeRenderNode(); + + void initGL(); + QSize textureSize() const { return m_textureSize; } + void setTextureSize(const QSize &size); + void setSeriesData(bool mapDirty, const GLXYDataMap &dataMap); + +public Q_SLOTS: + void render(); + +private: + void renderGL(); + void recreateFBO(); + void cleanXYSeriesResources(const QXYSeries *series); + + QSGTexture *m_texture; + QQuickWindow *m_window; + QQuickWindow::CreateTextureOptions m_textureOptions; + QSize m_textureSize; + bool m_recreateFbo; + GLXYDataMap m_xyDataMap; + QOpenGLFramebufferObject *m_fbo; + 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; + bool m_renderNeeded; +}; + +QT_CHARTS_END_NAMESPACE + +#endif // DECLARATIVERENDERNODE_P_H diff --git a/tests/auto/qlineseries/tst_qlineseries.cpp b/tests/auto/qlineseries/tst_qlineseries.cpp index e1df6c78..1b7a57b6 100644 --- a/tests/auto/qlineseries/tst_qlineseries.cpp +++ b/tests/auto/qlineseries/tst_qlineseries.cpp @@ -19,8 +19,8 @@ #include "../qxyseries/tst_qxyseries.h" #include <QtCharts/QLineSeries> - Q_DECLARE_METATYPE(QList<QPointF>) +Q_DECLARE_METATYPE(QVector<QPointF>) class tst_QLineSeries : public tst_QXYSeries { @@ -76,6 +76,7 @@ void tst_QLineSeries::qlineseries() QCOMPARE(series.count(),0); QCOMPARE(series.brush(), QBrush()); QCOMPARE(series.points(), QList<QPointF>()); + QCOMPARE(series.pointsVector(), QVector<QPointF>()); QCOMPARE(series.pen(), QPen()); QCOMPARE(series.pointsVisible(), false); QCOMPARE(series.pointLabelsVisible(), false); diff --git a/tests/auto/qscatterseries/tst_qscatterseries.cpp b/tests/auto/qscatterseries/tst_qscatterseries.cpp index 588a6549..eedb4c3e 100644 --- a/tests/auto/qscatterseries/tst_qscatterseries.cpp +++ b/tests/auto/qscatterseries/tst_qscatterseries.cpp @@ -20,6 +20,7 @@ #include <QtCharts/QScatterSeries> Q_DECLARE_METATYPE(QList<QPointF>) +Q_DECLARE_METATYPE(QVector<QPointF>) class tst_QScatterSeries : public tst_QXYSeries { @@ -75,6 +76,7 @@ void tst_QScatterSeries::qscatterseries() QCOMPARE(series.count(),0); QCOMPARE(series.brush(), QBrush()); QCOMPARE(series.points(), QList<QPointF>()); + QCOMPARE(series.pointsVector(), QVector<QPointF>()); QCOMPARE(series.pen(), QPen()); QCOMPARE(series.pointsVisible(), false); diff --git a/tests/auto/qsplineseries/tst_qsplineseries.cpp b/tests/auto/qsplineseries/tst_qsplineseries.cpp index 53ddf011..4f023088 100644 --- a/tests/auto/qsplineseries/tst_qsplineseries.cpp +++ b/tests/auto/qsplineseries/tst_qsplineseries.cpp @@ -20,6 +20,7 @@ #include <QtCharts/QSplineSeries> Q_DECLARE_METATYPE(QList<QPointF>) +Q_DECLARE_METATYPE(QVector<QPointF>) class tst_QSplineSeries : public tst_QXYSeries { @@ -73,6 +74,7 @@ void tst_QSplineSeries::qsplineseries() QCOMPARE(series.count(),0); QCOMPARE(series.brush(), QBrush()); QCOMPARE(series.points(), QList<QPointF>()); + QCOMPARE(series.pointsVector(), QVector<QPointF>()); QCOMPARE(series.pen(), QPen()); QCOMPARE(series.pointsVisible(), false); diff --git a/tests/auto/qxyseries/tst_qxyseries.cpp b/tests/auto/qxyseries/tst_qxyseries.cpp index bb827412..762c7a09 100644 --- a/tests/auto/qxyseries/tst_qxyseries.cpp +++ b/tests/auto/qxyseries/tst_qxyseries.cpp @@ -198,6 +198,7 @@ void tst_QXYSeries::append_raw() TRY_COMPARE(spy0.count(), 0); TRY_COMPARE(addedSpy.count(), points.count()); QCOMPARE(m_series->points(), points); + QCOMPARE(m_series->pointsVector(), points.toVector()); // Process events between appends foreach (const QPointF &point, otherPoints) { |