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

Provided by KDAB

Privacy Policy
Learn to use CMake with our Intro Training
Find out more

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