summaryrefslogtreecommitdiffstats
path: root/src/chartsqml2/declarativeopenglrendernode.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/chartsqml2/declarativeopenglrendernode.cpp')
-rw-r--r--src/chartsqml2/declarativeopenglrendernode.cpp327
1 files changed, 327 insertions, 0 deletions
diff --git a/src/chartsqml2/declarativeopenglrendernode.cpp b/src/chartsqml2/declarativeopenglrendernode.cpp
new file mode 100644
index 00000000..0799e36c
--- /dev/null
+++ b/src/chartsqml2/declarativeopenglrendernode.cpp
@@ -0,0 +1,327 @@
+/****************************************************************************
+**
+** Copyright (C) 2016 The Qt Company Ltd.
+** Contact: https://www.qt.io/licensing/
+**
+** This file is part of the Qt Charts module of the Qt Toolkit.
+**
+** $QT_BEGIN_LICENSE:GPL$
+** Commercial License Usage
+** Licensees holding valid commercial Qt licenses may use this file in
+** accordance with the commercial license agreement provided with the
+** Software or, alternatively, in accordance with the terms contained in
+** a written agreement between you and The Qt Company. For licensing terms
+** and conditions see https://www.qt.io/terms-conditions. For further
+** information use the contact form at https://www.qt.io/contact-us.
+**
+** GNU General Public License Usage
+** Alternatively, this file may be used under the terms of the GNU
+** General Public License version 3 or (at your option) any later version
+** approved by the KDE Free Qt Foundation. The licenses are as published by
+** the Free Software Foundation and appearing in the file LICENSE.GPL3
+** included in the packaging of this file. Please review the following
+** information to ensure the GNU General Public License requirements will
+** be met: https://www.gnu.org/licenses/gpl-3.0.html.
+**
+** $QT_END_LICENSE$
+**
+****************************************************************************/
+
+#include "declarativeopenglrendernode.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.
+DeclarativeOpenGLRenderNode::DeclarativeOpenGLRenderNode(QQuickWindow *window) :
+ QObject(),
+ m_texture(0),
+ m_imageNode(nullptr),
+ 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();
+
+ connect(m_window, &QQuickWindow::beforeRendering,
+ this, &DeclarativeOpenGLRenderNode::render);
+}
+
+DeclarativeOpenGLRenderNode::~DeclarativeOpenGLRenderNode()
+{
+ 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"
+ "uniform highp mat4 matrix;\n"
+ "void main() {\n"
+ " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n"
+ " gl_Position = matrix * 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 DeclarativeOpenGLRenderNode::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");
+ m_matrixUniformLoc = m_program->uniformLocation("matrix");
+
+ // 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 DeclarativeOpenGLRenderNode::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);
+ if (!m_imageNode) {
+ m_imageNode = m_window->createImageNode();
+ m_imageNode->setFiltering(QSGTexture::Linear);
+ m_imageNode->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically);
+ m_imageNode->setFlag(OwnedByParent);
+ if (!m_rect.isEmpty())
+ m_imageNode->setRect(m_rect);
+ appendChildNode(m_imageNode);
+ }
+ m_imageNode->setTexture(m_texture);
+
+ m_recreateFbo = false;
+}
+
+// Must be called on render thread and in context
+void DeclarativeOpenGLRenderNode::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 DeclarativeOpenGLRenderNode::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 = *newData;
+ }
+ 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 = *newData;
+ }
+ }
+ }
+ markDirty(DirtyMaterial);
+ m_renderNeeded = true;
+}
+
+void DeclarativeOpenGLRenderNode::setRect(const QRectF &rect)
+{
+ m_rect = rect;
+
+ if (m_imageNode)
+ m_imageNode->setRect(rect);
+}
+
+void DeclarativeOpenGLRenderNode::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);
+ m_program->setUniformValue(m_matrixUniformLoc, data->matrix);
+
+ 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 DeclarativeOpenGLRenderNode::render()
+{
+ if (m_renderNeeded) {
+ if (m_xyDataMap.size()) {
+ if (!m_program)
+ initGL();
+ if (m_recreateFbo)
+ recreateFBO();
+ renderGL();
+ } else {
+ if (m_imageNode && m_imageNode->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 DeclarativeOpenGLRenderNode::cleanXYSeriesResources(const QXYSeries *series)
+{
+ if (series) {
+ delete m_seriesBufferMap.take(series);
+ delete m_xyDataMap.take(series);
+ } else {
+ foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
+ delete buffer;
+ m_seriesBufferMap.clear();
+ foreach (GLXYSeriesData *data, m_xyDataMap.values())
+ delete data;
+ m_xyDataMap.clear();
+ }
+}
+
+QT_CHARTS_END_NAMESPACE