| 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 |  | 
| 44 | QT_CHARTS_BEGIN_NAMESPACE | 
| 45 |  | 
| 46 | GLWidget::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 |  | 
| 87 | GLWidget::~GLWidget() | 
| 88 | { | 
| 89 |     cleanup(); | 
| 90 | } | 
| 91 |  | 
| 92 | void 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 |  | 
| 106 | void 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 |  | 
| 120 | static 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 |         "}" ; | 
| 131 | static const char *fragmentSource = | 
| 132 |         "uniform highp vec3 color;\n"  | 
| 133 |         "void main() {\n"  | 
| 134 |         "  gl_FragColor = vec4(color,1);\n"  | 
| 135 |         "}\n" ; | 
| 136 |  | 
| 137 | void 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 |  | 
| 181 | void 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 |  | 
| 203 | void 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 |  | 
| 211 | void 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 |  | 
| 218 | void 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 |  | 
| 241 | void 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 |  | 
| 252 | void 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 |  | 
| 277 | QXYSeries *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 |  | 
| 323 | void 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 |  | 
| 377 | void 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. | 
| 392 | QXYSeries *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 |  | 
| 406 | bool GLWidget::needsReset() const | 
| 407 | { | 
| 408 |     return m_view->renderHints().testFlag(flag: QPainter::Antialiasing) != m_antiAlias; | 
| 409 | } | 
| 410 |  | 
| 411 | QT_CHARTS_END_NAMESPACE | 
| 412 |  | 
| 413 | #endif | 
| 414 |  |