diff options
45 files changed, 2388 insertions, 374 deletions
diff --git a/examples/opengl/hellogl_es2/hellogl_es2.pro b/examples/opengl/hellogl_es2/hellogl_es2.pro deleted file mode 100644 index b15e8d1909..0000000000 --- a/examples/opengl/hellogl_es2/hellogl_es2.pro +++ /dev/null @@ -1,23 +0,0 @@ -QT += opengl widgets - -SOURCES += main.cpp -SOURCES += glwidget.cpp -SOURCES += mainwindow.cpp -SOURCES += bubble.cpp - -HEADERS += glwidget.h -HEADERS += mainwindow.h -HEADERS += bubble.h - -RESOURCES += texture.qrc - -# install -target.path = $$[QT_INSTALL_EXAMPLES]/opengl/hellogl_es2 -INSTALLS += target - -maemo5 { - # Debian package name may not contain numbers or special characters - # such as '_', lets change this in Maemo. - TARGET = helloglestwo - CONFIG += qt_example -} diff --git a/examples/opengl/hellogl_es2/mainwindow.cpp b/examples/opengl/hellogl_es2/mainwindow.cpp deleted file mode 100644 index eb4ce0ac3e..0000000000 --- a/examples/opengl/hellogl_es2/mainwindow.cpp +++ /dev/null @@ -1,107 +0,0 @@ -/**************************************************************************** -** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). -** Contact: http://www.qt-project.org/legal -** -** This file is part of the examples of the Qt Toolkit. -** -** $QT_BEGIN_LICENSE:BSD$ -** You may use this file under the terms of the BSD license as follows: -** -** "Redistribution and use in source and binary forms, with or without -** modification, are permitted provided that the following conditions are -** met: -** * Redistributions of source code must retain the above copyright -** notice, this list of conditions and the following disclaimer. -** * Redistributions in binary form must reproduce the above copyright -** notice, this list of conditions and the following disclaimer in -** the documentation and/or other materials provided with the -** distribution. -** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names -** of its contributors may be used to endorse or promote products derived -** from this software without specific prior written permission. -** -** -** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS -** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT -** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR -** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT -** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, -** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT -** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, -** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY -** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." -** -** $QT_END_LICENSE$ -** -****************************************************************************/ - -#include "mainwindow.h" - -#include <QApplication> -#include <QMenuBar> -#include <QGroupBox> -#include <QGridLayout> -#include <QSlider> -#include <QLabel> -#include <QTimer> - -#include "glwidget.h" - -MainWindow::MainWindow() -{ - GLWidget *glwidget = new GLWidget(); - QLabel *label = new QLabel(this); - QTimer *timer = new QTimer(this); - QSlider *slider = new QSlider(this); - slider->setOrientation(Qt::Horizontal); - - slider->setRange(0, 100); - slider->setSliderPosition(50); - timer->setInterval(10); - label->setText("A QGlWidget with OpenGl ES"); - label->setAlignment(Qt::AlignHCenter); - - QGroupBox * groupBox = new QGroupBox(this); - setCentralWidget(groupBox); - groupBox->setTitle("OpenGL ES Example"); - - QGridLayout *layout = new QGridLayout(groupBox); - - layout->addWidget(glwidget,1,0,8,1); - layout->addWidget(label,9,0,1,1); - layout->addWidget(slider, 11,0,1,1); - - groupBox->setLayout(layout); - - QMenu *fileMenu = new QMenu("File"); - QMenu *helpMenu = new QMenu("Help"); - QMenu *showMenu = new QMenu("Show"); - menuBar()->addMenu(fileMenu); - menuBar()->addMenu(showMenu); - menuBar()->addMenu(helpMenu); - QAction *exit = new QAction("Exit", fileMenu); - QAction *aboutQt = new QAction("AboutQt", helpMenu); - QAction *showLogo = new QAction("Show 3D Logo", showMenu); - QAction *showTexture = new QAction("Show 2D Texture", showMenu); - QAction *showBubbles = new QAction("Show bubbles", showMenu); - showBubbles->setCheckable(true); - showBubbles->setChecked(true); - fileMenu->addAction(exit); - helpMenu->addAction(aboutQt); - showMenu->addAction(showLogo); - showMenu->addAction(showTexture); - showMenu->addAction(showBubbles); - - QObject::connect(timer, SIGNAL(timeout()), glwidget, SLOT(updateGL())); - QObject::connect(exit, SIGNAL(triggered(bool)), this, SLOT(close())); - QObject::connect(aboutQt, SIGNAL(triggered(bool)), qApp, SLOT(aboutQt())); - - QObject::connect(showLogo, SIGNAL(triggered(bool)), glwidget, SLOT(setLogo())); - QObject::connect(showTexture, SIGNAL(triggered(bool)), glwidget, SLOT(setTexture())); - QObject::connect(showBubbles, SIGNAL(triggered(bool)), glwidget, SLOT(showBubbles(bool))); - QObject::connect(slider, SIGNAL(valueChanged(int)), glwidget, SLOT(setScaling(int))); - timer->start(); -} diff --git a/examples/opengl/opengl.pro b/examples/opengl/opengl.pro index aea281ce5c..fb5f02c07c 100644 --- a/examples/opengl/opengl.pro +++ b/examples/opengl/opengl.pro @@ -5,10 +5,9 @@ TEMPLATE = subdirs contains(QT_CONFIG, dynamicgl) { SUBDIRS = hellowindow \ contextinfo \ - hellogl_es2 -} else: contains(QT_CONFIG, opengles2){ - SUBDIRS = hellogl_es2 -} else { + qopenglwidget \ + threadedqopenglwidget +} else: !contains(QT_CONFIG, opengles2) { SUBDIRS = 2dpainting \ grabber \ hellogl \ @@ -22,6 +21,8 @@ contains(QT_CONFIG, dynamicgl) { paintedwindow \ contextinfo \ cube \ - textures + textures \ + qopenglwidget \ + threadedqopenglwidget EXAMPLE_FILES = shared diff --git a/examples/opengl/hellogl_es2/bubble.cpp b/examples/opengl/qopenglwidget/bubble.cpp index 3e0f053bc8..bf04c64966 100644 --- a/examples/opengl/hellogl_es2/bubble.cpp +++ b/examples/opengl/qopenglwidget/bubble.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. diff --git a/examples/opengl/hellogl_es2/bubble.h b/examples/opengl/qopenglwidget/bubble.h index 60195b4415..ea33466ef7 100644 --- a/examples/opengl/hellogl_es2/bubble.h +++ b/examples/opengl/qopenglwidget/bubble.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. diff --git a/examples/opengl/hellogl_es2/glwidget.cpp b/examples/opengl/qopenglwidget/glwidget.cpp index 733475d321..e95ca363fc 100644 --- a/examples/opengl/hellogl_es2/glwidget.cpp +++ b/examples/opengl/qopenglwidget/glwidget.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. @@ -43,19 +43,25 @@ #include <QPaintEngine> #include <math.h> +#include "mainwindow.h" #include "bubble.h" - const int bubbleNum = 8; -GLWidget::GLWidget(QWidget *parent) - : QGLWidget(parent) +GLWidget::GLWidget(MainWindow *mw, bool button, const QColor &background) + : m_mainWindow(mw), + m_transparent(false), + m_btn(0), + m_hasButton(button), + m_background(background) { + QSurfaceFormat format; + format.setDepthBufferSize(24); + format.setStencilBufferSize(8); + setFormat(format); + qtLogo = true; frames = 0; - setAttribute(Qt::WA_PaintOnScreen); - setAttribute(Qt::WA_NoSystemBackground); - setAutoBufferSwap(false); m_showBubbles = true; setMinimumSize(300, 250); } @@ -66,10 +72,10 @@ GLWidget::~GLWidget() void GLWidget::setScaling(int scale) { - if (scale > 50) - m_fScale = 1 + qreal(scale -50) / 50 * 0.5; - else if (scale < 50) - m_fScale = 1- (qreal(50 - scale) / 50 * 1/2); + if (scale > 30) + m_fScale = 1 + qreal(scale - 30) / 30 * 0.25; + else if (scale < 30) + m_fScale = 1 - (qreal(30 - scale) / 30 * 0.25); else m_fScale = 1; } @@ -173,12 +179,14 @@ void GLWidget::initializeGL () { initializeOpenGLFunctions(); - glClearColor(0.1f, 0.1f, 0.2f, 1.0f); - glGenTextures(1, &m_uiTexture); - m_uiTexture = bindTexture(QImage(":/qt.png")); + QImage img = QImage(":/qt.png").convertToFormat(QImage::Format_RGBA8888); + glBindTexture(GL_TEXTURE_2D, m_uiTexture); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR); + glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR); + glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, img.width(), img.height(), 0, GL_RGBA, GL_UNSIGNED_BYTE, img.constBits()); - QGLShader *vshader1 = new QGLShader(QGLShader::Vertex, this); + QOpenGLShader *vshader1 = new QOpenGLShader(QOpenGLShader::Vertex, this); const char *vsrc1 = "attribute highp vec4 vertex;\n" "attribute mediump vec3 normal;\n" @@ -195,7 +203,7 @@ void GLWidget::initializeGL () "}\n"; vshader1->compileSourceCode(vsrc1); - QGLShader *fshader1 = new QGLShader(QGLShader::Fragment, this); + QOpenGLShader *fshader1 = new QOpenGLShader(QOpenGLShader::Fragment, this); const char *fsrc1 = "varying mediump vec4 color;\n" "void main(void)\n" @@ -212,7 +220,7 @@ void GLWidget::initializeGL () normalAttr1 = program1.attributeLocation("normal"); matrixUniform1 = program1.uniformLocation("matrix"); - QGLShader *vshader2 = new QGLShader(QGLShader::Vertex); + QOpenGLShader *vshader2 = new QOpenGLShader(QOpenGLShader::Vertex); const char *vsrc2 = "attribute highp vec4 vertex;\n" "attribute highp vec4 texCoord;\n" @@ -229,7 +237,7 @@ void GLWidget::initializeGL () "}\n"; vshader2->compileSourceCode(vsrc2); - QGLShader *fshader2 = new QGLShader(QGLShader::Fragment); + QOpenGLShader *fshader2 = new QOpenGLShader(QOpenGLShader::Fragment); const char *fsrc2 = "varying highp vec4 texc;\n" "uniform sampler2D tex;\n" @@ -252,9 +260,6 @@ void GLWidget::initializeGL () matrixUniform2 = program2.uniformLocation("matrix"); textureUniform2 = program2.uniformLocation("tex"); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - m_fAngle = 0; m_fScale = 1; createGeometry(); @@ -270,12 +275,10 @@ void GLWidget::paintGL() painter.beginNativePainting(); - glClearColor(0.1f, 0.1f, 0.2f, 1.0f); + glClearColor(m_background.red() / 255.0f, m_background.green() / 255.0f, + m_background.blue() / 255.0f, m_transparent ? 0.0f : 1.0f); glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR ); - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR ); - glFrontFace(GL_CW); glCullFace(GL_FRONT); glEnable(GL_CULL_FACE); @@ -313,14 +316,12 @@ void GLWidget::paintGL() if (const int elapsed = time.elapsed()) { QString framesPerSecond; framesPerSecond.setNum(frames /(elapsed / 1000.0), 'f', 2); - painter.setPen(Qt::white); - painter.drawText(20, 40, framesPerSecond + " fps"); + painter.setPen(m_transparent ? Qt::black : Qt::white); + painter.drawText(20, 40, framesPerSecond + " paintGL calls / s"); } painter.end(); - swapBuffers(); - QMutableListIterator<Bubble*> iter(bubbles); while (iter.hasNext()) { @@ -461,3 +462,28 @@ void GLWidget::extrude(qreal x1, qreal y1, qreal x2, qreal y2) normals << n; normals << n; } + +void GLWidget::setTransparent(bool transparent) +{ + setAttribute(Qt::WA_AlwaysStackOnTop, transparent); + m_transparent = transparent; + // Call update() on the top-level window after toggling AlwayStackOnTop to make sure + // the entire backingstore is updated accordingly. + window()->update(); +} + +void GLWidget::resizeGL(int w, int h) +{ + if (m_hasButton) { + if (!m_btn) { + m_btn = new QPushButton("A widget on top.\nPress me!", this); + connect(m_btn, &QPushButton::clicked, this, &GLWidget::handleButtonPress); + } + m_btn->move(w / 2, h / 2); + } +} + +void GLWidget::handleButtonPress() +{ + m_mainWindow->addNew(); +} diff --git a/examples/opengl/hellogl_es2/glwidget.h b/examples/opengl/qopenglwidget/glwidget.h index 00073aa047..6c43ac1576 100644 --- a/examples/opengl/hellogl_es2/glwidget.h +++ b/examples/opengl/qopenglwidget/glwidget.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. @@ -41,33 +41,45 @@ #ifndef GLWIDGET_H #define GLWIDGET_H -#include <QGLWidget> +#include <QOpenGLWidget> #include <QOpenGLFunctions> -#include <QGLShaderProgram> +#include <QOpenGLShaderProgram> #include <QVector3D> #include <QMatrix4x4> #include <QTime> #include <QVector> +#include <QPushButton> class Bubble; -class GLWidget : public QGLWidget, protected QOpenGLFunctions +class MainWindow; + +class GLWidget : public QOpenGLWidget, protected QOpenGLFunctions { Q_OBJECT public: - GLWidget(QWidget *parent = 0); + GLWidget(MainWindow *mw, bool button, const QColor &background); ~GLWidget(); + public slots: void setScaling(int scale); void setLogo(); void setTexture(); void showBubbles(bool); + void setTransparent(bool transparent); + +private slots: + void handleButtonPress(); + protected: - void paintGL (); - void initializeGL (); + void resizeGL(int w, int h) Q_DECL_OVERRIDE; + void paintGL() Q_DECL_OVERRIDE; + void initializeGL() Q_DECL_OVERRIDE; + private: - GLuint m_uiTexture; - qreal m_fAngle; - qreal m_fScale; + MainWindow *m_mainWindow; + GLuint m_uiTexture; + qreal m_fAngle; + qreal m_fScale; bool m_showBubbles; void paintTexturedCube(); void paintQtLogo(); @@ -81,8 +93,8 @@ private: QList<Bubble*> bubbles; int frames; QTime time; - QGLShaderProgram program1; - QGLShaderProgram program2; + QOpenGLShaderProgram program1; + QOpenGLShaderProgram program2; int vertexAttr1; int normalAttr1; int matrixUniform1; @@ -91,5 +103,10 @@ private: int texCoordAttr2; int matrixUniform2; int textureUniform2; + bool m_transparent; + QPushButton *m_btn; + bool m_hasButton; + QColor m_background; }; + #endif diff --git a/examples/opengl/hellogl_es2/main.cpp b/examples/opengl/qopenglwidget/main.cpp index 5fb3385989..2156e60155 100644 --- a/examples/opengl/hellogl_es2/main.cpp +++ b/examples/opengl/qopenglwidget/main.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. diff --git a/examples/opengl/qopenglwidget/mainwindow.cpp b/examples/opengl/qopenglwidget/mainwindow.cpp new file mode 100644 index 0000000000..f09acacaf0 --- /dev/null +++ b/examples/opengl/qopenglwidget/mainwindow.cpp @@ -0,0 +1,152 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" + +#include <QApplication> +#include <QMenuBar> +#include <QGroupBox> +#include <QSlider> +#include <QLabel> +#include <QCheckBox> +#include <QSpinBox> + +#include "glwidget.h" + +MainWindow::MainWindow() + : m_nextX(1), m_nextY(1) +{ + GLWidget *glwidget = new GLWidget(this, true, qRgb(20, 20, 50)); + QLabel *label = new QLabel(this); + m_timer = new QTimer(this); + QSlider *slider = new QSlider(this); + slider->setOrientation(Qt::Horizontal); + + QLabel *updateLabel = new QLabel("Update interval"); + QSpinBox *updateInterval = new QSpinBox(this); + updateInterval->setSuffix(" ms"); + updateInterval->setValue(10); + updateInterval->setToolTip("Interval for the timer that calls update().\n" + "Note that on most systems the swap will block to wait for vsync\n" + "and therefore an interval < 16 ms will likely lead to a 60 FPS update rate."); + QGroupBox *updateGroupBox = new QGroupBox(this); + QCheckBox *transparent = new QCheckBox("Transparent background", this); + transparent->setToolTip("Toggles Qt::WA_AlwaysStackOnTop and transparent clear color for glClear().\n" + "Note how the button on top stacks incorrectly when enabling this."); + QHBoxLayout *updateLayout = new QHBoxLayout; + updateLayout->addWidget(updateLabel); + updateLayout->addWidget(updateInterval); + updateLayout->addWidget(transparent); + updateGroupBox->setLayout(updateLayout); + + slider->setRange(0, 50); + slider->setSliderPosition(30); + m_timer->setInterval(10); + label->setText("A QOpenGLWidget"); + label->setAlignment(Qt::AlignHCenter); + + QGroupBox * groupBox = new QGroupBox(this); + setCentralWidget(groupBox); + groupBox->setTitle("QOpenGLWidget Example"); + + m_layout = new QGridLayout(groupBox); + + m_layout->addWidget(glwidget,1,0,8,1); + m_layout->addWidget(label,9,0,1,1); + m_layout->addWidget(updateGroupBox, 10, 0, 1, 1); + m_layout->addWidget(slider, 11,0,1,1); + + groupBox->setLayout(m_layout); + + QMenu *fileMenu = new QMenu("&File"); + QMenu *helpMenu = new QMenu("&Help"); + QMenu *showMenu = new QMenu("&Show"); + menuBar()->addMenu(fileMenu); + menuBar()->addMenu(showMenu); + menuBar()->addMenu(helpMenu); + QAction *exit = new QAction("E&xit", fileMenu); + QAction *aboutQt = new QAction("About Qt", helpMenu); + QAction *showLogo = new QAction("Show 3D Logo", showMenu); + QAction *showTexture = new QAction("Show 2D Texture", showMenu); + QAction *showBubbles = new QAction("Show bubbles", showMenu); + showBubbles->setCheckable(true); + showBubbles->setChecked(true); + fileMenu->addAction(exit); + helpMenu->addAction(aboutQt); + showMenu->addAction(showLogo); + showMenu->addAction(showTexture); + showMenu->addAction(showBubbles); + + connect(exit, SIGNAL(triggered(bool)), this, SLOT(close())); + connect(aboutQt, SIGNAL(triggered(bool)), qApp, SLOT(aboutQt())); + + connect(m_timer, SIGNAL(timeout()), glwidget, SLOT(update())); + + connect(showLogo, SIGNAL(triggered(bool)), glwidget, SLOT(setLogo())); + connect(showTexture, SIGNAL(triggered(bool)), glwidget, SLOT(setTexture())); + connect(showBubbles, SIGNAL(triggered(bool)), glwidget, SLOT(showBubbles(bool))); + connect(slider, SIGNAL(valueChanged(int)), glwidget, SLOT(setScaling(int))); + connect(transparent, &QCheckBox::toggled, glwidget, &GLWidget::setTransparent); + + connect(updateInterval, SIGNAL(valueChanged(int)), this, SLOT(updateIntervalChanged(int))); + + m_timer->start(); +} + +void MainWindow::updateIntervalChanged(int value) +{ + m_timer->setInterval(value); + m_timer->start(); +} + +void MainWindow::addNew() +{ + if (m_nextY == 4) + return; + GLWidget *w = new GLWidget(this, false, qRgb(qrand() % 256, qrand() % 256, qrand() % 256)); + connect(m_timer, SIGNAL(timeout()), w, SLOT(update())); + m_layout->addWidget(w, m_nextY, m_nextX, 1, 1); + if (m_nextX == 3) { + m_nextX = 1; + ++m_nextY; + } else { + ++m_nextX; + } +} diff --git a/examples/opengl/qopenglwidget/mainwindow.h b/examples/opengl/qopenglwidget/mainwindow.h new file mode 100644 index 0000000000..9db3e8cbec --- /dev/null +++ b/examples/opengl/qopenglwidget/mainwindow.h @@ -0,0 +1,66 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include <QMainWindow> +#include <QTimer> +#include <QGridLayout> + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + MainWindow(); + void addNew(); + +private slots: + void updateIntervalChanged(int value); + +private: + QTimer *m_timer; + QGridLayout *m_layout; + int m_nextX; + int m_nextY; +}; + +#endif diff --git a/examples/opengl/qopenglwidget/qopenglwidget.pro b/examples/opengl/qopenglwidget/qopenglwidget.pro new file mode 100644 index 0000000000..0165285c02 --- /dev/null +++ b/examples/opengl/qopenglwidget/qopenglwidget.pro @@ -0,0 +1,15 @@ +QT += widgets + +SOURCES += main.cpp \ + glwidget.cpp \ + mainwindow.cpp \ + bubble.cpp + +HEADERS += glwidget.h \ + mainwindow.h \ + bubble.h + +RESOURCES += texture.qrc + +target.path = $$[QT_INSTALL_EXAMPLES]/opengl/qopenglwidget +INSTALLS += target diff --git a/examples/opengl/hellogl_es2/qt.png b/examples/opengl/qopenglwidget/qt.png Binary files differindex 79e383cf50..79e383cf50 100644 --- a/examples/opengl/hellogl_es2/qt.png +++ b/examples/opengl/qopenglwidget/qt.png diff --git a/examples/opengl/hellogl_es2/texture.qrc b/examples/opengl/qopenglwidget/texture.qrc index ff1d0e535f..ff1d0e535f 100644 --- a/examples/opengl/hellogl_es2/texture.qrc +++ b/examples/opengl/qopenglwidget/texture.qrc diff --git a/examples/opengl/threadedqopenglwidget/glwidget.cpp b/examples/opengl/threadedqopenglwidget/glwidget.cpp new file mode 100644 index 0000000000..34d97bf1bf --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/glwidget.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "glwidget.h" +#include <math.h> +#include <QGuiApplication> + +const int bubbleNum = 8; + +GLWidget::GLWidget(QWidget *parent) + : QOpenGLWidget(parent) +{ + QSurfaceFormat format; + format.setDepthBufferSize(16); + setFormat(format); + + setMinimumSize(300, 250); + + connect(this, &QOpenGLWidget::aboutToCompose, this, &GLWidget::onAboutToCompose); + connect(this, &QOpenGLWidget::frameSwapped, this, &GLWidget::onFrameSwapped); + connect(this, &QOpenGLWidget::aboutToResize, this, &GLWidget::onAboutToResize); + connect(this, &QOpenGLWidget::resized, this, &GLWidget::onResized); + + m_thread = new QThread; + m_renderer = new Renderer(this); + m_renderer->moveToThread(m_thread); + connect(m_thread, &QThread::finished, m_renderer, &QObject::deleteLater); + + connect(this, &GLWidget::renderRequested, m_renderer, &Renderer::render); + connect(m_renderer, &Renderer::contextWanted, this, &GLWidget::grabContext); + + m_thread->start(); +} + +GLWidget::~GLWidget() +{ + m_renderer->prepareExit(); + m_thread->quit(); + m_thread->wait(); + delete m_thread; +} + +void GLWidget::onAboutToCompose() +{ + // We are on the gui thread here. Composition is about to + // begin. Wait until the render thread finishes. + m_renderer->lockRenderer(); +} + +void GLWidget::onFrameSwapped() +{ + m_renderer->unlockRenderer(); + // Assuming a blocking swap, our animation is driven purely by the + // vsync in this example. + emit renderRequested(); +} + +void GLWidget::onAboutToResize() +{ + m_renderer->lockRenderer(); +} + +void GLWidget::onResized() +{ + m_renderer->unlockRenderer(); +} + +void GLWidget::grabContext() +{ + m_renderer->lockRenderer(); + QMutexLocker lock(m_renderer->grabMutex()); + context()->moveToThread(m_thread); + m_renderer->grabCond()->wakeAll(); + m_renderer->unlockRenderer(); +} + +Renderer::Renderer(GLWidget *w) + : m_inited(false), + m_glwidget(w), + m_exiting(false) +{ +} + +void Renderer::paintQtLogo() +{ + program.enableAttributeArray(normalAttr); + program.enableAttributeArray(vertexAttr); + program.setAttributeArray(vertexAttr, vertices.constData()); + program.setAttributeArray(normalAttr, normals.constData()); + glDrawArrays(GL_TRIANGLES, 0, vertices.size()); + program.disableAttributeArray(normalAttr); + program.disableAttributeArray(vertexAttr); +} + +// Some OpenGL implementations have serious issues with compiling and linking +// shaders on multiple threads concurrently. Avoid this. +Q_GLOBAL_STATIC(QMutex, initMutex) + +void Renderer::render() +{ + if (m_exiting) + return; + + QOpenGLContext *ctx = m_glwidget->context(); + if (!ctx) // QOpenGLWidget not yet initialized + return; + + // Grab the context. + m_grabMutex.lock(); + emit contextWanted(); + m_grabCond.wait(&m_grabMutex); + QMutexLocker lock(&m_renderMutex); + m_grabMutex.unlock(); + + if (m_exiting) + return; + + Q_ASSERT(ctx->thread() == QThread::currentThread()); + + // Make the context (and an offscreen surface) current for this thread. The + // QOpenGLWidget's fbo is bound in the context. + m_glwidget->makeCurrent(); + + if (!m_inited) { + m_inited = true; + initializeOpenGLFunctions(); + + QMutexLocker initLock(initMutex()); + QOpenGLShader *vshader = new QOpenGLShader(QOpenGLShader::Vertex, this); + const char *vsrc = + "attribute highp vec4 vertex;\n" + "attribute mediump vec3 normal;\n" + "uniform mediump mat4 matrix;\n" + "varying mediump vec4 color;\n" + "void main(void)\n" + "{\n" + " vec3 toLight = normalize(vec3(0.0, 0.3, 1.0));\n" + " float angle = max(dot(normal, toLight), 0.0);\n" + " vec3 col = vec3(0.40, 1.0, 0.0);\n" + " color = vec4(col * 0.2 + col * 0.8 * angle, 1.0);\n" + " color = clamp(color, 0.0, 1.0);\n" + " gl_Position = matrix * vertex;\n" + "}\n"; + vshader->compileSourceCode(vsrc); + + QOpenGLShader *fshader = new QOpenGLShader(QOpenGLShader::Fragment, this); + const char *fsrc = + "varying mediump vec4 color;\n" + "void main(void)\n" + "{\n" + " gl_FragColor = color;\n" + "}\n"; + fshader->compileSourceCode(fsrc); + + program.addShader(vshader); + program.addShader(fshader); + program.link(); + + vertexAttr = program.attributeLocation("vertex"); + normalAttr = program.attributeLocation("normal"); + matrixUniform = program.uniformLocation("matrix"); + + m_fAngle = 0; + m_fScale = 1; + createGeometry(); + + m_elapsed.start(); + } + + //qDebug("%p elapsed %lld", QThread::currentThread(), m_elapsed.restart()); + + glClearColor(0.1f, 0.2f, 0.2f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); + + glFrontFace(GL_CW); + glCullFace(GL_FRONT); + glEnable(GL_CULL_FACE); + glEnable(GL_DEPTH_TEST); + + QMatrix4x4 modelview; + modelview.rotate(m_fAngle, 0.0f, 1.0f, 0.0f); + modelview.rotate(m_fAngle, 1.0f, 0.0f, 0.0f); + modelview.rotate(m_fAngle, 0.0f, 0.0f, 1.0f); + modelview.scale(m_fScale); + modelview.translate(0.0f, -0.2f, 0.0f); + + program.bind(); + program.setUniformValue(matrixUniform, modelview); + paintQtLogo(); + program.release(); + + glDisable(GL_DEPTH_TEST); + glDisable(GL_CULL_FACE); + + m_fAngle += 1.0f; + + // Make no context current on this thread and move the QOpenGLWidget's + // context back to the gui thread. + m_glwidget->doneCurrent(); + ctx->moveToThread(qGuiApp->thread()); + + // Schedule composition. Note that this will use QueuedConnection, meaning + // that update() will be invoked on the gui thread. + QMetaObject::invokeMethod(m_glwidget, "update"); +} + +void Renderer::createGeometry() +{ + vertices.clear(); + normals.clear(); + + qreal x1 = +0.06f; + qreal y1 = -0.14f; + qreal x2 = +0.14f; + qreal y2 = -0.06f; + qreal x3 = +0.08f; + qreal y3 = +0.00f; + qreal x4 = +0.30f; + qreal y4 = +0.22f; + + quad(x1, y1, x2, y2, y2, x2, y1, x1); + quad(x3, y3, x4, y4, y4, x4, y3, x3); + + extrude(x1, y1, x2, y2); + extrude(x2, y2, y2, x2); + extrude(y2, x2, y1, x1); + extrude(y1, x1, x1, y1); + extrude(x3, y3, x4, y4); + extrude(x4, y4, y4, x4); + extrude(y4, x4, y3, x3); + + const qreal Pi = 3.14159f; + const int NumSectors = 100; + + for (int i = 0; i < NumSectors; ++i) { + qreal angle1 = (i * 2 * Pi) / NumSectors; + qreal x5 = 0.30 * sin(angle1); + qreal y5 = 0.30 * cos(angle1); + qreal x6 = 0.20 * sin(angle1); + qreal y6 = 0.20 * cos(angle1); + + qreal angle2 = ((i + 1) * 2 * Pi) / NumSectors; + qreal x7 = 0.20 * sin(angle2); + qreal y7 = 0.20 * cos(angle2); + qreal x8 = 0.30 * sin(angle2); + qreal y8 = 0.30 * cos(angle2); + + quad(x5, y5, x6, y6, x7, y7, x8, y8); + + extrude(x6, y6, x7, y7); + extrude(x8, y8, x5, y5); + } + + for (int i = 0;i < vertices.size();i++) + vertices[i] *= 2.0f; +} + +void Renderer::quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4) +{ + vertices << QVector3D(x1, y1, -0.05f); + vertices << QVector3D(x2, y2, -0.05f); + vertices << QVector3D(x4, y4, -0.05f); + + vertices << QVector3D(x3, y3, -0.05f); + vertices << QVector3D(x4, y4, -0.05f); + vertices << QVector3D(x2, y2, -0.05f); + + QVector3D n = QVector3D::normal + (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(x4 - x1, y4 - y1, 0.0f)); + + normals << n; + normals << n; + normals << n; + + normals << n; + normals << n; + normals << n; + + vertices << QVector3D(x4, y4, 0.05f); + vertices << QVector3D(x2, y2, 0.05f); + vertices << QVector3D(x1, y1, 0.05f); + + vertices << QVector3D(x2, y2, 0.05f); + vertices << QVector3D(x4, y4, 0.05f); + vertices << QVector3D(x3, y3, 0.05f); + + n = QVector3D::normal + (QVector3D(x2 - x4, y2 - y4, 0.0f), QVector3D(x1 - x4, y1 - y4, 0.0f)); + + normals << n; + normals << n; + normals << n; + + normals << n; + normals << n; + normals << n; +} + +void Renderer::extrude(qreal x1, qreal y1, qreal x2, qreal y2) +{ + vertices << QVector3D(x1, y1, +0.05f); + vertices << QVector3D(x2, y2, +0.05f); + vertices << QVector3D(x1, y1, -0.05f); + + vertices << QVector3D(x2, y2, -0.05f); + vertices << QVector3D(x1, y1, -0.05f); + vertices << QVector3D(x2, y2, +0.05f); + + QVector3D n = QVector3D::normal + (QVector3D(x2 - x1, y2 - y1, 0.0f), QVector3D(0.0f, 0.0f, -0.1f)); + + normals << n; + normals << n; + normals << n; + + normals << n; + normals << n; + normals << n; +} diff --git a/examples/opengl/threadedqopenglwidget/glwidget.h b/examples/opengl/threadedqopenglwidget/glwidget.h new file mode 100644 index 0000000000..9d746e681b --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/glwidget.h @@ -0,0 +1,124 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef GLWIDGET_H +#define GLWIDGET_H + +#include <QOpenGLWidget> +#include <QOpenGLFunctions> +#include <QOpenGLShaderProgram> +#include <QVector3D> +#include <QMatrix4x4> +#include <QThread> +#include <QMutex> +#include <QWaitCondition> +#include <QElapsedTimer> + +class GLWidget; + +class Renderer : public QObject, protected QOpenGLFunctions +{ + Q_OBJECT + +public: + Renderer(GLWidget *w); + void lockRenderer() { m_renderMutex.lock(); } + void unlockRenderer() { m_renderMutex.unlock(); } + QMutex *grabMutex() { return &m_grabMutex; } + QWaitCondition *grabCond() { return &m_grabCond; } + void prepareExit() { m_exiting = true; m_grabCond.wakeAll(); } + +signals: + void contextWanted(); + +public slots: + void render(); + +private: + void paintQtLogo(); + void createGeometry(); + void quad(qreal x1, qreal y1, qreal x2, qreal y2, qreal x3, qreal y3, qreal x4, qreal y4); + void extrude(qreal x1, qreal y1, qreal x2, qreal y2); + + bool m_inited; + qreal m_fAngle; + qreal m_fScale; + QVector<QVector3D> vertices; + QVector<QVector3D> normals; + QOpenGLShaderProgram program; + int vertexAttr; + int normalAttr; + int matrixUniform; + GLWidget *m_glwidget; + QMutex m_renderMutex; + QElapsedTimer m_elapsed; + QMutex m_grabMutex; + QWaitCondition m_grabCond; + bool m_exiting; +}; + +class GLWidget : public QOpenGLWidget +{ + Q_OBJECT +public: + GLWidget(QWidget *parent); + ~GLWidget(); + +protected: + void paintEvent(QPaintEvent *) Q_DECL_OVERRIDE { } + +signals: + void renderRequested(); + +public slots: + void grabContext(); + +private slots: + void onAboutToCompose(); + void onFrameSwapped(); + void onAboutToResize(); + void onResized(); + +private: + QThread *m_thread; + Renderer *m_renderer; +}; + +#endif diff --git a/examples/opengl/threadedqopenglwidget/main.cpp b/examples/opengl/threadedqopenglwidget/main.cpp new file mode 100644 index 0000000000..046a24d576 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/main.cpp @@ -0,0 +1,69 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QApplication> +#include <QMainWindow> +#include "mainwindow.h" +#include "glwidget.h" + +int main( int argc, char ** argv ) +{ + QApplication a( argc, argv ); + + // Two top-level windows with two QOpenGLWidget children in each. + // The rendering for the four QOpenGLWidgets happens on four separate threads. + MainWindow mw1; + mw1.setMinimumSize(800, 400); + mw1.show(); + + QScopedPointer<MainWindow> mw2; + if (!QApplication::arguments().contains(QStringLiteral("--single"))) { + mw2.reset(new MainWindow); + mw2->setMinimumSize(800, 400); + mw2->show(); + + // And a top-level. + GLWidget *bonus = new GLWidget(0); + bonus->resize(200, 200); + bonus->show(); + } + + return a.exec(); +} diff --git a/examples/opengl/threadedqopenglwidget/mainwindow.cpp b/examples/opengl/threadedqopenglwidget/mainwindow.cpp new file mode 100644 index 0000000000..29c59573cf --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/mainwindow.cpp @@ -0,0 +1,52 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the examples of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "mainwindow.h" +#include "glwidget.h" + +MainWindow::MainWindow() +{ + GLWidget *glwidget1 = new GLWidget(this); + glwidget1->resize(400, 400); + + GLWidget *glwidget2 = new GLWidget(this); + glwidget2->resize(400, 400); + glwidget2->move(400, 0); +} diff --git a/examples/opengl/hellogl_es2/mainwindow.h b/examples/opengl/threadedqopenglwidget/mainwindow.h index 3f05fd4a78..91f7580717 100644 --- a/examples/opengl/hellogl_es2/mainwindow.h +++ b/examples/opengl/threadedqopenglwidget/mainwindow.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the examples of the Qt Toolkit. @@ -41,16 +41,14 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include <QMainWindow> +#include <QWidget> -class MainWindow : public QMainWindow +class MainWindow : public QWidget { Q_OBJECT public: MainWindow(); - -private: }; #endif diff --git a/examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pro b/examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pro new file mode 100644 index 0000000000..4e941b28c4 --- /dev/null +++ b/examples/opengl/threadedqopenglwidget/threadedqopenglwidget.pro @@ -0,0 +1,11 @@ +QT += widgets + +SOURCES += main.cpp \ + glwidget.cpp \ + mainwindow.cpp + +HEADERS += glwidget.h \ + mainwindow.h + +target.path = $$[QT_INSTALL_EXAMPLES]/opengl/threadedqopenglwidget +INSTALLS += target diff --git a/examples/widgets/graphicsview/padnavigator/padnavigator.cpp b/examples/widgets/graphicsview/padnavigator/padnavigator.cpp index 82671578fb..977b65395a 100644 --- a/examples/widgets/graphicsview/padnavigator/padnavigator.cpp +++ b/examples/widgets/graphicsview/padnavigator/padnavigator.cpp @@ -292,7 +292,7 @@ PadNavigator::PadNavigator(const QSize &size, QWidget *parent) | QPainter::SmoothPixmapTransform | QPainter::TextAntialiasing); #ifndef QT_NO_OPENGL - setViewport(new QGLWidget(QGLFormat(QGL::SampleBuffers))); + setViewport(new QOpenGLWidget); #endif stateMachine->start(); diff --git a/src/corelib/global/qnamespace.h b/src/corelib/global/qnamespace.h index 3ec859dbb7..1b92a0e845 100644 --- a/src/corelib/global/qnamespace.h +++ b/src/corelib/global/qnamespace.h @@ -499,6 +499,8 @@ public: WA_X11DoNotAcceptFocus = 126, WA_MacNoShadow = 127, + WA_AlwaysStackOnTop = 128, + // Add new attributes before this line WA_AttributeCount }; diff --git a/src/corelib/global/qnamespace.qdoc b/src/corelib/global/qnamespace.qdoc index be569bcffe..f6ea3a3a81 100644 --- a/src/corelib/global/qnamespace.qdoc +++ b/src/corelib/global/qnamespace.qdoc @@ -1203,6 +1203,13 @@ to this top level window. This attribute has no effect on non-X11 platforms. + \value WA_AlwaysStackOnTop Since Qt 5.4, this value forces QOpenGLWidget and + QQuickWidget to be drawn last, on top of other widgets. Ignored for other + type of widgets. Setting this attribute breaks the stacking order, but + allows having a semi-transparent OpenGL widget with other widgets visible + underneath. It is strongly recommended to call update() on the widget's + top-level window after enabling or disabling this attribute. + \omitvalue WA_SetLayoutDirection \omitvalue WA_InputMethodTransparent \omitvalue WA_WState_CompressKeys diff --git a/src/gui/opengl/qopenglpaintdevice.cpp b/src/gui/opengl/qopenglpaintdevice.cpp index 6750458f83..96fc992b45 100644 --- a/src/gui/opengl/qopenglpaintdevice.cpp +++ b/src/gui/opengl/qopenglpaintdevice.cpp @@ -63,9 +63,9 @@ QT_BEGIN_NAMESPACE \ingroup painting-3D - The QOpenGLPaintDevice uses the current QOpenGL context to render - QPainter draw commands. It requires OpenGL (ES) 2.0 support or - higher. + The QOpenGLPaintDevice uses the \b current QOpenGL context to render + QPainter draw commands. The context is captured upon construction. It + requires support for OpenGL (ES) 2.0 or higher. \section1 Performance @@ -359,9 +359,9 @@ bool QOpenGLPaintDevice::paintFlipped() const } /*! - This virtual method is provided as a callback to allow re-binding a - target frame buffer object when different QOpenGLPaintDevice instances - are issuing draw calls alternately on the same OpenGL context. + This virtual method is provided as a callback to allow re-binding a target + frame buffer object or context when different QOpenGLPaintDevice instances + are issuing draw calls alternately. QPainter::beginNativePainting will also trigger this method. */ diff --git a/src/gui/opengl/qopenglpaintengine.cpp b/src/gui/opengl/qopenglpaintengine.cpp index de5b6be492..d0590ca521 100644 --- a/src/gui/opengl/qopenglpaintengine.cpp +++ b/src/gui/opengl/qopenglpaintengine.cpp @@ -1972,6 +1972,8 @@ bool QOpenGL2PaintEngineEx::begin(QPaintDevice *pdev) if (!d->device) return false; + d->device->ensureActiveTarget(); + if (d->device->context() != QOpenGLContext::currentContext()) { qWarning("QPainter::begin(): QOpenGLPaintDevice's context needs to be current"); return false; diff --git a/src/gui/painting/qplatformbackingstore.cpp b/src/gui/painting/qplatformbackingstore.cpp index 3728b0bddd..e5b06f499c 100644 --- a/src/gui/painting/qplatformbackingstore.cpp +++ b/src/gui/painting/qplatformbackingstore.cpp @@ -90,6 +90,7 @@ struct QBackingstoreTextureInfo { GLuint textureId; QRect rect; + bool stacksOnTop; }; Q_DECLARE_TYPEINFO(QBackingstoreTextureInfo, Q_MOVABLE_TYPE); @@ -127,6 +128,12 @@ GLuint QPlatformTextureList::textureId(int index) const return d->textures.at(index).textureId; } +bool QPlatformTextureList::stacksOnTop(int index) const +{ + Q_D(const QPlatformTextureList); + return d->textures.at(index).stacksOnTop; +} + QRect QPlatformTextureList::geometry(int index) const { Q_D(const QPlatformTextureList); @@ -148,12 +155,13 @@ bool QPlatformTextureList::isLocked() const return d->locked; } -void QPlatformTextureList::appendTexture(GLuint textureId, const QRect &geometry) +void QPlatformTextureList::appendTexture(GLuint textureId, const QRect &geometry, bool stacksOnTop) { Q_D(QPlatformTextureList); QBackingstoreTextureInfo bi; bi.textureId = textureId; bi.rect = geometry; + bi.stacksOnTop = stacksOnTop; d->textures.append(bi); } @@ -238,29 +246,39 @@ void QPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion ®i QRect windowRect(QPoint(), window->size() * window->devicePixelRatio()); + // Textures for renderToTexture widgets. for (int i = 0; i < textures->count(); ++i) { - GLuint textureId = textures->textureId(i); - funcs->glBindTexture(GL_TEXTURE_2D, textureId); - - QRect targetRect = deviceRect(textures->geometry(i), window); - QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect); - d_ptr->blitter->blit(textureId, target, QOpenGLTextureBlitter::OriginBottomLeft); + if (!textures->stacksOnTop(i)) { + QRect targetRect = deviceRect(textures->geometry(i), window); + QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect); + d_ptr->blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft); + } } - GLuint textureId = toTexture(deviceRegion(region, window), &d_ptr->textureSize); - if (!textureId) - return; - funcs->glEnable(GL_BLEND); funcs->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); - QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(QRect(QPoint(), d_ptr->textureSize), windowRect); - d_ptr->blitter->setSwizzleRB(true); - d_ptr->blitter->blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); - d_ptr->blitter->setSwizzleRB(false); + // Backingstore texture with the normal widgets. + GLuint textureId = toTexture(deviceRegion(region, window), &d_ptr->textureSize); + if (textureId) { + QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(QRect(QPoint(), d_ptr->textureSize), windowRect); + d_ptr->blitter->setSwizzleRB(true); + d_ptr->blitter->blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); + d_ptr->blitter->setSwizzleRB(false); + } + + // Textures for renderToTexture widgets that have WA_AlwaysStackOnTop set. + for (int i = 0; i < textures->count(); ++i) { + if (textures->stacksOnTop(i)) { + QRect targetRect = deviceRect(textures->geometry(i), window); + QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(targetRect, windowRect); + d_ptr->blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft); + } + } funcs->glDisable(GL_BLEND); d_ptr->blitter->release(); + context->swapBuffers(window); } diff --git a/src/gui/painting/qplatformbackingstore.h b/src/gui/painting/qplatformbackingstore.h index 4728622cac..fe0f460ed9 100644 --- a/src/gui/painting/qplatformbackingstore.h +++ b/src/gui/painting/qplatformbackingstore.h @@ -84,10 +84,11 @@ public: bool isEmpty() const { return count() == 0; } GLuint textureId(int index) const; QRect geometry(int index) const; + bool stacksOnTop(int index) const; void lock(bool on); bool isLocked() const; - void appendTexture(GLuint textureId, const QRect &geometry); + void appendTexture(GLuint textureId, const QRect &geometry, bool stacksOnTop = false); void clear(); Q_SIGNALS: diff --git a/src/opengl/qgl.cpp b/src/opengl/qgl.cpp index d1417cfde5..30e55592c5 100644 --- a/src/opengl/qgl.cpp +++ b/src/opengl/qgl.cpp @@ -3612,6 +3612,11 @@ void QGLContext::doneCurrent() except that you have the choice between using QPainter and standard OpenGL rendering commands. + \note This class is part of the legacy \l {Qt OpenGL} module and, + like the other \c QGL classes, should be avoided in the new + applications. Instead, starting from Qt 5.4, prefer using + QOpenGLWidget and the \c QOpenGL classes. + QGLWidget provides three convenient virtual functions that you can reimplement in your subclass to perform the typical OpenGL tasks: @@ -3807,7 +3812,7 @@ void QGLContext::doneCurrent() \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other countries.} - \sa QGLPixelBuffer, {Hello GL Example}, {2D Painting Example}, {Overpainting Example}, + \sa QOpenGLWidget, QGLPixelBuffer, {Hello GL Example}, {2D Painting Example}, {Overpainting Example}, {Grabber Example} */ diff --git a/src/platformsupport/eglconvenience/qeglcompositor.cpp b/src/platformsupport/eglconvenience/qeglcompositor.cpp index 028f92f3cf..0deb8d3c39 100644 --- a/src/platformsupport/eglconvenience/qeglcompositor.cpp +++ b/src/platformsupport/eglconvenience/qeglcompositor.cpp @@ -142,7 +142,6 @@ void QEGLCompositor::render(QEGLPlatformWindow *window) for (int i = 0; i < textures->count(); ++i) { uint textureId = textures->textureId(i); - glBindTexture(GL_TEXTURE_2D, textureId); QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect); const float opacity = window->window()->opacity(); @@ -160,13 +159,21 @@ void QEGLCompositor::render(QEGLPlatformWindow *window) const bool translucent = window->window()->requestedFormat().alphaBufferSize() > 0; blend.set(translucent); m_blitter->blit(textureId, target, QOpenGLTextureBlitter::OriginTopLeft); - } else { + } else if (!textures->stacksOnTop(i)) { // Texture from an FBO belonging to a QOpenGLWidget blend.set(false); m_blitter->blit(textureId, target, QOpenGLTextureBlitter::OriginBottomLeft); } } + for (int i = 0; i < textures->count(); ++i) { + if (textures->stacksOnTop(i)) { + QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(textures->geometry(i), targetWindowRect); + blend.set(true); + m_blitter->blit(textures->textureId(i), target, QOpenGLTextureBlitter::OriginBottomLeft); + } + } + m_blitter->setOpacity(1.0f); } diff --git a/src/platformsupport/eglconvenience/qeglplatformbackingstore.cpp b/src/platformsupport/eglconvenience/qeglplatformbackingstore.cpp index 3cb31e36ec..7b627f85ae 100644 --- a/src/platformsupport/eglconvenience/qeglplatformbackingstore.cpp +++ b/src/platformsupport/eglconvenience/qeglplatformbackingstore.cpp @@ -181,11 +181,8 @@ void QEGLPlatformBackingStore::composeAndFlush(QWindow *window, const QRegion &r screen->compositingContext()->makeCurrent(dstWin->window()); m_textures->clear(); - for (int i = 0; i < textures->count(); ++i) { - uint textureId = textures->textureId(i); - QRect geom = textures->geometry(i); - m_textures->appendTexture(textureId, geom); - } + for (int i = 0; i < textures->count(); ++i) + m_textures->appendTexture(textures->textureId(i), textures->geometry(i), textures->stacksOnTop(i)); updateTexture(); m_textures->appendTexture(m_bsTexture, window->geometry()); diff --git a/src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp b/src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp new file mode 100644 index 0000000000..bc279e4406 --- /dev/null +++ b/src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +class MyGLWidget : public QOpenGLWidget +{ +public: + MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { } + +protected: + void initializeGL() + { + // Set up the rendering context, load shaders and other resources, etc.: + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + ... + } + + void resizeGL(int w, int h) + { + // Update projection matrix and other size related settings: + m_projection.setToIdentity(); + m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f); + ... + } + + void paintGL() + { + // Draw the scene: + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClear(GL_COLOR_BUFFER_BIT); + ... + } + +}; +//! [0] + +//! [1] +class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + ... + void initializeGL() + { + initializeOpenGLFunctions(); + glClearColor(...); + ... + } + ... +}; +//! [1] + +//! [2] +QOpenGLWidget *widget = new QOpenGLWidget(parent); +QSurfaceFormat format; +format.setDepthBufferSize(24); +format.setStencilBufferSize(8); +format.setVersion(3, 2); +format.setProfile(QSurfaceFormat::CoreProfile); +widget->setFormat(format); // must be called before the widget or its parent window gets shown +//! [2] + +//! [3] + ... + void paintGL() + { + QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>(); + ... + f->glDrawArraysInstanced(...); + ... + } + ... +//! [3] diff --git a/src/widgets/graphicsview/qgraphicsview.cpp b/src/widgets/graphicsview/qgraphicsview.cpp index 9cd684a408..7bdfae5fec 100644 --- a/src/widgets/graphicsview/qgraphicsview.cpp +++ b/src/widgets/graphicsview/qgraphicsview.cpp @@ -2762,7 +2762,7 @@ void QGraphicsView::setupViewport(QWidget *widget) return; } - const bool isGLWidget = widget->inherits("QGLWidget"); + const bool isGLWidget = widget->inherits("QGLWidget") || widget->inherits("QOpenGLWidget"); d->accelerateScrolling = !(isGLWidget); diff --git a/src/widgets/kernel/kernel.pri b/src/widgets/kernel/kernel.pri index a9b8627beb..88c1e2595b 100644 --- a/src/widgets/kernel/kernel.pri +++ b/src/widgets/kernel/kernel.pri @@ -78,6 +78,6 @@ wince*: { } contains(QT_CONFIG, opengl) { - HEADERS += kernel/qopenglwidget_p.h + HEADERS += kernel/qopenglwidget.h SOURCES += kernel/qopenglwidget.cpp } diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp index 307d0bb909..907f69dfbd 100644 --- a/src/widgets/kernel/qopenglwidget.cpp +++ b/src/widgets/kernel/qopenglwidget.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -39,146 +39,892 @@ ** ****************************************************************************/ -#include "qopenglwidget_p.h" -#include <QOpenGLContext> -#include <QtWidgets/private/qwidget_p.h> - -#include <QOpenGLFramebufferObject> -#include <QOpenGLFunctions> -#include <QWindow> -#include <qpa/qplatformwindow.h> -#include <QDebug> +#include "qopenglwidget.h" +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFramebufferObject> +#include <QtGui/QOffscreenSurface> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QWindow> #include <QtGui/QGuiApplication> #include <QtGui/QScreen> +#include <QtGui/QOpenGLPaintDevice> +#include <QtGui/qpa/qplatformwindow.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qopenglextensions_p.h> +#include <QtGui/private/qfont_p.h> +#include <QtWidgets/private/qwidget_p.h> QT_BEGIN_NAMESPACE +/*! + \class QOpenGLWidget + \inmodule QtWidgets + \since 5.4 + + \brief The QOpenGLWidget class is a widget for rendering OpenGL graphics. + + QOpenGLWidget provides functionality for displaying OpenGL graphics + integrated into a Qt application. It is very simple to use: Make + your class inherit from it and use the subclass like any other + QWidget, except that you have the choice between using QPainter and + standard OpenGL rendering commands. + + QOpenGLWidget provides three convenient virtual functions that you + can reimplement in your subclass to perform the typical OpenGL + tasks: + + \list + \li paintGL() - Renders the OpenGL scene. Gets called whenever the widget + needs to be updated. + \li resizeGL() - Sets up the OpenGL viewport, projection, etc. Gets + called whenever the widget has been resized (and also when it + is shown for the first time because all newly created widgets get a + resize event automatically). + \li initializeGL() - Sets up the OpenGL resources and state. Gets called + once before the first time resizeGL() or paintGL() is called. + \endlist + + If you need to trigger a repaint from places other than paintGL() (a + typical example is when using \l{QTimer}{timers} to animate scenes), + you should call the widget's update() function to schedule an update. + + Your widget's OpenGL rendering context is made current when + paintGL(), resizeGL(), or initializeGL() is called. If you need to + call the standard OpenGL API functions from other places (e.g. in + your widget's constructor or in your own paint functions), you + must call makeCurrent() first. + + All rendering happens into an OpenGL framebuffer + object. makeCurrent() ensure that it is bound in the context. Keep + this in mind when creating and binding additional framebuffer + objects in the rendering code in paintGL(). Never re-bind the + framebuffer with ID 0. Instead, call defaultFramebufferObject() to + get the ID that should be bound. + + QOpenGLWidget allows using different OpenGL versions and profiles + when the platform supports it. Just set the requested format via + setFormat(). Keep in mind however that having multiple QOpenGLWidget + instances in the same window requires that they all use the same + format, or at least formats that do not make the contexts + non-sharable. + + \section1 Painting Techniques + + As described above, subclass QOpenGLWidget to render pure 3D content in the + following way: + + \list + + \li Reimplement the initializeGL() and resizeGL() functions to + set up the OpenGL state and provide a perspective transformation. + + \li Reimplement paintGL() to paint the 3D scene, calling only + OpenGL functions. + + \endlist + + It is also possible to draw 2D graphics onto a QOpenGLWidget subclass using QPainter: + + \list + + \li In paintGL(), instead of issuing OpenGL commands, construct a QPainter + object for use on the widget. + + \li Draw primitives using QPainter's member functions. + + \li Direct OpenGL commands can still be issued. However, you must make sure + these are enclosed by a call to the painter's beginNativePainting() and + endNativePainting(). + + \endlist + + When performing drawing using QPainter only, it is also possible to perform + the painting like it is done for ordinary widgets: by reimplementing paintEvent(). + + \list + + \li Reimplement the paintEvent() function. + + \li Construct a QPainter object targeting the widget. Either pass the widget to the + constructor or the QPainter::begin() function. + + \li Draw primitives using QPainter's member functions. + + \li Painting finishes then the QPainter instance is destroyed. Alternatively, + call QPainter::end() explicitly. + + \endlist + + \section1 OpenGL function calls, headers and QOpenGLFunctions + + When making OpenGL function calls, it is strongly recommended to avoid calling + the functions directly. Instead, prefer using QOpenGLFunctions (when making + portable applications) or the versioned variants (for example, + QOpenGLFunctions_3_2_Core and similar, when targeting modern, desktop-only + OpenGL). This way the application will work correctly in all Qt build + configurations, including the ones that perform dynamic OpenGL implementation + loading which means applications are not directly linking to an GL + implementation and thus direct function calls are not feasible. + + In paintGL() the current context is always accessible by caling + QOpenGLContext::currentContext(). From this context an already initialized, + ready-to-be-used QOpenGLFunctions instance is retrievable by calling + QOpenGLContext::functions(). An alternative to prefixing every GL call is to + inherit from QOpenGLFunctions and call + QOpenGLFunctions::initializeOpenGLFunctions() in initializeGL(). + + As for the OpenGL headers, note that in most cases there will be no need to + directly include any headers like GL.h. The OpenGL-related Qt headers will + include qopengl.h which will in turn include an appropriate header for the + system. This might be an OpenGL ES 3.x or 2.0 header, the highest version that + is available, or a system-provided gl.h. In addition, a copy of the extension + headers (called glext.h on some systems) is provided as part of Qt both for + OpenGL and OpenGL ES. These will get included automatically on platforms where + feasible. This means that constants and function pointer typedefs from ARB, + EXT, OES extensions are automatically available. + + \section1 Code examples + + To get started, the simplest QOpenGLWidget subclass could like like the following: + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 0 + + Alternatively, the prefixing of each and every OpenGL call can be avoidided by deriving + from QOpenGLFunctions instead: + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 1 + + To get a context compatible with a given OpenGL version or profile, or to + request depth and stencil buffers, call setFormat(): + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 2 + + With OpenGL 3.0+ contexts, when portability is not important, the versioned + QOpenGLFunctions variants give easy access to all the modern OpenGL functions + available in a given version: + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 3 + + \section1 Relation to QGLWidget + + The legacy QtOpenGL module (classes prefixed with QGL) provides a widget + called QGLWidget. QOpenGLWidget is intended to be a modern replacement for + it. Therefore, especially in new applications, the general recommendation is + to use QOpenGLWidget. + + While the API is very similar, there is an important difference between the + two: QOpenGLWidget always renders offscreen, using framebuffer + objects. QGLWidget on the other hand uses a native window and surface. The + latter causes issues when using it in complex user interfaces since, depending + on the platform, such native child widgets may have various limitations, + regarding stacking orders for example. QOpenGLWidget avoids this by not + creating a separate native window. + + \section1 Threading + + Performing offscreen rendering on worker threads, for example to generate + textures that are then used in the GUI/main thread in paintGL(), are supported + by exposing the widget's QOpenGLContext so that additional contexts sharing + with it can be created on each thread. + + Drawing directly to the QOpenGLWidget's framebuffer outside the GUI/main + thread is possible by reimplementing paintEvent() to do nothing. The context's + thread affinity has to be changed via QObject::moveToThread(). After that, + makeCurrent() and doneCurrent() are usable on the worker thread. Be careful to + move the context back to the GUI/main thread afterwards. + + Unlike QGLWidget, triggering a buffer swap just for the QOpenGLWidget is not + possible since there is no real, onscreen native surface for it. Instead, it + is up to the widget stack to manage composition and buffer swaps on the gui + thread. When a thread is done updating the framebuffer, call update() \b{on + the GUI/main thread} to schedule composition. + + Extra care has to be taken to avoid using the framebuffer when the GUI/main + thread is performing compositing. The signals aboutToCompose() and + frameSwapped() will be emitted when the composition is starting and + ending. They are emitted on the GUI/main thread. This means that by using a + direct connection aboutToCompose() can block the GUI/main thread until the + worker thread has finished its rendering. After that, the worker thread must + perform no further rendering until the frameSwapped() signal is emitted. If + this is not acceptable, the worker thread has to implement a double buffering + mechanism. This involves drawing using an alternative render target, that is + fully controlled by the thread, e.g. an additional framebuffer object, and + blitting to the QOpenGLWidget's framebuffer at a suitable time. + + \section1 Context sharing + + When multiple QOpenGLWidgets are added as children to the same top-level + widget, their contexts will share with each other. This does not apply for + QOpenGLWidget instances that belong to different windows. + + This means that all QOpenGLWidgets in the same window can access each other's + sharable resources, like textures, and there is no need for an extra "global + share" context, as was the case with QGLWidget. + + Note that QOpenGLWidget expects a standard conformant implementation of + resource sharing when it comes to the underlying graphics drivers. For + example, some drivers, in particular for mobile and embedded hardware, have + issues with setting up sharing between an existing context and others that are + created later. Some other drivers may behave in unexpected ways when trying to + utilize shared resources between different threads. + + \section1 Limitations + + Putting other widgets underneath and making the QOpenGLWidget transparent will + not lead to the expected results: The widgets underneath will not be + visible. This is because in practice the QOpenGLWidget is drawn before all + other regular, non-OpenGL widgets, and so see-through type of solutions are + not feasible. Other type of layouts, like having widgets on top of the + QOpenGLWidget, will function as expected. + + When absolutely necessary, this limitation can be overcome by setting the + Qt::WA_AlwaysStackOnTop attribute on the QOpenGLWidget. Be aware however that + this breaks stacking order, for example it will not be possible to have other + widgets on top of the QOpenGLWidget, so it should only be used in situations + where a semi-transparent QOpenGLWidget with other widgets visible underneath + is required. + + \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other + countries.} + + \sa QOpenGLFunctions +*/ + +/*! + \fn void QOpenGLWidget::aboutToCompose() + + This signal is emitted when the widget's top-level window is about to begin + composing the textures of its QOpenGLWidget children and the other widgets. +*/ + +/*! + \fn void QOpenGLWidget::frameSwapped() + + This signal is emitted after the widget's top-level window has finished + composition and returned from its potentially blocking + QOpenGLContext::swapBuffers() call. +*/ + +/*! + \fn void QOpenGLWidget::aboutToResize() + + This signal is emitted when the widget's size is changed and therefore the + framebuffer object is going to be recreated. +*/ + +/*! + \fn void QOpenGLWidget::resized() + + This signal is emitted right after the framebuffer object has been recreated + due to resizing the widget. +*/ + +class QOpenGLWidgetPaintDevice : public QOpenGLPaintDevice +{ +public: + QOpenGLWidgetPaintDevice(QOpenGLWidget *widget) : w(widget) { } + void ensureActiveTarget() Q_DECL_OVERRIDE; + +private: + QOpenGLWidget *w; +}; + class QOpenGLWidgetPrivate : public QWidgetPrivate { Q_DECLARE_PUBLIC(QOpenGLWidget) public: QOpenGLWidgetPrivate() - : fbo(0), uninitialized(true) + : context(0), + fbo(0), + resolvedFbo(0), + surface(0), + initialized(false), + fakeHidden(false), + paintDevice(0), + inBackingStorePaint(false) + { + } + + ~QOpenGLWidgetPrivate() { + reset(); } - GLuint textureId() const { return fbo ? fbo->texture() : 0; } - const QSurface *surface() const { return q_func()->window()->windowHandle(); } - QSurface *surface() { return q_func()->window()->windowHandle(); } + void reset(); + void recreateFbo(); + + GLuint textureId() const Q_DECL_OVERRIDE; + void initialize(); + void invokeUserPaint(); + void render(); - QOpenGLContext context; - QOpenGLFramebufferObject *fbo; - bool uninitialized; + QImage grabFramebuffer() Q_DECL_OVERRIDE; + void beginBackingStorePainting() Q_DECL_OVERRIDE { inBackingStorePaint = true; } + void endBackingStorePainting() Q_DECL_OVERRIDE { inBackingStorePaint = false; } + void beginCompose() Q_DECL_OVERRIDE; + void endCompose() Q_DECL_OVERRIDE; + void resizeViewportFramebuffer() Q_DECL_OVERRIDE; - int w,h; + QOpenGLContext *context; + QOpenGLFramebufferObject *fbo; + QOpenGLFramebufferObject *resolvedFbo; + QOffscreenSurface *surface; + bool initialized; + bool fakeHidden; + QOpenGLPaintDevice *paintDevice; + bool inBackingStorePaint; + QSurfaceFormat requestedFormat; }; +void QOpenGLWidgetPaintDevice::ensureActiveTarget() +{ + QOpenGLWidgetPrivate *d = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w)); + if (!d->initialized) + return; + + if (QOpenGLContext::currentContext() != d->context) + w->makeCurrent(); + else + d->fbo->bind(); +} + +GLuint QOpenGLWidgetPrivate::textureId() const +{ + return resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0); +} + +void QOpenGLWidgetPrivate::reset() +{ + delete paintDevice; + paintDevice = 0; + delete fbo; + fbo = 0; + delete resolvedFbo; + resolvedFbo = 0; + delete context; + context = 0; + delete surface; + surface = 0; + initialized = fakeHidden = inBackingStorePaint = false; +} + +void QOpenGLWidgetPrivate::recreateFbo() +{ + Q_Q(QOpenGLWidget); + + emit q->aboutToResize(); + + context->makeCurrent(surface); + + delete fbo; + delete resolvedFbo; + + int samples = get(q->window())->shareContext()->format().samples(); + QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions()); + if (!extfuncs->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) + samples = 0; + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + format.setSamples(samples); + + const QSize deviceSize = q->size() * q->devicePixelRatio(); + fbo = new QOpenGLFramebufferObject(deviceSize, format); + if (samples > 0) + resolvedFbo = new QOpenGLFramebufferObject(deviceSize); + + fbo->bind(); + + paintDevice->setSize(deviceSize); + + emit q->resized(); +} + +void QOpenGLWidgetPrivate::beginCompose() +{ + Q_Q(QOpenGLWidget); + emit q->aboutToCompose(); +} + +void QOpenGLWidgetPrivate::endCompose() +{ + Q_Q(QOpenGLWidget); + emit q->frameSwapped(); +} + void QOpenGLWidgetPrivate::initialize() { Q_Q(QOpenGLWidget); - if (!uninitialized) + if (initialized) + return; + + // Get our toplevel's context with which we will share in order to make the + // texture usable by the underlying window's backingstore. + QOpenGLContext *shareContext = get(q->window())->shareContext(); + if (!shareContext) { + qWarning("QOpenGLWidget: Cannot be used without a context shared with the toplevel."); return; - context.setShareContext(get(q->window())->shareContext()); - context.setFormat(surface()->format()); - context.create(); - context.makeCurrent(surface()); + } + + QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext); + ctx->setShareContext(shareContext); + ctx->setFormat(requestedFormat); + if (!ctx->create()) { + qWarning("QOpenGLWidget: Failed to create context"); + return; + } + + // The top-level window's surface is not good enough since it causes way too + // much trouble with regards to the QSurfaceFormat for example. So just like + // in QQuickWidget, use a dedicated QOffscreenSurface. + surface = new QOffscreenSurface; + surface->setFormat(ctx->format()); + surface->create(); + + if (!ctx->makeCurrent(surface)) { + qWarning("QOpenGLWidget: Failed to make context current"); + return; + } + + paintDevice = new QOpenGLWidgetPaintDevice(q); + paintDevice->setSize(q->size() * q->devicePixelRatio()); + paintDevice->setDevicePixelRatio(q->devicePixelRatio()); + + context = ctx.take(); + initialized = true; + q->initializeGL(); - uninitialized = false; } +void QOpenGLWidgetPrivate::invokeUserPaint() +{ + Q_Q(QOpenGLWidget); + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glViewport(0, 0, q->width() * q->devicePixelRatio(), q->height() * q->devicePixelRatio()); + + q->paintGL(); + + if (resolvedFbo) { + QRect rect(QPoint(0, 0), fbo->size()); + QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect); + } +} + +void QOpenGLWidgetPrivate::render() +{ + Q_Q(QOpenGLWidget); + + if (fakeHidden || !initialized) + return; + + q->makeCurrent(); + invokeUserPaint(); + context->functions()->glFlush(); +} + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + +QImage QOpenGLWidgetPrivate::grabFramebuffer() +{ + Q_Q(QOpenGLWidget); + if (!initialized) + return QImage(); + + render(); + q->makeCurrent(); + QImage res = qt_gl_read_framebuffer(q->size() * q->devicePixelRatio(), false, false); + + return res; +} + +void QOpenGLWidgetPrivate::resizeViewportFramebuffer() +{ + Q_Q(QOpenGLWidget); + if (!initialized) + return; + + if (!fbo || q->size() != fbo->size()) + recreateFbo(); +} + +/*! + Constructs a widget which is a child of \a parent, with widget flags set to \a f. + */ QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(*(new QOpenGLWidgetPrivate), parent, f) { Q_D(QOpenGLWidget); - d->setRenderToTexture(); + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)) + d->setRenderToTexture(); + else + qWarning("QOpenGLWidget is not supported on this platform."); } +/*! + Destroys the widget + */ QOpenGLWidget::~QOpenGLWidget() { } +/*! + Sets the requested surface \a format. + + \note Requesting an alpha buffer via this function will not lead to the desired results + and should be avoided. Instead, use Qt::WA_AlwaysStackOnTop to enable semi-transparent + QOpenGLWidget instances with other widgets visible underneath. Keep in mind however that + this breaks the stacking order, so it will no longer be possible to have other widgets + on top of the QOpenGLWidget. + + \sa format(), Qt::WA_AlwaysStackOnTop + */ +void QOpenGLWidget::setFormat(const QSurfaceFormat &format) +{ + Q_UNUSED(format); + Q_D(QOpenGLWidget); + if (d->initialized) { + qWarning("QOpenGLWidget: Already initialized, setting the format has no effect"); + return; + } + + d->requestedFormat = format; +} + +/*! + Returns the context and surface format used by this widget and its toplevel + window. + + After the widget and its toplevel have both been created, resized and shown, + this function will return the actual format of the context. This may differ + from the requested format if the request could not be fulfilled by the + platform. It is also possible to get larger color buffer sizes than + requested. + + When the widget's window and the related OpenGL resources are not yet + initialized, the return value is the format that has been set via + setFormat(). + + \sa setFormat(), context() + */ +QSurfaceFormat QOpenGLWidget::format() const +{ + Q_D(const QOpenGLWidget); + return d->initialized ? d->context->format() : d->requestedFormat; +} + +/*! + \return \e true if the widget and OpenGL resources, like the context, have + been successfully initialized. Note that the return value is always false + until the widget is shown. +*/ bool QOpenGLWidget::isValid() const { Q_D(const QOpenGLWidget); - return d->context.isValid(); + return d->initialized && d->context->isValid(); } +/*! + Prepares for rendering OpenGL content for this widget by making the + corresponding context current and binding the framebuffer object in that + context. + + It is not necessary to call this function in most cases, because it + is called automatically before invoking paintGL(). + + \sa context(), paintGL(), doneCurrent() + */ void QOpenGLWidget::makeCurrent() { Q_D(QOpenGLWidget); - d->context.makeCurrent(d->surface()); + if (!d->initialized) { + qWarning("QOpenGLWidget: Cannot make uninitialized widget current"); + return; + } + + d->context->makeCurrent(d->surface); d->fbo->bind(); } +/*! + Releases the context. + + It is not necessary to call this function in most cases, since the + widget will make sure the context is bound and released properly + when invoking paintGL(). + */ void QOpenGLWidget::doneCurrent() { Q_D(QOpenGLWidget); - d->context.doneCurrent(); + if (!d->initialized) + return; + + d->context->doneCurrent(); } -QSurfaceFormat QOpenGLWidget::format() const +/*! + \return The QOpenGLContext used by this widget or \c 0 if not yet initialized. + + \note The context and the framebuffer object used by the widget changes when + reparenting the widget via setParent(). + + \sa QOpenGLContext::setShareContext(), defaultFramebufferObject() + */ +QOpenGLContext *QOpenGLWidget::context() const { Q_D(const QOpenGLWidget); - return d->surface()->format(); + return d->context; } +/*! + \return The framebuffer object handle or \c 0 if not yet initialized. + + \note The framebuffer object belongs to the context returned by context() + and may not be accessible from other contexts. + + \note The context and the framebuffer object used by the widget changes when + reparenting the widget via setParent(). In addition, the framebuffer object + changes on each resize. + + \sa context() + */ GLuint QOpenGLWidget::defaultFramebufferObject() const { Q_D(const QOpenGLWidget); return d->fbo ? d->fbo->handle() : 0; } +/*! + This virtual function is called once before the first call to + paintGL() or resizeGL(). Reimplement it in a subclass. + + This function should set up any required OpenGL resources and state. + + There is no need to call makeCurrent() because this has already been + done when this function is called. Note however that the framebuffer + is not yet available at this stage, so avoid issuing draw calls from + here. Defer such calls to paintGL() instead. + + \sa paintGL(), resizeGL() +*/ void QOpenGLWidget::initializeGL() { - } +/*! + This virtual function is called whenever the widget has been + resized. Reimplement it in a subclass. The new size is passed in + \a w and \a h. + + There is no need to call makeCurrent() because this has already been + done when this function is called. Additionally, the framebuffer is + also bound. + + \sa initializeGL(), paintGL() +*/ void QOpenGLWidget::resizeGL(int w, int h) { Q_UNUSED(w); Q_UNUSED(h); } +/*! + This virtual function is called whenever the widget needs to be + painted. Reimplement it in a subclass. + + There is no need to call makeCurrent() because this has already + been done when this function is called. + + Before invoking this function, the context and the framebuffer are + bound, and the viewport is set up by a call to glViewport(). No + other state is set and no clearing or drawing is performed by the + framework. + + \sa initializeGL(), resizeGL() +*/ void QOpenGLWidget::paintGL() { } -void QOpenGLWidget::updateGL() +/*! + \internal + + Handles resize events that are passed in the \a event parameter. + Calls the virtual function resizeGL(). +*/ +void QOpenGLWidget::resizeEvent(QResizeEvent *e) { Q_D(QOpenGLWidget); - if (d->uninitialized || !d->surface()) + + if (e->size().isEmpty()) { + d->fakeHidden = true; + return; + } + d->fakeHidden = false; + + d->initialize(); + if (!d->initialized) return; - makeCurrent(); - paintGL(); - d->context.functions()->glFlush(); - doneCurrent(); - update(); + d->recreateFbo(); + resizeGL(width(), height()); + d->invokeUserPaint(); + d->context->functions()->glFlush(); } +/*! + Handles paint events. -void QOpenGLWidget::resizeEvent(QResizeEvent *) + Calling QWidget::update() will lead to sending a paint event \a e, + and thus invoking this function. (NB this is asynchronous and will + happen at some point after returning from update()). This function + will then, after some preparation, call the virtual paintGL() to + update the contents of the QOpenGLWidget's framebuffer. The widget's + top-level window will then composite the framebuffer's texture with + the rest of the window. +*/ +void QOpenGLWidget::paintEvent(QPaintEvent *e) { + Q_UNUSED(e); Q_D(QOpenGLWidget); - d->w = width(); - d->h = height(); - d->initialize(); + if (!d->initialized) + return; - d->context.makeCurrent(d->surface()); - delete d->fbo; // recreate when resized - d->fbo = new QOpenGLFramebufferObject(size() * devicePixelRatio(), QOpenGLFramebufferObject::CombinedDepthStencil); - d->fbo->bind(); - QOpenGLFunctions *funcs = d->context.functions(); - resizeGL(width(), height()); - paintGL(); - funcs->glFlush(); + if (updatesEnabled()) + d->render(); +} + +/*! + Renders and returns a 32-bit RGB image of the framebuffer. + + \note This is a potentially expensive operation because it relies on glReadPixels() + to read back the pixels. This may be slow and can stall the GPU pipeline. +*/ +QImage QOpenGLWidget::grabFramebuffer() +{ + Q_D(QOpenGLWidget); + return d->grabFramebuffer(); +} + +/*! + \internal +*/ +int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + Q_D(const QOpenGLWidget); + if (d->inBackingStorePaint) + return QWidget::metric(metric); + + QScreen *screen = window()->windowHandle()->screen(); + if (!screen && QGuiApplication::primaryScreen()) + screen = QGuiApplication::primaryScreen(); + + const float dpmx = qt_defaultDpiX() * 100. / 2.54; + const float dpmy = qt_defaultDpiY() * 100. / 2.54; + + switch (metric) { + case PdmWidth: + return width(); + case PdmHeight: + return height(); + case PdmDepth: + return 32; + case PdmWidthMM: + if (screen) + return width() * screen->physicalSize().width() / screen->geometry().width(); + else + return width() * 1000 / dpmx; + case PdmHeightMM: + if (screen) + return height() * screen->physicalSize().height() / screen->geometry().height(); + else + return height() * 1000 / dpmy; + case PdmNumColors: + return 0; + case PdmDpiX: + if (screen) + return qRound(screen->logicalDotsPerInchX()); + else + return qRound(dpmx * 0.0254); + case PdmDpiY: + if (screen) + return qRound(screen->logicalDotsPerInchY()); + else + return qRound(dpmy * 0.0254); + case PdmPhysicalDpiX: + if (screen) + return qRound(screen->physicalDotsPerInchX()); + else + return qRound(dpmx * 0.0254); + case PdmPhysicalDpiY: + if (screen) + return qRound(screen->physicalDotsPerInchY()); + else + return qRound(dpmy * 0.0254); + case PdmDevicePixelRatio: + if (screen) + return screen->devicePixelRatio(); + else + return 1.0; + default: + qWarning("QOpenGLWidget::metric(): unknown metric %d", metric); + return 0; + } +} + +/*! + \internal +*/ +QPaintDevice *QOpenGLWidget::redirected(QPoint *p) const +{ + Q_D(const QOpenGLWidget); + if (d->inBackingStorePaint) + return QWidget::redirected(p); + + return d->paintDevice; +} + +/*! + \internal +*/ +QPaintEngine *QOpenGLWidget::paintEngine() const +{ + Q_D(const QOpenGLWidget); + // QWidget needs to "punch a hole" into the backingstore. This needs the + // normal paint engine and device, not the GL one. So in this mode, behave + // like a normal widget. + if (d->inBackingStorePaint) + return QWidget::paintEngine(); + + if (!d->initialized) + return 0; + + return d->paintDevice->paintEngine(); } -void QOpenGLWidget::paintEvent(QPaintEvent *) +/*! + \internal +*/ +bool QOpenGLWidget::event(QEvent *e) { - qWarning("QOpenGLWidget does not support paintEvent() yet."); - return; + Q_D(QOpenGLWidget); + switch (e->type()) { + case QEvent::WindowChangeInternal: + if (d->initialized) + d->reset(); + // FALLTHROUGH + case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too + if (!d->initialized && !size().isEmpty() && window() && window()->windowHandle()) { + d->initialize(); + if (d->initialized) + d->recreateFbo(); + } + break; + default: + break; + } + return QWidget::event(e); } QT_END_NAMESPACE diff --git a/src/widgets/kernel/qopenglwidget_p.h b/src/widgets/kernel/qopenglwidget.h index 1c7f0bfeec..28d802e1f4 100644 --- a/src/widgets/kernel/qopenglwidget_p.h +++ b/src/widgets/kernel/qopenglwidget.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -39,21 +39,11 @@ ** ****************************************************************************/ -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It may change from version to version -// without notice, or even be removed. -// -// We mean it. -// #ifndef QOPENGLWIDGET_H #define QOPENGLWIDGET_H -#include <QWidget> -#include <QSurfaceFormat> - +#include <QtWidgets/QWidget> +#include <QtGui/QSurfaceFormat> #include <QtGui/qopengl.h> QT_BEGIN_NAMESPACE @@ -66,68 +56,43 @@ class Q_WIDGETS_EXPORT QOpenGLWidget : public QWidget Q_DECLARE_PRIVATE(QOpenGLWidget) public: - explicit QOpenGLWidget(QWidget* parent=0, - Qt::WindowFlags f=0); - -// This API is not finalized yet. The commented-out functions below are -// QGLWidget functions that have not been implemented for QOpenGLWidget. -// Some of them may not end up in the final version (which is planned for a -// future release of Qt). - -// explicit QOpenGLWidget(const QSurfaceFormat& format, QWidget* parent=0, -// Qt::WindowFlags f=0); + explicit QOpenGLWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); ~QOpenGLWidget(); -// void qglClearColor(const QColor& c) const; + void setFormat(const QSurfaceFormat &format); + QSurfaceFormat format() const; bool isValid() const; -// bool isSharing() const; void makeCurrent(); void doneCurrent(); -// void swapBuffers(); - - QSurfaceFormat format() const; + QOpenGLContext *context() const; GLuint defaultFramebufferObject() const; -// QPixmap renderPixmap(int w = 0, int h = 0, bool useContext = false); - QImage grabFrameBuffer(bool withAlpha = false); + QImage grabFramebuffer(); -// static QImage convertToGLFormat(const QImage& img); - -// QPaintEngine *paintEngine() const; - -// void drawTexture(const QRectF &target, GLuint textureId, GLenum textureTarget = GL_TEXTURE_2D); -// void drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget = GL_TEXTURE_2D); - -public Q_SLOTS: - void updateGL(); +Q_SIGNALS: + void aboutToCompose(); + void frameSwapped(); + void aboutToResize(); + void resized(); protected: -// bool event(QEvent *); virtual void initializeGL(); virtual void resizeGL(int w, int h); virtual void paintGL(); -// void setAutoBufferSwap(bool on); -// bool autoBufferSwap() const; + void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *e) Q_DECL_OVERRIDE; + bool event(QEvent *e) Q_DECL_OVERRIDE; - void paintEvent(QPaintEvent*); - void resizeEvent(QResizeEvent*); + int metric(QPaintDevice::PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + QPaintDevice *redirected(QPoint *p) const Q_DECL_OVERRIDE; + QPaintEngine *paintEngine() const Q_DECL_OVERRIDE; -// virtual void glInit(); -// virtual void glDraw(); - -// QOpenGLWidget(QOpenGLWidgetPrivate &dd, -// const QGLFormat &format = QGLFormat(), -// QWidget *parent = 0, -// const QOpenGLWidget* shareWidget = 0, -// Qt::WindowFlags f = 0); private: Q_DISABLE_COPY(QOpenGLWidget) - - }; QT_END_NAMESPACE diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index e692a985eb..43ce9a82f7 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -5492,18 +5492,22 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP //paint the background if ((asRoot || q->autoFillBackground() || onScreen || q->testAttribute(Qt::WA_StyledBackground)) && !q->testAttribute(Qt::WA_OpaquePaintEvent) && !q->testAttribute(Qt::WA_NoSystemBackground)) { + beginBackingStorePainting(); QPainter p(q); paintBackground(&p, toBePainted, (asRoot || onScreen) ? flags | DrawAsRoot : 0); + endBackingStorePainting(); } if (!sharedPainter) setSystemClip(pdev, toBePainted.translated(offset)); if (!onScreen && !asRoot && !isOpaque && q->testAttribute(Qt::WA_TintedBackground)) { + beginBackingStorePainting(); QPainter p(q); QColor tint = q->palette().window().color(); tint.setAlphaF(qreal(.6)); p.fillRect(toBePainted.boundingRect(), tint); + endBackingStorePainting(); } } @@ -5513,24 +5517,30 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP << "geometry ==" << QRect(q->mapTo(q->window(), QPoint(0, 0)), q->size()); #endif + bool grabbed = false; +#ifndef QT_NO_OPENGL if (renderToTexture) { // This widget renders into a texture which is composed later. We just need to // punch a hole in the backingstore, so the texture will be visible. -#ifndef QT_NO_OPENGL - QPainter p(q); - - if (backingStore) { - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(q->rect(), Qt::transparent); - } else { - // We are not drawing to a backingstore: fall back to QImage - p.drawImage(q->rect(), grabFramebuffer()); + if (!q->testAttribute(Qt::WA_AlwaysStackOnTop)) { + beginBackingStorePainting(); + QPainter p(q); + if (backingStore) { + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(q->rect(), Qt::transparent); + } else { + // We are not drawing to a backingstore: fall back to QImage + p.drawImage(q->rect(), grabFramebuffer()); + grabbed = true; + } + endBackingStorePainting(); } -#endif - } else { + } +#endif // QT_NO_OPENGL + + if (!grabbed) { //actually send the paint event - QPaintEvent e(toBePainted); - QCoreApplication::sendSpontaneousEvent(q, &e); + sendPaintEvent(toBePainted); } // Native widgets need to be marked dirty on screen so painting will be done in correct context @@ -5586,6 +5596,13 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP } } +void QWidgetPrivate::sendPaintEvent(const QRegion &toBePainted) +{ + Q_Q(QWidget); + QPaintEvent e(toBePainted); + QCoreApplication::sendSpontaneousEvent(q, &e); +} + void QWidgetPrivate::render(QPaintDevice *target, const QPoint &targetOffset, const QRegion &sourceRegion, QWidget::RenderFlags renderFlags) { @@ -11968,7 +11985,7 @@ QOpenGLContext *QWidgetPrivate::shareContext() const } QWidgetPrivate *that = const_cast<QWidgetPrivate *>(this); if (!extra->topextra->shareContext) { - QOpenGLContext *ctx = new QOpenGLContext(); + QOpenGLContext *ctx = new QOpenGLContext; ctx->setShareContext(QOpenGLContextPrivate::globalShareContext()); ctx->setFormat(extra->topextra->window->format()); ctx->create(); @@ -11978,6 +11995,24 @@ QOpenGLContext *QWidgetPrivate::shareContext() const #endif // QT_NO_OPENGL } +#ifndef QT_NO_OPENGL +void QWidgetPrivate::sendComposeStatus(QWidget *w, bool end) +{ + QWidgetPrivate *wd = QWidgetPrivate::get(w); + if (!wd->textureChildSeen) + return; + if (end) + wd->endCompose(); + else + wd->beginCompose(); + for (int i = 0; i < wd->children.size(); ++i) { + w = qobject_cast<QWidget *>(wd->children.at(i)); + if (w && !w->isWindow() && !w->isHidden() && QWidgetPrivate::get(w)->textureChildSeen) + sendComposeStatus(w, end); + } +} +#endif // QT_NO_OPENGL + Q_WIDGETS_EXPORT QWidgetData *qt_qwidget_data(QWidget *widget) { return widget->data; diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h index 6520832943..011d456a39 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h @@ -61,6 +61,7 @@ #include "QtGui/qregion.h" #include "QtGui/qinputmethod.h" #include "QtGui/qopengl.h" +#include "QtGui/qsurfaceformat.h" #include "QtWidgets/qsizepolicy.h" #include "QtWidgets/qstyle.h" #include "QtWidgets/qapplication.h" @@ -392,6 +393,7 @@ public: QWidget::RenderFlags renderFlags); void drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter = 0, QWidgetBackingStore *backingStore = 0); + void sendPaintEvent(const QRegion &toBePainted); void paintSiblingsRecursive(QPaintDevice *pdev, const QObjectList& children, int index, @@ -627,7 +629,11 @@ public: #ifndef QT_NO_OPENGL virtual GLuint textureId() const { return 0; } - virtual QImage grabFramebuffer() const { return QImage(); } + virtual QImage grabFramebuffer() { return QImage(); } + virtual void beginBackingStorePainting() { } + virtual void endBackingStorePainting() { } + virtual void beginCompose() { } + virtual void endCompose() { } void setRenderToTexture() { renderToTexture = true; setTextureChildSeen(); } void setTextureChildSeen() { @@ -642,6 +648,11 @@ public: get(parent)->setTextureChildSeen(); } } + static void sendComposeStatus(QWidget *w, bool end); + // When using a QOpenGLWidget as viewport with QAbstractScrollArea, resize events are + // filtered away from the widget. This is fine for QGLWidget but bad for QOpenGLWidget + // since the fbo must be resized. We need an alternative way to notify. + virtual void resizeViewportFramebuffer() { } #endif // Variables. diff --git a/src/widgets/kernel/qwidgetbackingstore.cpp b/src/widgets/kernel/qwidgetbackingstore.cpp index 9d024fd359..6f635611f0 100644 --- a/src/widgets/kernel/qwidgetbackingstore.cpp +++ b/src/widgets/kernel/qwidgetbackingstore.cpp @@ -74,7 +74,8 @@ extern QRegion qt_dirtyRegion(QWidget *); * \a region is the region to be updated in \a widget coordinates. */ void QWidgetBackingStore::qt_flush(QWidget *widget, const QRegion ®ion, QBackingStore *backingStore, - QWidget *tlw, const QPoint &tlwOffset, QPlatformTextureList *widgetTextures) + QWidget *tlw, const QPoint &tlwOffset, QPlatformTextureList *widgetTextures, + QWidgetBackingStore *widgetBackingStore) { #ifdef QT_NO_OPENGL Q_UNUSED(widgetTextures); @@ -92,33 +93,29 @@ void QWidgetBackingStore::qt_flush(QWidget *widget, const QRegion ®ion, QBack QWidgetBackingStore::showYellowThing(widget, region, flushUpdate * 10, false); #endif - //The performance hit by doing this should be negligible. However, be aware that - //using this FPS when you have > 1 windowsurface can give you inaccurate FPS + if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen)) + return; static bool fpsDebug = qgetenv("QT_DEBUG_FPS").toInt(); if (fpsDebug) { - static QTime time = QTime::currentTime(); - static int frames = 0; - - frames++; - - if(time.elapsed() > 5000) { - double fps = double(frames * 1000) /time.restart(); - fprintf(stderr,"FPS: %.1f\n",fps); - frames = 0; + if (!widgetBackingStore->perfFrames++) + widgetBackingStore->perfTime.start(); + if (widgetBackingStore->perfTime.elapsed() > 5000) { + double fps = double(widgetBackingStore->perfFrames * 1000) / widgetBackingStore->perfTime.restart(); + qDebug("FPS: %.1f\n", fps); + widgetBackingStore->perfFrames = 0; } } - if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen)) - return; - QPoint offset = tlwOffset; if (widget != tlw) offset += widget->mapTo(tlw, QPoint()); #ifndef QT_NO_OPENGL - if (widgetTextures) + if (widgetTextures) { + widget->window()->d_func()->sendComposeStatus(widget->window(), false); backingStore->handle()->composeAndFlush(widget->windowHandle(), region, offset, widgetTextures, tlw->d_func()->shareContext()); - else + widget->window()->d_func()->sendComposeStatus(widget->window(), true); + } else #endif backingStore->flush(region, widget->windowHandle(), offset); } @@ -270,7 +267,7 @@ void QWidgetBackingStore::unflushPaint(QWidget *widget, const QRegion &rgn) return; const QPoint offset = widget->mapTo(tlw, QPoint()); - qt_flush(widget, rgn, tlwExtra->backingStoreTracker->store, tlw, offset); + qt_flush(widget, rgn, tlwExtra->backingStoreTracker->store, tlw, offset, 0, tlw->d_func()->maybeBackingStore()); } #endif // QT_NO_PAINT_DEBUG @@ -519,6 +516,8 @@ void QWidgetBackingStore::markDirty(const QRegion &rgn, QWidget *widget, const QPoint offset = widget->mapTo(tlw, QPoint()); if (QWidgetPrivate::get(widget)->renderToTexture) { + if (!widget->d_func()->inDirtyList) + addDirtyRenderToTextureWidget(widget); if (!updateRequestSent || updateTime == UpdateNow) sendUpdateRequest(tlw, updateTime); return; @@ -613,6 +612,8 @@ void QWidgetBackingStore::markDirty(const QRect &rect, QWidget *widget, } if (QWidgetPrivate::get(widget)->renderToTexture) { + if (!widget->d_func()->inDirtyList) + addDirtyRenderToTextureWidget(widget); if (!updateRequestSent || updateTime == UpdateNow) sendUpdateRequest(tlw, updateTime); return; @@ -710,6 +711,7 @@ void QWidgetBackingStore::removeDirtyWidget(QWidget *w) dirtyWidgetsRemoveAll(w); dirtyOnScreenWidgetsRemoveAll(w); + dirtyRenderToTextureWidgets.removeAll(w); resetWidget(w); QWidgetPrivate *wd = w->d_func(); @@ -744,7 +746,8 @@ QWidgetBackingStore::QWidgetBackingStore(QWidget *topLevel) widgetTextures(0), fullUpdatePending(0), updateRequestSent(0), - textureListWatcher(0) + textureListWatcher(0), + perfFrames(0) { store = tlw->backingStore(); Q_ASSERT(store); @@ -755,9 +758,11 @@ QWidgetBackingStore::QWidgetBackingStore(QWidget *topLevel) QWidgetBackingStore::~QWidgetBackingStore() { - for (int c = 0; c < dirtyWidgets.size(); ++c) { + for (int c = 0; c < dirtyWidgets.size(); ++c) resetWidget(dirtyWidgets.at(c)); - } + for (int c = 0; c < dirtyRenderToTextureWidgets.size(); ++c) + resetWidget(dirtyRenderToTextureWidgets.at(c)); + #ifndef QT_NO_OPENGL delete dirtyOnScreenWidgets; #endif @@ -944,7 +949,7 @@ void QWidgetBackingStore::sync(QWidget *exposedWidget, const QRegion &exposedReg // Nothing to repaint. if (!isDirty() && store->size().isValid()) { - qt_flush(exposedWidget, exposedRegion, store, tlw, tlwOffset, widgetTextures); + qt_flush(exposedWidget, exposedRegion, store, tlw, tlwOffset, widgetTextures, this); return; } @@ -961,7 +966,8 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo { QWidgetPrivate *wd = QWidgetPrivate::get(widget); if (wd->renderToTexture) - widgetTextures->appendTexture(wd->textureId(), QRect(widget->mapTo(tlw, QPoint()), widget->size())); + widgetTextures->appendTexture(wd->textureId(), QRect(widget->mapTo(tlw, QPoint()), widget->size()), + widget->testAttribute(Qt::WA_AlwaysStackOnTop)); for (int i = 0; i < wd->children.size(); ++i) { QWidget *w = qobject_cast<QWidget *>(wd->children.at(i)); @@ -1125,13 +1131,47 @@ void QWidgetBackingStore::doSync() #endif if (toClean.isEmpty()) { - // Nothing to repaint. However, we might have newly exposed areas on the - // screen if this function was called from sync(QWidget *, QRegion)), so - // we have to make sure those are flushed. + // Nothing to repaint. However renderToTexture widgets are handled + // specially, they are not in the regular dirty list, in order to + // prevent triggering unnecessary backingstore painting when only the + // OpenGL content changes. Check if we have such widgets in the special + // dirty list. + for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i) { + QWidget *w = dirtyRenderToTextureWidgets.at(i); + w->d_func()->sendPaintEvent(w->rect()); + resetWidget(w); + } + dirtyRenderToTextureWidgets.clear(); + + // We might have newly exposed areas on the screen if this function was + // called from sync(QWidget *, QRegion)), so we have to make sure those + // are flushed. We also need to composite the renderToTexture widgets. flush(); + return; } + // There is something other dirty than the renderToTexture widgets. + // Now it is time to include the renderToTexture ones among the others. + if (widgetTextures && widgetTextures->count()) { + for (int i = 0; i < widgetTextures->count(); ++i) { + const QRect rect = widgetTextures->geometry(i); // mapped to the tlw already + dirty += rect; + toClean += rect; + } + } + // The dirtyRenderToTextureWidgets list is useless here, so just reset. As + // unintuitive as it is, we need to send paint events to renderToTexture + // widgets always when something (any widget) needs to be updated, even if + // the renderToTexture widget itself is clean, i.e. there was no update() + // call for it. This is because changing any widget will cause a flush and + // so a potentially blocking buffer swap for the window, and skipping paints + // for the renderToTexture widgets would make it impossible to have smoothly + // animated content in them. + for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i) + resetWidget(dirtyRenderToTextureWidgets.at(i)); + dirtyRenderToTextureWidgets.clear(); + #ifndef QT_NO_GRAPHICSVIEW if (tlw->d_func()->extra->proxyWidget) { updateStaticContentsSize(); @@ -1200,15 +1240,17 @@ void QWidgetBackingStore::flush(QWidget *widget) { if (!dirtyOnScreen.isEmpty()) { QWidget *target = widget ? widget : tlw; - qt_flush(target, dirtyOnScreen, store, tlw, tlwOffset, widgetTextures); + qt_flush(target, dirtyOnScreen, store, tlw, tlwOffset, widgetTextures, this); dirtyOnScreen = QRegion(); + if (widgetTextures && widgetTextures->count()) + return; } if (!dirtyOnScreenWidgets || dirtyOnScreenWidgets->isEmpty()) { #ifndef QT_NO_OPENGL if (widgetTextures && widgetTextures->count()) { QWidget *target = widget ? widget : tlw; - qt_flush(target, QRegion(), store, tlw, tlwOffset, widgetTextures); + qt_flush(target, QRegion(), store, tlw, tlwOffset, widgetTextures, this); } #endif return; @@ -1218,7 +1260,7 @@ void QWidgetBackingStore::flush(QWidget *widget) QWidget *w = dirtyOnScreenWidgets->at(i); QWidgetPrivate *wd = w->d_func(); Q_ASSERT(wd->needsFlush); - qt_flush(w, *wd->needsFlush, store, tlw, tlwOffset); + qt_flush(w, *wd->needsFlush, store, tlw, tlwOffset, 0, this); *wd->needsFlush = QRegion(); } dirtyOnScreenWidgets->clear(); diff --git a/src/widgets/kernel/qwidgetbackingstore_p.h b/src/widgets/kernel/qwidgetbackingstore_p.h index 88404ce205..0da85e33d5 100644 --- a/src/widgets/kernel/qwidgetbackingstore_p.h +++ b/src/widgets/kernel/qwidgetbackingstore_p.h @@ -133,6 +133,7 @@ private: QRegion dirty; // needsRepaint QRegion dirtyFromPreviousSync; QVector<QWidget *> dirtyWidgets; + QVector<QWidget *> dirtyRenderToTextureWidgets; QVector<QWidget *> *dirtyOnScreenWidgets; QList<QWidget *> staticWidgets; QPlatformTextureList *widgetTextures; @@ -143,6 +144,8 @@ private: QPoint tlwOffset; QPlatformTextureListWatcher *textureListWatcher; + QElapsedTimer perfTime; + int perfFrames; void sendUpdateRequest(QWidget *widget, UpdateTime updateTime); @@ -150,7 +153,8 @@ private: static void unflushPaint(QWidget *widget, const QRegion &rgn); static void qt_flush(QWidget *widget, const QRegion ®ion, QBackingStore *backingStore, QWidget *tlw, const QPoint &tlwOffset, - QPlatformTextureList *widgetTextures = 0); + QPlatformTextureList *widgetTextures, + QWidgetBackingStore *widgetBackingStore); void doSync(); bool bltRect(const QRect &rect, int dx, int dy, QWidget *widget); @@ -184,6 +188,16 @@ private: } } + inline void addDirtyRenderToTextureWidget(QWidget *widget) + { + if (widget && !widget->d_func()->inDirtyList && !widget->data->in_destructor) { + QWidgetPrivate *widgetPrivate = widget->d_func(); + Q_ASSERT(widgetPrivate->renderToTexture); + dirtyRenderToTextureWidgets.append(widget); + widgetPrivate->inDirtyList = true; + } + } + inline void dirtyWidgetsRemoveAll(QWidget *widget) { int i = 0; diff --git a/src/widgets/widgets/qabstractscrollarea.cpp b/src/widgets/widgets/qabstractscrollarea.cpp index e1e933cdd8..e830ac3c76 100644 --- a/src/widgets/widgets/qabstractscrollarea.cpp +++ b/src/widgets/widgets/qabstractscrollarea.cpp @@ -1191,6 +1191,10 @@ bool QAbstractScrollArea::viewportEvent(QEvent *e) case QEvent::DragMove: case QEvent::DragLeave: #endif + // QOpenGLWidget needs special support because it has to know + // its size has changed, so that it can resize its fbo. + if (e->type() == QEvent::Resize) + QWidgetPrivate::get(viewport())->resizeViewportFramebuffer(); return QFrame::event(e); case QEvent::LayoutRequest: #ifndef QT_NO_GESTURES diff --git a/tests/auto/widgets/widgets/qopenglwidget/qopenglwidget.pro b/tests/auto/widgets/widgets/qopenglwidget/qopenglwidget.pro new file mode 100644 index 0000000000..bbc6e987af --- /dev/null +++ b/tests/auto/widgets/widgets/qopenglwidget/qopenglwidget.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +CONFIG += parallel_test +TARGET = tst_qopenglwidget +QT += gui-private core-private testlib widgets + +SOURCES += tst_qopenglwidget.cpp + +win32-msvc2010:contains(QT_CONFIG, angle):CONFIG += insignificant_test # QTBUG-31611 diff --git a/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp b/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp new file mode 100644 index 0000000000..14d06e7111 --- /dev/null +++ b/tests/auto/widgets/widgets/qopenglwidget/tst_qopenglwidget.cpp @@ -0,0 +1,266 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the test suite of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** 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 Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 2.1 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 2.1 requirements +** will be met: http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Digia gives you certain additional +** rights. These rights are described in the Digia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 3.0 as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL included in the +** packaging of this file. Please review the following information to +** ensure the GNU General Public License version 3.0 requirements will be +** met: http://www.gnu.org/copyleft/gpl.html. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include <QtWidgets/QOpenGLWidget> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QPainter> +#include <QtTest/QtTest> +#include <QSignalSpy> + +class tst_QOpenGLWidget : public QObject +{ + Q_OBJECT + +private slots: + void create(); + void clearAndGrab(); + void clearAndResizeAndGrab(); + void createNonTopLevel(); + void painter(); + void reparentToAlreadyCreated(); + void reparentToNotYetCreated(); +}; + +void tst_QOpenGLWidget::create() +{ + QScopedPointer<QOpenGLWidget> w(new QOpenGLWidget); + QVERIFY(!w->isValid()); + QSignalSpy frameSwappedSpy(w.data(), SIGNAL(frameSwapped())); + w->show(); + QTest::qWaitForWindowExposed(w.data()); + QVERIFY(frameSwappedSpy.count() > 0); + + QVERIFY(w->isValid()); + QVERIFY(w->context()); + QVERIFY(w->context()->format() == w->format()); + QVERIFY(w->defaultFramebufferObject() != 0); +} + +class ClearWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ +public: + ClearWidget(QWidget *parent, int expectedWidth, int expectedHeight) + : QOpenGLWidget(parent), + m_initCalled(false), m_paintCalled(false), m_resizeCalled(false), + m_resizeOk(false), + m_w(expectedWidth), m_h(expectedHeight) { } + + void initializeGL() Q_DECL_OVERRIDE { + m_initCalled = true; + initializeOpenGLFunctions(); + } + void paintGL() Q_DECL_OVERRIDE { + m_paintCalled = true; + glClearColor(1.0f, 0.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + } + void resizeGL(int w, int h) Q_DECL_OVERRIDE { + m_resizeCalled = true; + m_resizeOk = w == m_w && h == m_h; + } + + bool m_initCalled; + bool m_paintCalled; + bool m_resizeCalled; + bool m_resizeOk; + int m_w; + int m_h; +}; + +void tst_QOpenGLWidget::clearAndGrab() +{ + QScopedPointer<ClearWidget> w(new ClearWidget(0, 800, 600)); + w->resize(800, 600); + w->show(); + QTest::qWaitForWindowExposed(w.data()); + QVERIFY(w->m_initCalled); + QVERIFY(w->m_resizeCalled); + QVERIFY(w->m_resizeOk); + QVERIFY(w->m_paintCalled); + + QImage image = w->grabFramebuffer(); + QVERIFY(!image.isNull()); + QCOMPARE(image.width(), w->width()); + QCOMPARE(image.height(), w->height()); + QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); +} + +void tst_QOpenGLWidget::clearAndResizeAndGrab() +{ + QScopedPointer<QOpenGLWidget> w(new ClearWidget(0, 640, 480)); + w->resize(640, 480); + w->show(); + QTest::qWaitForWindowExposed(w.data()); + + QImage image = w->grabFramebuffer(); + QVERIFY(!image.isNull()); + QCOMPARE(image.width(), w->width()); + QCOMPARE(image.height(), w->height()); + w->resize(800, 600); + image = w->grabFramebuffer(); + QVERIFY(!image.isNull()); + QCOMPARE(image.width(), 800); + QCOMPARE(image.height(), 600); + QCOMPARE(image.width(), w->width()); + QCOMPARE(image.height(), w->height()); + QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); +} + +void tst_QOpenGLWidget::createNonTopLevel() +{ + QWidget w; + ClearWidget *glw = new ClearWidget(&w, 600, 700); + QSignalSpy frameSwappedSpy(glw, SIGNAL(frameSwapped())); + w.resize(400, 400); + w.show(); + QTest::qWaitForWindowExposed(&w); + QVERIFY(frameSwappedSpy.count() > 0); + + QVERIFY(glw->m_resizeCalled); + glw->m_resizeCalled = false; + QVERIFY(!glw->m_resizeOk); + glw->resize(600, 700); + + QVERIFY(glw->m_initCalled); + QVERIFY(glw->m_resizeCalled); + QVERIFY(glw->m_resizeOk); + QVERIFY(glw->m_paintCalled); + + QImage image = glw->grabFramebuffer(); + QVERIFY(!image.isNull()); + QCOMPARE(image.width(), glw->width()); + QCOMPARE(image.height(), glw->height()); + QVERIFY(image.pixel(30, 40) == qRgb(255, 0, 0)); + + glw->doneCurrent(); + QVERIFY(!QOpenGLContext::currentContext()); + glw->makeCurrent(); + QVERIFY(QOpenGLContext::currentContext() == glw->context() && glw->context()); +} + +class PainterWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ +public: + PainterWidget(QWidget *parent) + : QOpenGLWidget(parent), m_clear(false) { } + + void initializeGL() Q_DECL_OVERRIDE { + initializeOpenGLFunctions(); + } + void paintGL() Q_DECL_OVERRIDE { + QPainter p(this); + QCOMPARE(p.device()->width(), width()); + QCOMPARE(p.device()->height(), height()); + p.fillRect(QRect(QPoint(0, 0), QSize(width(), height())), Qt::blue); + if (m_clear) { + p.beginNativePainting(); + glClearColor(0.0f, 1.0f, 0.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT); + p.endNativePainting(); + } + } + bool m_clear; +}; + +void tst_QOpenGLWidget::painter() +{ + QWidget w; + PainterWidget *glw = new PainterWidget(&w); + w.resize(640, 480); + glw->resize(320, 200); + w.show(); + QTest::qWaitForWindowExposed(&w); + + QImage image = glw->grabFramebuffer(); + QCOMPARE(image.width(), glw->width()); + QCOMPARE(image.height(), glw->height()); + QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255)); + + glw->m_clear = true; + image = glw->grabFramebuffer(); + QVERIFY(image.pixel(20, 10) == qRgb(0, 255, 0)); +} + +void tst_QOpenGLWidget::reparentToAlreadyCreated() +{ + QWidget w1; + PainterWidget *glw = new PainterWidget(&w1); + w1.resize(640, 480); + glw->resize(320, 200); + w1.show(); + QTest::qWaitForWindowExposed(&w1); + + QWidget w2; + w2.show(); + QTest::qWaitForWindowExposed(&w2); + + glw->setParent(&w2); + glw->show(); + + QImage image = glw->grabFramebuffer(); + QCOMPARE(image.width(), 320); + QCOMPARE(image.height(), 200); + QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255)); +} + +void tst_QOpenGLWidget::reparentToNotYetCreated() +{ + QWidget w1; + PainterWidget *glw = new PainterWidget(&w1); + w1.resize(640, 480); + glw->resize(320, 200); + w1.show(); + QTest::qWaitForWindowExposed(&w1); + + QWidget w2; + glw->setParent(&w2); + w2.show(); + QTest::qWaitForWindowExposed(&w2); + + QImage image = glw->grabFramebuffer(); + QCOMPARE(image.width(), 320); + QCOMPARE(image.height(), 200); + QVERIFY(image.pixel(20, 10) == qRgb(0, 0, 255)); +} + +QTEST_MAIN(tst_QOpenGLWidget) + +#include "tst_qopenglwidget.moc" diff --git a/tests/auto/widgets/widgets/widgets.pro b/tests/auto/widgets/widgets/widgets.pro index 29d1f7746c..423b3952d4 100644 --- a/tests/auto/widgets/widgets/widgets.pro +++ b/tests/auto/widgets/widgets/widgets.pro @@ -52,3 +52,5 @@ SUBDIRS=\ qmainwindow \ qtextedit \ qtoolbar \ + +contains(QT_CONFIG, opengl): SUBDIRS += qopenglwidget diff --git a/tests/manual/qopenglwidget/openglwidget/main.cpp b/tests/manual/qopenglwidget/openglwidget/main.cpp index 68f9be7199..0268d47767 100644 --- a/tests/manual/qopenglwidget/openglwidget/main.cpp +++ b/tests/manual/qopenglwidget/openglwidget/main.cpp @@ -45,20 +45,32 @@ #include <QMdiArea> #include <QLCDNumber> #include <QTimer> - +#include <QSurfaceFormat> +#include <QDebug> int main(int argc, char *argv[]) { QApplication a(argc, argv); + QSurfaceFormat format; + if (QCoreApplication::arguments().contains(QLatin1String("--multisample"))) + format.setSamples(4); + if (QCoreApplication::arguments().contains(QLatin1String("--coreprofile"))) { + format.setVersion(3, 2); + format.setProfile(QSurfaceFormat::CoreProfile); + } + qDebug() << "Requesting" << format; + QMdiArea w; w.resize(400,400); OpenGLWidget *glw = new OpenGLWidget; + glw->setFormat(format); w.addSubWindow(glw); glw->setMinimumSize(100,100); OpenGLWidget *glw2 = new OpenGLWidget; + glw2->setFormat(format); glw2->setMinimumSize(100,100); w.addSubWindow(glw2); @@ -69,5 +81,8 @@ int main(int argc, char *argv[]) w.show(); + if (glw->isValid()) + qDebug() << "Got" << glw->format(); + return a.exec(); } diff --git a/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp b/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp index bec89b6b41..516de35ff8 100644 --- a/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp +++ b/tests/manual/qopenglwidget/openglwidget/openglwidget.cpp @@ -58,7 +58,7 @@ #include <QtCore/qmath.h> #include <qopengl.h> -class OpenGLWidgetPrivate +class OpenGLWidgetPrivate : protected QOpenGLFunctions { public: OpenGLWidgetPrivate(QWidget *q) @@ -91,7 +91,7 @@ OpenGLWidget::OpenGLWidget(QWidget *parent) { d = new OpenGLWidgetPrivate(this); QTimer *timer = new QTimer(this); - connect(timer, SIGNAL(timeout()), this, SLOT(updateGL())); + connect(timer, SIGNAL(timeout()), this, SLOT(update())); timer->start(30); } @@ -137,6 +137,7 @@ static const char *fragmentShaderSource = void OpenGLWidgetPrivate::initialize() { + initializeOpenGLFunctions(); m_program = new QOpenGLShaderProgram; m_program->addShaderFromSourceCode(QOpenGLShader::Vertex, vertexShaderSource); m_program->addShaderFromSourceCode(QOpenGLShader::Fragment, fragmentShaderSource); @@ -157,7 +158,7 @@ void OpenGLWidgetPrivate::render() m_program->bind(); QMatrix4x4 matrix; - matrix.perspective(60, 4.0/3.0, 0.1, 100.0); + matrix.perspective(60.0f, 4.0f/3.0f, 0.1f, 100.0f); matrix.translate(0, 0, -2); matrix.rotate(100.0f * m_frame / 30/*screen()->refreshRate()*/, 0, 1, 0); diff --git a/tests/manual/qopenglwidget/openglwidget/openglwidget.h b/tests/manual/qopenglwidget/openglwidget/openglwidget.h index eaba27df26..ada01d4cc1 100644 --- a/tests/manual/qopenglwidget/openglwidget/openglwidget.h +++ b/tests/manual/qopenglwidget/openglwidget/openglwidget.h @@ -42,7 +42,7 @@ #ifndef OPENGLWIDGET_H #define OPENGLWIDGET_H -#include <QtWidgets/private/qopenglwidget_p.h> +#include <QtWidgets/QOpenGLWidget> class OpenGLWidgetPrivate; class OpenGLWidget : public QOpenGLWidget |