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

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