1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the Qt Charts module of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 or (at your option) any later version
20** approved by the KDE Free Qt Foundation. The licenses are as published by
21** the Free Software Foundation and appearing in the file LICENSE.GPL3
22** included in the packaging of this file. Please review the following
23** information to ensure the GNU General Public License requirements will
24** be met: https://www.gnu.org/licenses/gpl-3.0.html.
25**
26** $QT_END_LICENSE$
27**
28****************************************************************************/
29
30#include "declarativeopenglrendernode_p.h"
31
32#include <QtGui/QOpenGLContext>
33#include <QtGui/QOpenGLFunctions>
34#include <QtGui/QOpenGLFramebufferObjectFormat>
35#include <QtGui/QOpenGLFramebufferObject>
36#include <QOpenGLShaderProgram>
37#include <QtGui/QOpenGLBuffer>
38
39//#define QDEBUG_TRACE_GL_FPS
40#ifdef QDEBUG_TRACE_GL_FPS
41# include <QElapsedTimer>
42#endif
43
44QT_CHARTS_BEGIN_NAMESPACE
45
46// This node draws the xy series data on a transparent background using OpenGL.
47// It is used as a child node of the chart node.
48DeclarativeOpenGLRenderNode::DeclarativeOpenGLRenderNode(QQuickWindow *window) :
49 QObject(),
50 m_texture(nullptr),
51 m_imageNode(nullptr),
52 m_window(window),
53 m_textureOptions(QQuickWindow::TextureHasAlphaChannel),
54 m_textureSize(1, 1),
55 m_recreateFbo(false),
56 m_fbo(nullptr),
57 m_resolvedFbo(nullptr),
58 m_selectionFbo(nullptr),
59 m_program(nullptr),
60 m_shaderAttribLoc(-1),
61 m_colorUniformLoc(-1),
62 m_minUniformLoc(-1),
63 m_deltaUniformLoc(-1),
64 m_pointSizeUniformLoc(-1),
65 m_renderNeeded(true),
66 m_antialiasing(false),
67 m_selectionRenderNeeded(true),
68 m_mousePressed(false),
69 m_lastPressSeries(nullptr),
70 m_lastHoverSeries(nullptr)
71{
72 initializeOpenGLFunctions();
73
74 connect(sender: m_window, signal: &QQuickWindow::beforeRendering,
75 receiver: this, slot: &DeclarativeOpenGLRenderNode::render);
76}
77
78DeclarativeOpenGLRenderNode::~DeclarativeOpenGLRenderNode()
79{
80 cleanXYSeriesResources(series: 0);
81
82 delete m_texture;
83 delete m_fbo;
84 delete m_resolvedFbo;
85 delete m_selectionFbo;
86 delete m_program;
87
88 qDeleteAll(c: m_mouseEvents);
89}
90
91static const char *vertexSourceCore =
92 "#version 150\n"
93 "in vec2 points;\n"
94 "uniform vec2 min;\n"
95 "uniform vec2 delta;\n"
96 "uniform float pointSize;\n"
97 "uniform mat4 matrix;\n"
98 "void main() {\n"
99 " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n"
100 " gl_Position = matrix * vec4(normalPoint, 0, 1);\n"
101 " gl_PointSize = pointSize;\n"
102 "}";
103static const char *fragmentSourceCore =
104 "#version 150\n"
105 "uniform vec3 color;\n"
106 "out vec4 fragColor;\n"
107 "void main() {\n"
108 " fragColor = vec4(color,1);\n"
109 "}\n";
110
111static const char *vertexSource =
112 "attribute highp vec2 points;\n"
113 "uniform highp vec2 min;\n"
114 "uniform highp vec2 delta;\n"
115 "uniform highp float pointSize;\n"
116 "uniform highp mat4 matrix;\n"
117 "void main() {\n"
118 " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n"
119 " gl_Position = matrix * vec4(normalPoint, 0, 1);\n"
120 " gl_PointSize = pointSize;\n"
121 "}";
122static const char *fragmentSource =
123 "uniform highp vec3 color;\n"
124 "void main() {\n"
125 " gl_FragColor = vec4(color,1);\n"
126 "}\n";
127
128// Must be called on render thread and in context
129void DeclarativeOpenGLRenderNode::initGL()
130{
131 recreateFBO();
132
133 m_program = new QOpenGLShaderProgram;
134 if (QOpenGLContext::currentContext()->format().profile() == QSurfaceFormat::CoreProfile) {
135 m_program->addShaderFromSourceCode(type: QOpenGLShader::Vertex, source: vertexSourceCore);
136 m_program->addShaderFromSourceCode(type: QOpenGLShader::Fragment, source: fragmentSourceCore);
137 } else {
138 m_program->addShaderFromSourceCode(type: QOpenGLShader::Vertex, source: vertexSource);
139 m_program->addShaderFromSourceCode(type: QOpenGLShader::Fragment, source: fragmentSource);
140 }
141 m_program->bindAttributeLocation(name: "points", location: 0);
142 m_program->link();
143
144 m_program->bind();
145 m_colorUniformLoc = m_program->uniformLocation(name: "color");
146 m_minUniformLoc = m_program->uniformLocation(name: "min");
147 m_deltaUniformLoc = m_program->uniformLocation(name: "delta");
148 m_pointSizeUniformLoc = m_program->uniformLocation(name: "pointSize");
149 m_matrixUniformLoc = m_program->uniformLocation(name: "matrix");
150
151 // Create a vertex array object. In OpenGL ES 2.0 and OpenGL 2.x
152 // implementations this is optional and support may not be present
153 // at all. Nonetheless the below code works in all cases and makes
154 // sure there is a VAO when one is needed.
155 m_vao.create();
156 QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
157
158#if !defined(QT_OPENGL_ES_2)
159 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
160 // Make it possible to change point primitive size and use textures with them in
161 // the shaders. These are implicitly enabled in ES2.
162 // Qt Quick doesn't change these flags, so it should be safe to just enable them
163 // at initialization.
164 glEnable(GL_PROGRAM_POINT_SIZE);
165 }
166#endif
167
168 m_program->release();
169}
170
171void DeclarativeOpenGLRenderNode::recreateFBO()
172{
173 QOpenGLFramebufferObjectFormat fboFormat;
174 fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment);
175
176 int samples = 0;
177 QOpenGLContext *context = QOpenGLContext::currentContext();
178
179 if (m_antialiasing && (!context->isOpenGLES() || context->format().majorVersion() >= 3))
180 samples = 4;
181 fboFormat.setSamples(samples);
182
183 delete m_fbo;
184 delete m_resolvedFbo;
185 delete m_selectionFbo;
186 m_resolvedFbo = nullptr;
187
188 m_fbo = new QOpenGLFramebufferObject(m_textureSize, fboFormat);
189 if (samples > 0)
190 m_resolvedFbo = new QOpenGLFramebufferObject(m_textureSize);
191 m_selectionFbo = new QOpenGLFramebufferObject(m_textureSize);
192
193 delete m_texture;
194 uint textureId = m_resolvedFbo ? m_resolvedFbo->texture() : m_fbo->texture();
195 m_texture = m_window->createTextureFromId(id: textureId, size: m_textureSize, options: m_textureOptions);
196 if (!m_imageNode) {
197 m_imageNode = m_window->createImageNode();
198 m_imageNode->setFiltering(QSGTexture::Linear);
199 m_imageNode->setTextureCoordinatesTransform(QSGImageNode::MirrorVertically);
200 m_imageNode->setFlag(OwnedByParent);
201 if (!m_rect.isEmpty())
202 m_imageNode->setRect(m_rect);
203 appendChildNode(node: m_imageNode);
204 }
205 m_imageNode->setTexture(m_texture);
206
207 m_recreateFbo = false;
208}
209
210// Must be called on render thread and in context
211void DeclarativeOpenGLRenderNode::setTextureSize(const QSize &size)
212{
213 m_textureSize = size;
214 m_recreateFbo = true;
215 m_renderNeeded = true;
216 m_selectionRenderNeeded = true;
217}
218
219// Must be called on render thread while gui thread is blocked, and in context
220void DeclarativeOpenGLRenderNode::setSeriesData(bool mapDirty, const GLXYDataMap &dataMap)
221{
222 bool dirty = false;
223 if (mapDirty) {
224 // Series have changed, recreate map, but utilize old data where feasible
225 GLXYDataMap oldMap = m_xyDataMap;
226 m_xyDataMap.clear();
227
228 for (auto i = dataMap.begin(), end = dataMap.end(); i != end; ++i) {
229 GLXYSeriesData *data = oldMap.take(akey: i.key());
230 const GLXYSeriesData *newData = i.value();
231 if (!data || newData->dirty) {
232 if (!data)
233 data = new GLXYSeriesData;
234 *data = *newData;
235 }
236 m_xyDataMap.insert(akey: i.key(), avalue: data);
237 }
238 // Delete remaining old data
239 for (auto i = oldMap.begin(), end = oldMap.end(); i != end; ++i) {
240 delete i.value();
241 cleanXYSeriesResources(series: i.key());
242 }
243 dirty = true;
244 } else {
245 // Series have not changed, so just copy dirty data over
246 for (auto i = dataMap.begin(), end = dataMap.end(); i != end; ++i) {
247 const GLXYSeriesData *newData = i.value();
248 if (i.value()->dirty) {
249 dirty = true;
250 GLXYSeriesData *data = m_xyDataMap.value(akey: i.key());
251 if (data)
252 *data = *newData;
253 }
254 }
255 }
256 if (dirty) {
257 markDirty(bits: DirtyMaterial);
258 m_renderNeeded = true;
259 m_selectionRenderNeeded = true;
260 }
261}
262
263void DeclarativeOpenGLRenderNode::setRect(const QRectF &rect)
264{
265 m_rect = rect;
266
267 if (m_imageNode)
268 m_imageNode->setRect(rect);
269}
270
271void DeclarativeOpenGLRenderNode::setAntialiasing(bool enable)
272{
273 if (m_antialiasing != enable) {
274 m_antialiasing = enable;
275 m_recreateFbo = true;
276 m_renderNeeded = true;
277 }
278}
279
280void DeclarativeOpenGLRenderNode::addMouseEvents(const QVector<QMouseEvent *> &events)
281{
282 if (events.size()) {
283 m_mouseEvents.append(l: events);
284 markDirty(bits: DirtyMaterial);
285 }
286}
287
288void DeclarativeOpenGLRenderNode::takeMouseEventResponses(QVector<MouseEventResponse> &responses)
289{
290 responses.append(l: m_mouseEventResponses);
291 m_mouseEventResponses.clear();
292}
293
294void DeclarativeOpenGLRenderNode::renderGL(bool selection)
295{
296 glClearColor(red: 0, green: 0, blue: 0, alpha: 0);
297
298 QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
299 m_program->bind();
300
301 glClear(GL_COLOR_BUFFER_BIT);
302 glEnableVertexAttribArray(index: 0);
303
304 glViewport(x: 0, y: 0, width: m_textureSize.width(), height: m_textureSize.height());
305
306 int counter = 0;
307 for (auto i = m_xyDataMap.begin(), end = m_xyDataMap.end(); i != end; ++i) {
308 QOpenGLBuffer *vbo = m_seriesBufferMap.value(akey: i.key());
309 GLXYSeriesData *data = i.value();
310
311 if (data->visible) {
312 if (selection) {
313 m_selectionVector[counter] = i.key();
314 m_program->setUniformValue(location: m_colorUniformLoc, value: QVector3D((counter & 0xff) / 255.0f,
315 ((counter & 0xff00) >> 8) / 255.0f,
316 ((counter & 0xff0000) >> 16) / 255.0f));
317 counter++;
318 } else {
319 m_program->setUniformValue(location: m_colorUniformLoc, value: data->color);
320 }
321 m_program->setUniformValue(location: m_minUniformLoc, value: data->min);
322 m_program->setUniformValue(location: m_deltaUniformLoc, value: data->delta);
323 m_program->setUniformValue(location: m_matrixUniformLoc, value: data->matrix);
324
325 if (!vbo) {
326 vbo = new QOpenGLBuffer;
327 m_seriesBufferMap.insert(akey: i.key(), avalue: vbo);
328 vbo->create();
329 }
330 vbo->bind();
331 if (data->dirty) {
332 vbo->allocate(data: data->array.constData(), count: data->array.count() * sizeof(GLfloat));
333 data->dirty = false;
334 }
335
336 glVertexAttribPointer(indx: 0, size: 2, GL_FLOAT, GL_FALSE, stride: 0, ptr: 0);
337 if (data->type == QAbstractSeries::SeriesTypeLine) {
338 glLineWidth(width: data->width);
339 glDrawArrays(GL_LINE_STRIP, first: 0, count: data->array.size() / 2);
340 } else { // Scatter
341 m_program->setUniformValue(location: m_pointSizeUniformLoc, value: data->width);
342 glDrawArrays(GL_POINTS, first: 0, count: data->array.size() / 2);
343 }
344 vbo->release();
345 }
346 }
347}
348
349void DeclarativeOpenGLRenderNode::renderSelection()
350{
351 m_selectionFbo->bind();
352
353 m_selectionVector.resize(asize: m_xyDataMap.size());
354
355 renderGL(selection: true);
356
357 m_selectionRenderNeeded = false;
358}
359
360void DeclarativeOpenGLRenderNode::renderVisual()
361{
362 m_fbo->bind();
363
364 renderGL(selection: false);
365
366 if (m_resolvedFbo) {
367 QRect rect(QPoint(0, 0), m_fbo->size());
368 QOpenGLFramebufferObject::blitFramebuffer(target: m_resolvedFbo, targetRect: rect, source: m_fbo, sourceRect: rect);
369 }
370
371 markDirty(bits: DirtyMaterial);
372
373#ifdef QDEBUG_TRACE_GL_FPS
374 static QElapsedTimer stopWatch;
375 static int frameCount = -1;
376 if (frameCount == -1) {
377 stopWatch.start();
378 frameCount = 0;
379 }
380 frameCount++;
381 int elapsed = stopWatch.elapsed();
382 if (elapsed >= 1000) {
383 elapsed = stopWatch.restart();
384 qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed))));
385 qDebug() << "FPS:" << fps;
386 frameCount = 0;
387 }
388#endif
389}
390
391// Must be called on render thread as response to beforeRendering signal
392void DeclarativeOpenGLRenderNode::render()
393{
394 if (m_renderNeeded) {
395 if (m_xyDataMap.size()) {
396 if (!m_program)
397 initGL();
398 if (m_recreateFbo)
399 recreateFBO();
400 renderVisual();
401 } else {
402 if (m_imageNode && m_imageNode->rect() != QRectF()) {
403 glClearColor(red: 0, green: 0, blue: 0, alpha: 0);
404 m_fbo->bind();
405 glClear(GL_COLOR_BUFFER_BIT);
406
407 // If last series was removed, zero out the node rect
408 setRect(QRectF());
409 }
410 }
411 m_renderNeeded = false;
412 }
413 handleMouseEvents();
414 m_window->resetOpenGLState();
415}
416
417void DeclarativeOpenGLRenderNode::cleanXYSeriesResources(const QXYSeries *series)
418{
419 if (series) {
420 delete m_seriesBufferMap.take(akey: series);
421 delete m_xyDataMap.take(akey: series);
422 } else {
423 foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
424 delete buffer;
425 m_seriesBufferMap.clear();
426 foreach (GLXYSeriesData *data, m_xyDataMap.values())
427 delete data;
428 m_xyDataMap.clear();
429 }
430}
431
432void DeclarativeOpenGLRenderNode::handleMouseEvents()
433{
434 if (m_mouseEvents.size()) {
435 if (m_xyDataMap.size()) {
436 if (m_selectionRenderNeeded)
437 renderSelection();
438 }
439 Q_FOREACH (QMouseEvent *event, m_mouseEvents) {
440 const QXYSeries *series = findSeriesAtEvent(event);
441 switch (event->type()) {
442 case QEvent::MouseMove: {
443 if (series != m_lastHoverSeries) {
444 if (m_lastHoverSeries) {
445 m_mouseEventResponses.append(
446 t: MouseEventResponse(MouseEventResponse::HoverLeave,
447 event->pos(), m_lastHoverSeries));
448 }
449 if (series) {
450 m_mouseEventResponses.append(
451 t: MouseEventResponse(MouseEventResponse::HoverEnter,
452 event->pos(), series));
453 }
454 m_lastHoverSeries = series;
455 }
456 break;
457 }
458 case QEvent::MouseButtonPress: {
459 if (series) {
460 m_mousePressed = true;
461 m_mousePressPos = event->pos();
462 m_lastPressSeries = series;
463 m_mouseEventResponses.append(
464 t: MouseEventResponse(MouseEventResponse::Pressed,
465 event->pos(), series));
466 }
467 break;
468 }
469 case QEvent::MouseButtonRelease: {
470 m_mouseEventResponses.append(
471 t: MouseEventResponse(MouseEventResponse::Released,
472 m_mousePressPos, m_lastPressSeries));
473 if (m_mousePressed) {
474 m_mouseEventResponses.append(
475 t: MouseEventResponse(MouseEventResponse::Clicked,
476 m_mousePressPos, m_lastPressSeries));
477 }
478 if (m_lastHoverSeries == m_lastPressSeries && m_lastHoverSeries != series) {
479 if (m_lastHoverSeries) {
480 m_mouseEventResponses.append(
481 t: MouseEventResponse(MouseEventResponse::HoverLeave,
482 event->pos(), m_lastHoverSeries));
483 }
484 m_lastHoverSeries = nullptr;
485 }
486 m_lastPressSeries = nullptr;
487 m_mousePressed = false;
488 break;
489 }
490 case QEvent::MouseButtonDblClick: {
491 if (series) {
492 m_mouseEventResponses.append(
493 t: MouseEventResponse(MouseEventResponse::DoubleClicked,
494 event->pos(), series));
495 }
496 break;
497 }
498 default:
499 break;
500 }
501 }
502
503 qDeleteAll(c: m_mouseEvents);
504 m_mouseEvents.clear();
505 }
506}
507
508const QXYSeries *DeclarativeOpenGLRenderNode::findSeriesAtEvent(QMouseEvent *event)
509{
510 const QXYSeries *series = nullptr;
511 int index = -1;
512
513 if (m_xyDataMap.size()) {
514 m_selectionFbo->bind();
515
516 GLubyte pixel[4] = {0, 0, 0, 0};
517 glReadPixels(x: event->pos().x(), y: m_textureSize.height() - event->pos().y(),
518 width: 1, height: 1, GL_RGBA, GL_UNSIGNED_BYTE,
519 pixels: (void *)pixel);
520 if (pixel[3] == 0xff)
521 index = pixel[0] + (pixel[1] << 8) + (pixel[2] << 16);
522 }
523
524 if (index >= 0 && index < m_selectionVector.size())
525 series = m_selectionVector.at(i: index);
526
527 return series;
528}
529
530QT_CHARTS_END_NAMESPACE
531

source code of qtcharts/src/chartsqml2/declarativeopenglrendernode.cpp