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#ifndef QT_NO_OPENGL
31
32#include "private/glwidget_p.h"
33#include "private/glxyseriesdata_p.h"
34#include "private/qabstractseries_p.h"
35#include <QtGui/QOpenGLShaderProgram>
36#include <QtGui/QOpenGLContext>
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
46GLWidget::GLWidget(GLXYSeriesDataManager *xyDataManager, QtCharts::QChart *chart,
47 QGraphicsView *parent)
48 : QOpenGLWidget(parent->viewport()),
49 m_program(nullptr),
50 m_shaderAttribLoc(-1),
51 m_colorUniformLoc(-1),
52 m_minUniformLoc(-1),
53 m_deltaUniformLoc(-1),
54 m_pointSizeUniformLoc(-1),
55 m_xyDataManager(xyDataManager),
56 m_antiAlias(parent->renderHints().testFlag(flag: QPainter::Antialiasing)),
57 m_view(parent),
58 m_selectionFbo(nullptr),
59 m_chart(chart),
60 m_recreateSelectionFbo(true),
61 m_selectionRenderNeeded(true),
62 m_mousePressed(false),
63 m_lastPressSeries(nullptr),
64 m_lastHoverSeries(nullptr)
65{
66 setAttribute(Qt::WA_TranslucentBackground);
67 setAttribute(Qt::WA_AlwaysStackOnTop);
68
69 QSurfaceFormat surfaceFormat;
70 surfaceFormat.setDepthBufferSize(0);
71 surfaceFormat.setStencilBufferSize(0);
72 surfaceFormat.setRedBufferSize(8);
73 surfaceFormat.setGreenBufferSize(8);
74 surfaceFormat.setBlueBufferSize(8);
75 surfaceFormat.setAlphaBufferSize(8);
76 surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
77 surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType);
78 surfaceFormat.setSamples(m_antiAlias ? 4 : 0);
79 setFormat(surfaceFormat);
80
81 connect(sender: xyDataManager, signal: &GLXYSeriesDataManager::seriesRemoved,
82 receiver: this, slot: &GLWidget::cleanXYSeriesResources);
83
84 setMouseTracking(true);
85}
86
87GLWidget::~GLWidget()
88{
89 cleanup();
90}
91
92void GLWidget::cleanup()
93{
94 makeCurrent();
95
96 delete m_program;
97 m_program = 0;
98
99 foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
100 delete buffer;
101 m_seriesBufferMap.clear();
102
103 doneCurrent();
104}
105
106void GLWidget::cleanXYSeriesResources(const QXYSeries *series)
107{
108 makeCurrent();
109 if (series) {
110 delete m_seriesBufferMap.take(akey: series);
111 } else {
112 // Null series means all series were removed
113 foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
114 delete buffer;
115 m_seriesBufferMap.clear();
116 }
117 doneCurrent();
118}
119
120static const char *vertexSource =
121 "attribute highp vec2 points;\n"
122 "uniform highp vec2 min;\n"
123 "uniform highp vec2 delta;\n"
124 "uniform highp float pointSize;\n"
125 "uniform highp mat4 matrix;\n"
126 "void main() {\n"
127 " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n"
128 " gl_Position = matrix * vec4(normalPoint, 0, 1);\n"
129 " gl_PointSize = pointSize;\n"
130 "}";
131static const char *fragmentSource =
132 "uniform highp vec3 color;\n"
133 "void main() {\n"
134 " gl_FragColor = vec4(color,1);\n"
135 "}\n";
136
137void GLWidget::initializeGL()
138{
139 connect(sender: context(), signal: &QOpenGLContext::aboutToBeDestroyed, receiver: this, slot: &GLWidget::cleanup);
140
141 initializeOpenGLFunctions();
142 glClearColor(red: 0, green: 0, blue: 0, alpha: 0);
143
144 m_program = new QOpenGLShaderProgram;
145 m_program->addShaderFromSourceCode(type: QOpenGLShader::Vertex, source: vertexSource);
146 m_program->addShaderFromSourceCode(type: QOpenGLShader::Fragment, source: fragmentSource);
147 m_program->bindAttributeLocation(name: "points", location: 0);
148 m_program->link();
149
150 m_program->bind();
151 m_colorUniformLoc = m_program->uniformLocation(name: "color");
152 m_minUniformLoc = m_program->uniformLocation(name: "min");
153 m_deltaUniformLoc = m_program->uniformLocation(name: "delta");
154 m_pointSizeUniformLoc = m_program->uniformLocation(name: "pointSize");
155 m_matrixUniformLoc = m_program->uniformLocation(name: "matrix");
156
157
158 // Create a vertex array object. In OpenGL ES 2.0 and OpenGL 2.x
159 // implementations this is optional and support may not be present
160 // at all. Nonetheless the below code works in all cases and makes
161 // sure there is a VAO when one is needed.
162 m_vao.create();
163 QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
164
165 glEnableVertexAttribArray(index: 0);
166
167 glDisable(GL_DEPTH_TEST);
168 glDisable(GL_STENCIL_TEST);
169
170#if !defined(QT_OPENGL_ES_2)
171 if (!QOpenGLContext::currentContext()->isOpenGLES()) {
172 // Make it possible to change point primitive size and use textures with them in
173 // the shaders. These are implicitly enabled in ES2.
174 glEnable(GL_PROGRAM_POINT_SIZE);
175 }
176#endif
177
178 m_program->release();
179}
180
181void GLWidget::paintGL()
182{
183 render(selection: false);
184
185#ifdef QDEBUG_TRACE_GL_FPS
186 static QElapsedTimer stopWatch;
187 static int frameCount = -1;
188 if (frameCount == -1) {
189 stopWatch.start();
190 frameCount = 0;
191 }
192 frameCount++;
193 int elapsed = stopWatch.elapsed();
194 if (elapsed >= 1000) {
195 elapsed = stopWatch.restart();
196 qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed))));
197 qDebug() << "FPS:" << fps;
198 frameCount = 0;
199 }
200#endif
201}
202
203void GLWidget::resizeGL(int w, int h)
204{
205 m_fboSize.setWidth(w);
206 m_fboSize.setHeight(h);
207 m_recreateSelectionFbo = true;
208 m_selectionRenderNeeded = true;
209}
210
211void GLWidget::mouseDoubleClickEvent(QMouseEvent *event)
212{
213 QXYSeries *series = findSeriesAtEvent(event);
214 if (series)
215 emit series->doubleClicked(point: series->d_ptr->domain()->calculateDomainPoint(point: event->pos()));
216}
217
218void GLWidget::mouseMoveEvent(QMouseEvent *event)
219{
220 if (m_view->hasMouseTracking() && !event->buttons()) {
221 QXYSeries *series = findSeriesAtEvent(event);
222 if (series != m_lastHoverSeries) {
223 if (m_lastHoverSeries) {
224 if (chartSeries(cSeries: m_lastHoverSeries)) {
225 emit m_lastHoverSeries->hovered(
226 point: m_lastHoverSeries->d_ptr->domain()->calculateDomainPoint(
227 point: event->pos()), state: false);
228 }
229 }
230 if (series) {
231 emit series->hovered(
232 point: series->d_ptr->domain()->calculateDomainPoint(point: event->pos()), state: true);
233 }
234 m_lastHoverSeries = series;
235 }
236 } else {
237 event->ignore();
238 }
239}
240
241void GLWidget::mousePressEvent(QMouseEvent *event)
242{
243 QXYSeries *series = findSeriesAtEvent(event);
244 if (series) {
245 m_mousePressed = true;
246 m_mousePressPos = event->pos();
247 m_lastPressSeries = series;
248 emit series->pressed(point: series->d_ptr->domain()->calculateDomainPoint(point: event->pos()));
249 }
250}
251
252void GLWidget::mouseReleaseEvent(QMouseEvent *event)
253{
254 if (chartSeries(cSeries: m_lastPressSeries)) {
255 emit m_lastPressSeries->released(
256 point: m_lastPressSeries->d_ptr->domain()->calculateDomainPoint(point: m_mousePressPos));
257 if (m_mousePressed) {
258 emit m_lastPressSeries->clicked(
259 point: m_lastPressSeries->d_ptr->domain()->calculateDomainPoint(point: m_mousePressPos));
260 }
261 if (m_lastHoverSeries == m_lastPressSeries
262 && m_lastHoverSeries != findSeriesAtEvent(event)) {
263 if (chartSeries(cSeries: m_lastHoverSeries)) {
264 emit m_lastHoverSeries->hovered(
265 point: m_lastHoverSeries->d_ptr->domain()->calculateDomainPoint(
266 point: event->pos()), state: false);
267 }
268 m_lastHoverSeries = nullptr;
269 }
270 m_lastPressSeries = nullptr;
271 m_mousePressed = false;
272 } else {
273 event->ignore();
274 }
275}
276
277QXYSeries *GLWidget::findSeriesAtEvent(QMouseEvent *event)
278{
279 QXYSeries *series = nullptr;
280 int index = -1;
281
282 if (m_xyDataManager->dataMap().size()) {
283 makeCurrent();
284
285 if (m_recreateSelectionFbo)
286 recreateSelectionFbo();
287
288 m_selectionFbo->bind();
289
290 if (m_selectionRenderNeeded) {
291 m_selectionVector.resize(asize: m_xyDataManager->dataMap().size());
292 render(selection: true);
293 m_selectionRenderNeeded = false;
294 }
295
296 GLubyte pixel[4] = {0, 0, 0, 0};
297 glReadPixels(x: event->pos().x(), y: m_fboSize.height() - event->pos().y(),
298 width: 1, height: 1, GL_RGBA, GL_UNSIGNED_BYTE,
299 pixels: (void *)pixel);
300 if (pixel[3] == 0xff)
301 index = pixel[0] + (pixel[1] << 8) + (pixel[2] << 16);
302
303 glBindFramebuffer(GL_FRAMEBUFFER, framebuffer: defaultFramebufferObject());
304
305 doneCurrent();
306 }
307
308 if (index >= 0) {
309 const QXYSeries *cSeries = nullptr;
310 if (index < m_selectionVector.size())
311 cSeries = m_selectionVector.at(i: index);
312
313 series = chartSeries(cSeries);
314 }
315 if (series)
316 event->accept();
317 else
318 event->ignore();
319
320 return series;
321}
322
323void GLWidget::render(bool selection)
324{
325 glClear(GL_COLOR_BUFFER_BIT);
326
327 QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
328 m_program->bind();
329
330 int counter = 0;
331 const auto &dataMap = m_xyDataManager->dataMap();
332 for (auto i = dataMap.begin(), end = dataMap.end(); i != end; ++i) {
333 QOpenGLBuffer *vbo = m_seriesBufferMap.value(akey: i.key());
334 GLXYSeriesData *data = i.value();
335
336 if (data->visible) {
337 if (selection) {
338 m_selectionVector[counter] = i.key();
339 m_program->setUniformValue(location: m_colorUniformLoc, value: QVector3D((counter & 0xff) / 255.0f,
340 ((counter & 0xff00) >> 8) / 255.0f,
341 ((counter & 0xff0000) >> 16) / 255.0f));
342 counter++;
343 } else {
344 m_program->setUniformValue(location: m_colorUniformLoc, value: data->color);
345 }
346 m_program->setUniformValue(location: m_minUniformLoc, value: data->min);
347 m_program->setUniformValue(location: m_deltaUniformLoc, value: data->delta);
348 m_program->setUniformValue(location: m_matrixUniformLoc, value: data->matrix);
349 bool dirty = data->dirty;
350 if (!vbo) {
351 vbo = new QOpenGLBuffer;
352 m_seriesBufferMap.insert(akey: i.key(), avalue: vbo);
353 vbo->create();
354 dirty = true;
355 }
356 vbo->bind();
357 if (dirty) {
358 vbo->allocate(data: data->array.constData(), count: data->array.count() * sizeof(GLfloat));
359 dirty = false;
360 m_selectionRenderNeeded = true;
361 }
362
363 glVertexAttribPointer(indx: 0, size: 2, GL_FLOAT, GL_FALSE, stride: 0, ptr: 0);
364 if (data->type == QAbstractSeries::SeriesTypeLine) {
365 glLineWidth(width: data->width);
366 glDrawArrays(GL_LINE_STRIP, first: 0, count: data->array.size() / 2);
367 } else { // Scatter
368 m_program->setUniformValue(location: m_pointSizeUniformLoc, value: data->width);
369 glDrawArrays(GL_POINTS, first: 0, count: data->array.size() / 2);
370 }
371 vbo->release();
372 }
373 }
374 m_program->release();
375}
376
377void GLWidget::recreateSelectionFbo()
378{
379 QOpenGLFramebufferObjectFormat fboFormat;
380 fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment);
381
382 delete m_selectionFbo;
383
384 const QSize deviceSize = m_fboSize * devicePixelRatioF();
385 m_selectionFbo = new QOpenGLFramebufferObject(deviceSize, fboFormat);
386 m_recreateSelectionFbo = false;
387 m_selectionRenderNeeded = true;
388}
389
390// This function makes sure the series we are dealing with has not been removed from the
391// chart since we stored the pointer.
392QXYSeries *GLWidget::chartSeries(const QXYSeries *cSeries)
393{
394 QXYSeries *series = nullptr;
395 if (cSeries) {
396 Q_FOREACH (QAbstractSeries *chartSeries, m_chart->series()) {
397 if (cSeries == chartSeries) {
398 series = qobject_cast<QXYSeries *>(object: chartSeries);
399 break;
400 }
401 }
402 }
403 return series;
404}
405
406bool GLWidget::needsReset() const
407{
408 return m_view->renderHints().testFlag(flag: QPainter::Antialiasing) != m_antiAlias;
409}
410
411QT_CHARTS_END_NAMESPACE
412
413#endif
414

source code of qtcharts/src/charts/glwidget.cpp