Revision control

++ // Copyright (C) 2016 The Qt Company Ltd.
++ // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
++
++ #undef QT_NO_FOREACH // this file contains unported legacy Q_FOREACH uses
++
++ #ifndef QT_NO_OPENGL
++
++ #include "private/glwidget_p.h"
++ #include "private/glxyseriesdata_p.h"
++ #include "private/qabstractseries_p.h"
++ #include <QtOpenGL/QOpenGLShaderProgram>
++ #include <QtGui/QOpenGLContext>
++ #include <QtOpenGL/QOpenGLBuffer>
++
++ //#define QDEBUG_TRACE_GL_FPS
++ #ifdef QDEBUG_TRACE_GL_FPS
++ # include <QElapsedTimer>
++ #endif
++
++ QT_BEGIN_NAMESPACE
++
++ GLWidget::GLWidget(GLXYSeriesDataManager *xyDataManager, QChart *chart,
++ QGraphicsView *parent)
++ : QOpenGLWidget(parent->viewport()),
++ m_program(nullptr),
++ m_shaderAttribLoc(-1),
++ m_colorUniformLoc(-1),
++ m_minUniformLoc(-1),
++ m_deltaUniformLoc(-1),
++ m_pointSizeUniformLoc(-1),
++ m_xyDataManager(xyDataManager),
++ m_antiAlias(parent->renderHints().testFlag(QPainter::Antialiasing)),
++ m_view(parent),
++ m_selectionFbo(nullptr),
++ m_chart(chart),
++ m_recreateSelectionFbo(true),
++ m_selectionRenderNeeded(true),
++ m_mousePressed(false),
++ m_lastPressSeries(nullptr),
++ m_lastHoverSeries(nullptr)
++ {
++ setAttribute(Qt::WA_TranslucentBackground);
++ setAttribute(Qt::WA_AlwaysStackOnTop);
++
++ QSurfaceFormat surfaceFormat;
++ surfaceFormat.setDepthBufferSize(0);
++ surfaceFormat.setStencilBufferSize(0);
++ surfaceFormat.setRedBufferSize(8);
++ surfaceFormat.setGreenBufferSize(8);
++ surfaceFormat.setBlueBufferSize(8);
++ surfaceFormat.setAlphaBufferSize(8);
++ surfaceFormat.setSwapBehavior(QSurfaceFormat::DoubleBuffer);
++ surfaceFormat.setRenderableType(QSurfaceFormat::DefaultRenderableType);
++ surfaceFormat.setSamples(m_antiAlias ? 4 : 0);
++ setFormat(surfaceFormat);
++
++ connect(xyDataManager, &GLXYSeriesDataManager::seriesRemoved,
++ this, &GLWidget::cleanXYSeriesResources);
++
++ setMouseTracking(true);
++ }
++
++ GLWidget::~GLWidget()
++ {
++ cleanup();
++ }
++
++ void GLWidget::cleanup()
++ {
++ makeCurrent();
++
++ delete m_program;
++ m_program = 0;
++
++ for (auto buffer : std::as_const(m_seriesBufferMap))
++ delete buffer;
++ m_seriesBufferMap.clear();
++
++ doneCurrent();
++
++ if (context())
++ context()->disconnect(this);
++ }
++
++ void GLWidget::cleanXYSeriesResources(const QXYSeries *series)
++ {
++ makeCurrent();
++ if (series) {
++ delete m_seriesBufferMap.take(series);
++ } else {
++ // Null series means all series were removed
++ for (auto buffer : std::as_const(m_seriesBufferMap))
++ delete buffer;
++ m_seriesBufferMap.clear();
++ }
++ doneCurrent();
++ }
++
++ static const char *vertexSource =
++ "attribute highp vec2 points;\n"
++ "uniform highp vec2 min;\n"
++ "uniform highp vec2 delta;\n"
++ "uniform highp float pointSize;\n"
++ "uniform highp mat4 matrix;\n"
++ "void main() {\n"
++ " vec2 normalPoint = vec2(-1, -1) + ((points - min) / delta);\n"
++ " gl_Position = matrix * vec4(normalPoint, 0, 1);\n"
++ " gl_PointSize = pointSize;\n"
++ "}";
++ static const char *fragmentSource =
++ "uniform highp vec3 color;\n"
++ "void main() {\n"
++ " gl_FragColor = vec4(color,1);\n"
++ "}\n";
++
++ void GLWidget::initializeGL()
++ {
++ connect(context(), &QOpenGLContext::aboutToBeDestroyed, this, &GLWidget::cleanup);
++
++ initializeOpenGLFunctions();
++ glClearColor(0, 0, 0, 0);
++
++ m_program = new QOpenGLShaderProgram;
++ m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexSource);
++ m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentSource);
++ m_program->bindAttributeLocation("points", 0);
++ m_program->link();
++
++ m_program->bind();
++ m_colorUniformLoc = m_program->uniformLocation("color");
++ m_minUniformLoc = m_program->uniformLocation("min");
++ m_deltaUniformLoc = m_program->uniformLocation("delta");
++ m_pointSizeUniformLoc = m_program->uniformLocation("pointSize");
++ m_matrixUniformLoc = m_program->uniformLocation("matrix");
++
++
++ // Create a vertex array object. In OpenGL ES 2.0 and OpenGL 2.x
++ // implementations this is optional and support may not be present
++ // at all. Nonetheless the below code works in all cases and makes
++ // sure there is a VAO when one is needed.
++ m_vao.create();
++ QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
++
++ glEnableVertexAttribArray(0);
++
++ glDisable(GL_DEPTH_TEST);
++ glDisable(GL_STENCIL_TEST);
++
++ #if !QT_CONFIG(opengles2)
++ if (!QOpenGLContext::currentContext()->isOpenGLES()) {
++ // Make it possible to change point primitive size and use textures with them in
++ // the shaders. These are implicitly enabled in ES2.
++ glEnable(GL_PROGRAM_POINT_SIZE);
++ }
++ #endif
++
++ m_program->release();
++ }
++
++ void GLWidget::paintGL()
++ {
++ render(false);
++
++ #ifdef QDEBUG_TRACE_GL_FPS
++ static QElapsedTimer stopWatch;
++ static int frameCount = -1;
++ if (frameCount == -1) {
++ stopWatch.start();
++ frameCount = 0;
++ }
++ frameCount++;
++ int elapsed = stopWatch.elapsed();
++ if (elapsed >= 1000) {
++ elapsed = stopWatch.restart();
++ qreal fps = qreal(0.1 * int(10000.0 * (qreal(frameCount) / qreal(elapsed))));
++ qDebug() << "FPS:" << fps;
++ frameCount = 0;
++ }
++ #endif
++ }
++
++ void GLWidget::resizeGL(int w, int h)
++ {
++ m_fboSize.setWidth(w);
++ m_fboSize.setHeight(h);
++ m_recreateSelectionFbo = true;
++ m_selectionRenderNeeded = true;
++ }
++
++ void GLWidget::mouseDoubleClickEvent(QMouseEvent *event)
++ {
++ QXYSeries *series = findSeriesAtEvent(event);
++ if (series)
++ emit series->doubleClicked(series->d_ptr->domain()->calculateDomainPoint(event->pos()));
++ }
++
++ void GLWidget::mouseMoveEvent(QMouseEvent *event)
++ {
++ if (m_view->hasMouseTracking() && !event->buttons()) {
++ QXYSeries *series = findSeriesAtEvent(event);
++ if (series != m_lastHoverSeries) {
++ if (m_lastHoverSeries) {
++ if (chartSeries(m_lastHoverSeries)) {
++ emit m_lastHoverSeries->hovered(
++ m_lastHoverSeries->d_ptr->domain()->calculateDomainPoint(
++ event->pos()), false);
++ }
++ }
++ if (series) {
++ emit series->hovered(
++ series->d_ptr->domain()->calculateDomainPoint(event->pos()), true);
++ }
++ m_lastHoverSeries = series;
++ }
++ } else {
++ event->ignore();
++ }
++ }
++
++ void GLWidget::mousePressEvent(QMouseEvent *event)
++ {
++ QXYSeries *series = findSeriesAtEvent(event);
++ if (series) {
++ m_mousePressed = true;
++ m_mousePressPos = event->pos();
++ m_lastPressSeries = series;
++ emit series->pressed(series->d_ptr->domain()->calculateDomainPoint(event->pos()));
++ }
++ }
++
++ void GLWidget::mouseReleaseEvent(QMouseEvent *event)
++ {
++ if (chartSeries(m_lastPressSeries)) {
++ emit m_lastPressSeries->released(
++ m_lastPressSeries->d_ptr->domain()->calculateDomainPoint(m_mousePressPos));
++ if (m_mousePressed) {
++ emit m_lastPressSeries->clicked(
++ m_lastPressSeries->d_ptr->domain()->calculateDomainPoint(m_mousePressPos));
++ }
++ if (m_lastHoverSeries == m_lastPressSeries
++ && m_lastHoverSeries != findSeriesAtEvent(event)) {
++ if (chartSeries(m_lastHoverSeries)) {
++ emit m_lastHoverSeries->hovered(
++ m_lastHoverSeries->d_ptr->domain()->calculateDomainPoint(
++ event->pos()), false);
++ }
++ m_lastHoverSeries = nullptr;
++ }
++ m_lastPressSeries = nullptr;
++ m_mousePressed = false;
++ } else {
++ event->ignore();
++ }
++ }
++
++ QXYSeries *GLWidget::findSeriesAtEvent(QMouseEvent *event)
++ {
++ QXYSeries *series = nullptr;
++ int index = -1;
++
++ if (m_xyDataManager->dataMap().size()) {
++ makeCurrent();
++
++ if (m_recreateSelectionFbo)
++ recreateSelectionFbo();
++
++ m_selectionFbo->bind();
++
++ if (m_selectionRenderNeeded) {
++ m_selectionList.resize(m_xyDataManager->dataMap().size());
++ render(true);
++ m_selectionRenderNeeded = false;
++ }
++
++ GLubyte pixel[4] = {0, 0, 0, 0};
++ glReadPixels(event->pos().x(), m_fboSize.height() - event->pos().y(),
++ 1, 1, GL_RGBA, GL_UNSIGNED_BYTE,
++ (void *)pixel);
++ if (pixel[3] == 0xff)
++ index = pixel[0] + (pixel[1] << 8) + (pixel[2] << 16);
++
++ glBindFramebuffer(GL_FRAMEBUFFER, defaultFramebufferObject());
++
++ doneCurrent();
++ }
++
++ if (index >= 0) {
++ const QXYSeries *cSeries = nullptr;
++ if (index < m_selectionList.size())
++ cSeries = m_selectionList.at(index);
++
++ series = chartSeries(cSeries);
++ }
++ if (series)
++ event->accept();
++ else
++ event->ignore();
++
++ return series;
++ }
++
++ void GLWidget::render(bool selection)
++ {
++ glClear(GL_COLOR_BUFFER_BIT);
++
++ QOpenGLVertexArrayObject::Binder vaoBinder(&m_vao);
++ m_program->bind();
++
++ int counter = 0;
++ const auto &dataMap = m_xyDataManager->dataMap();
++ for (auto i = dataMap.begin(), end = dataMap.end(); i != end; ++i) {
++ QOpenGLBuffer *vbo = m_seriesBufferMap.value(i.key());
++ GLXYSeriesData *data = i.value();
++
++ if (data->visible) {
++ if (selection) {
++ m_selectionList[counter] = i.key();
++ m_program->setUniformValue(m_colorUniformLoc, QVector3D((counter & 0xff) / 255.0f,
++ ((counter & 0xff00) >> 8) / 255.0f,
++ ((counter & 0xff0000) >> 16) / 255.0f));
++ counter++;
++ } else {
++ m_program->setUniformValue(m_colorUniformLoc, data->color);
++ }
++ m_program->setUniformValue(m_minUniformLoc, data->min);
++ m_program->setUniformValue(m_deltaUniformLoc, data->delta);
++ m_program->setUniformValue(m_matrixUniformLoc, data->matrix);
++ bool dirty = data->dirty;
++ if (!vbo) {
++ vbo = new QOpenGLBuffer;
++ m_seriesBufferMap.insert(i.key(), vbo);
++ vbo->create();
++ dirty = true;
++ }
++ vbo->bind();
++ if (dirty) {
++ vbo->allocate(data->array.constData(), int(data->array.size() * sizeof(GLfloat)));
++ dirty = false;
++ m_selectionRenderNeeded = true;
++ }
++
++ glVertexAttribPointer(0, 2, GL_FLOAT, GL_FALSE, 0, 0);
++ if (data->type == QAbstractSeries::SeriesTypeLine) {
++ glLineWidth(data->width);
++ glDrawArrays(GL_LINE_STRIP, 0, data->array.size() / 2);
++ } else { // Scatter
++ m_program->setUniformValue(m_pointSizeUniformLoc, data->width);
++ glDrawArrays(GL_POINTS, 0, data->array.size() / 2);
++ }
++ vbo->release();
++ }
++ }
++ m_program->release();
++ }
++
++ void GLWidget::recreateSelectionFbo()
++ {
++ QOpenGLFramebufferObjectFormat fboFormat;
++ fboFormat.setAttachment(QOpenGLFramebufferObject::NoAttachment);
++
++ delete m_selectionFbo;
++
++ const QSize deviceSize = m_fboSize * devicePixelRatio();
++ m_selectionFbo = new QOpenGLFramebufferObject(deviceSize, fboFormat);
++ m_recreateSelectionFbo = false;
++ m_selectionRenderNeeded = true;
++ }
++
++ // This function makes sure the series we are dealing with has not been removed from the
++ // chart since we stored the pointer.
++ QXYSeries *GLWidget::chartSeries(const QXYSeries *cSeries)
++ {
++ QXYSeries *series = nullptr;
++ if (cSeries) {
++ const auto allSeries = m_chart->series();
++ for (QAbstractSeries *chartSeries : allSeries) {
++ if (cSeries == chartSeries) {
++ series = qobject_cast<QXYSeries *>(chartSeries);
++ break;
++ }
++ }
++ }
++ return series;
++ }
++
++ bool GLWidget::needsReset() const
++ {
++ return m_view->renderHints().testFlag(QPainter::Antialiasing) != m_antiAlias;
++ }
++
++ QT_END_NAMESPACE
++
++ #endif