blob: 105e489a1c831573a3a94502559ba4fdab9e53d2 [file] [log] [blame]
/****************************************************************************
**
** Copyright (C) 2016 The Qt Company Ltd.
** Contact: https://www.qt.io/licensing/
**
** This file is part of the Qt Charts module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:GPL$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** GNU General Public License Usage
** Alternatively, this file may be used under the terms of the GNU
** General Public License version 3 or (at your option) any later version
** approved by the KDE Free Qt Foundation. The licenses are as published by
** the Free Software Foundation and appearing in the file LICENSE.GPL3
** included in the packaging of this file. Please review the following
** information to ensure the GNU General Public License requirements will
** be met: https://www.gnu.org/licenses/gpl-3.0.html.
**
** $QT_END_LICENSE$
**
****************************************************************************/
#ifndef QT_NO_OPENGL
#include "private/glwidget_p.h"
#include "private/glxyseriesdata_p.h"
#include "private/qabstractseries_p.h"
#include <QtGui/QOpenGLShaderProgram>
#include <QtGui/QOpenGLContext>
#include <QtGui/QOpenGLBuffer>
//#define QDEBUG_TRACE_GL_FPS
#ifdef QDEBUG_TRACE_GL_FPS
# include <QElapsedTimer>
#endif
QT_CHARTS_BEGIN_NAMESPACE
GLWidget::GLWidget(GLXYSeriesDataManager *xyDataManager, QtCharts::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;
foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
delete buffer;
m_seriesBufferMap.clear();
doneCurrent();
}
void GLWidget::cleanXYSeriesResources(const QXYSeries *series)
{
makeCurrent();
if (series) {
delete m_seriesBufferMap.take(series);
} else {
// Null series means all series were removed
foreach (QOpenGLBuffer *buffer, m_seriesBufferMap.values())
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 !defined(QT_OPENGL_ES_2)
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_selectionVector.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_selectionVector.size())
cSeries = m_selectionVector.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_selectionVector[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(), data->array.count() * 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 * devicePixelRatioF();
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) {
Q_FOREACH (QAbstractSeries *chartSeries, m_chart->series()) {
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_CHARTS_END_NAMESPACE
#endif