diff options
author | Laszlo Agocs <laszlo.agocs@digia.com> | 2014-03-20 12:58:22 +0100 |
---|---|---|
committer | Laszlo Agocs <laszlo.agocs@digia.com> | 2014-08-05 16:47:37 +0200 |
commit | e48737ae778b8ef5e8e905825a2787b97deea23d (patch) | |
tree | 220628a632981dd709e801d2855e563989b39331 | |
parent | b53e08e3356528e7d240188c8985e205a6eaa2a2 (diff) | |
download | qtbase-e48737ae778b8ef5e8e905825a2787b97deea23d.tar.gz |
Introduce QOpenGLWindow
[ChangeLog] Added QOpenGLWindow. This serves as a convenience class for
creating windows showing OpenGL content via an API similar to QGLWidget
and without any widget dependencies.
Done-with: Jorgen Lind <jorgen.lind@digia.com>
Task-number: QTBUG-36899
Change-Id: I52e9bc61acb129dbfd3841b3adeffab2dbcf7f05
Reviewed-by: Gunnar Sletta <gunnar.sletta@jollamobile.com>
-rw-r--r-- | examples/opengl/opengl.pro | 6 | ||||
-rw-r--r-- | examples/opengl/qopenglwindow/background.frag | 24 | ||||
-rw-r--r-- | examples/opengl/qopenglwindow/background_renderer.cpp | 201 | ||||
-rw-r--r-- | examples/opengl/qopenglwindow/background_renderer.h | 78 | ||||
-rw-r--r-- | examples/opengl/qopenglwindow/main.cpp | 159 | ||||
-rw-r--r-- | examples/opengl/qopenglwindow/qopenglwindow.pro | 15 | ||||
-rw-r--r-- | examples/opengl/qopenglwindow/shaders.qrc | 5 | ||||
-rw-r--r-- | src/gui/kernel/kernel.pri | 13 | ||||
-rw-r--r-- | src/gui/kernel/qopenglwindow.cpp | 615 | ||||
-rw-r--r-- | src/gui/kernel/qopenglwindow.h | 100 | ||||
-rw-r--r-- | src/gui/kernel/qpaintdevicewindow.cpp | 214 | ||||
-rw-r--r-- | src/gui/kernel/qpaintdevicewindow.h | 85 | ||||
-rw-r--r-- | src/gui/kernel/qpaintdevicewindow_p.h | 130 | ||||
-rw-r--r-- | tests/auto/gui/kernel/kernel.pro | 3 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro | 8 | ||||
-rw-r--r-- | tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp | 300 |
16 files changed, 1950 insertions, 6 deletions
diff --git a/examples/opengl/opengl.pro b/examples/opengl/opengl.pro index fb5f02c07c..8d09d21d72 100644 --- a/examples/opengl/opengl.pro +++ b/examples/opengl/opengl.pro @@ -6,7 +6,8 @@ contains(QT_CONFIG, dynamicgl) { SUBDIRS = hellowindow \ contextinfo \ qopenglwidget \ - threadedqopenglwidget + threadedqopenglwidget \ + qopenglwindow } else: !contains(QT_CONFIG, opengles2) { SUBDIRS = 2dpainting \ grabber \ @@ -23,6 +24,7 @@ contains(QT_CONFIG, dynamicgl) { cube \ textures \ qopenglwidget \ - threadedqopenglwidget + threadedqopenglwidget \ + qopenglwindow EXAMPLE_FILES = shared diff --git a/examples/opengl/qopenglwindow/background.frag b/examples/opengl/qopenglwindow/background.frag new file mode 100644 index 0000000000..067c3505dc --- /dev/null +++ b/examples/opengl/qopenglwindow/background.frag @@ -0,0 +1,24 @@ +uniform highp int currentTime; +uniform highp vec2 windowSize; + +float noise(vec2 co) +{ + return 0.5 * fract(sin(dot(co.xy, vec2(12.9898,78.233))) * 43758.5453); +} + +float curvSpeed() +{ + return mod(float(currentTime), 1000000.0) / 500.0; +} + +float curv() +{ + return 1.0 - abs((gl_FragCoord.y / (windowSize.y / 10.0) - 5.0) - sin((gl_FragCoord.x / (windowSize.x/20.0)) - curvSpeed())); +} + +void main() +{ + float coordNoise = noise(gl_FragCoord.xy); + float proximity = smoothstep(0.5, 1.0, (curv() + 1.0) * (coordNoise )); + gl_FragColor = vec4(coordNoise, coordNoise, coordNoise, 1.0) * proximity; +} diff --git a/examples/opengl/qopenglwindow/background_renderer.cpp b/examples/opengl/qopenglwindow/background_renderer.cpp new file mode 100644 index 0000000000..6cec6666b6 --- /dev/null +++ b/examples/opengl/qopenglwindow/background_renderer.cpp @@ -0,0 +1,201 @@ +/**************************************************************************** +** +** 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 "background_renderer.h" + +#include <QtCore/qmath.h> +#include <QtCore/QFileInfo> +#include <QtCore/QTime> + +#include <QtGui/QOpenGLShaderProgram> +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFunctions> + +#include <math.h> + +static const char vertex_shader[] = + "attribute highp vec3 vertexCoord;" + "void main() {" + " gl_Position = vec4(vertexCoord,1.0);" + "}"; + +static const char fragment_shader[] = + "void main() {" + " gl_FragColor = vec4(0.0,1.0,0.0,1.0);" + "}"; + +static const float vertices[] = { -1, -1, 0, + -1, 1, 0, + 1, -1, 0, + 1, 1, 0 }; + +FragmentToy::FragmentToy(const QString &fragmentSource, QObject *parent) + : QObject(parent) + , m_recompile_shaders(true) +{ + if (QFile::exists(fragmentSource)) { + QFileInfo info(fragmentSource); + m_fragment_file_last_modified = info.lastModified(); + m_fragment_file = fragmentSource; +#ifndef QT_NO_FILESYSTEMWATCHER + m_watcher.addPath(info.canonicalFilePath()); + QObject::connect(&m_watcher, &QFileSystemWatcher::fileChanged, this, &FragmentToy::fileChanged); +#endif + } +} + +void FragmentToy::draw(const QSize &windowSize) +{ + if (!m_program) + initializeOpenGLFunctions(); + + glDisable(GL_STENCIL_TEST); + glDisable(GL_DEPTH_TEST); + + glClearColor(0,0,0,0); + glClear(GL_COLOR_BUFFER_BIT); + if (!m_vao.isCreated()) + m_vao.create(); + + m_vao.bind(); + + if (!m_vertex_buffer.isCreated()) { + m_vertex_buffer.create(); + m_vertex_buffer.bind(); + m_vertex_buffer.allocate(vertices, sizeof(vertices)); + m_vertex_buffer.release(); + } + + if (!m_program) { + m_program.reset(new QOpenGLShaderProgram); + m_program->create(); + m_vertex_shader.reset(new QOpenGLShader(QOpenGLShader::Vertex)); + if (!m_vertex_shader->compileSourceCode(vertex_shader)) { + qWarning() << "Failed to compile the vertex shader:" << m_vertex_shader->log(); + } + if (!m_program->addShader(m_vertex_shader.data())) { + qWarning() << "Failed to add vertex shader to program:" << m_program->log(); + } + } + + if (!m_fragment_shader) { + QByteArray data; + if (m_fragment_file.size()) { + QFile file(m_fragment_file); + if (file.open(QIODevice::ReadOnly)) { + data = file.readAll(); + } else { + qWarning() << "Failed to load input file, falling back to default"; + data = QByteArray::fromRawData(fragment_shader, sizeof(fragment_shader)); + } + } else { + QFile qrcFile(":/background.frag"); + if (qrcFile.open(QIODevice::ReadOnly)) + data = qrcFile.readAll(); + else + data = QByteArray::fromRawData(fragment_shader, sizeof(fragment_shader)); + } + if (data.size()) { + m_fragment_shader.reset(new QOpenGLShader(QOpenGLShader::Fragment)); + if (!m_fragment_shader->compileSourceCode(data)) { + qWarning() << "Failed to compile fragment shader:" << m_fragment_shader->log(); + m_fragment_shader.reset(Q_NULLPTR); + } + } else { + qWarning() << "Unknown error, no fragment shader"; + } + + if (m_fragment_shader) { + if (!m_program->addShader(m_fragment_shader.data())) { + qWarning() << "Failed to add fragment shader to program:" << m_program->log(); + } + } + } + + if (m_recompile_shaders) { + m_recompile_shaders = false; + + if (m_program->link()) { + m_vertex_coord_pos = m_program->attributeLocation("vertexCoord"); + } else { + qWarning() << "Failed to link shader program" << m_program->log(); + } + + } + + m_program->bind(); + + m_vertex_buffer.bind(); + m_program->setAttributeBuffer("vertexCoord", GL_FLOAT, 0, 3, 0); + m_program->enableAttributeArray("vertexCoord"); + m_vertex_buffer.release(); + + float radiens = (QTime::currentTime().msecsSinceStartOfDay() / 60) / (2 * M_PI); + m_program->setUniformValue("currentTime", (uint) QDateTime::currentDateTime().toMSecsSinceEpoch()); + m_program->setUniformValue("radiens", radiens); + m_program->setUniformValue("windowSize", windowSize); + + QOpenGLContext::currentContext()->functions()->glDrawArrays(GL_TRIANGLE_STRIP, 0, 4); + + m_program->release(); + m_vao.release(); +} + +void FragmentToy::fileChanged(const QString &path) +{ + Q_UNUSED(path); + if (QFile::exists(m_fragment_file)) { + QFileInfo fragment_source(m_fragment_file); + if (fragment_source.lastModified() > m_fragment_file_last_modified) { + m_fragment_file_last_modified = fragment_source.lastModified(); + m_recompile_shaders = true; + if (m_program) { + m_program->removeShader(m_fragment_shader.data()); + m_fragment_shader.reset(Q_NULLPTR); + } + } + } else { + m_recompile_shaders = true; + if (m_program) { + m_program->removeShader(m_fragment_shader.data()); + m_fragment_shader.reset(Q_NULLPTR); + } + } +} diff --git a/examples/opengl/qopenglwindow/background_renderer.h b/examples/opengl/qopenglwindow/background_renderer.h new file mode 100644 index 0000000000..4f968325d5 --- /dev/null +++ b/examples/opengl/qopenglwindow/background_renderer.h @@ -0,0 +1,78 @@ +/**************************************************************************** +** +** 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 FRAGMENT_TOY_H +#define FRAGMENT_TOY_H + +#include <QtCore/QObject> +#include <QtCore/QFile> +#include <QtCore/QDateTime> +#include <QtCore/QFileSystemWatcher> +#include <QtGui/QOpenGLVertexArrayObject> +#include <QtGui/QOpenGLBuffer> +#include <QtGui/QOpenGLShaderProgram> +#include <QtGui/QOpenGLFunctions> + +class FragmentToy : public QObject, protected QOpenGLFunctions +{ + Q_OBJECT +public: + FragmentToy(const QString &fragmentSource, QObject *parent = 0); + + void draw(const QSize &windowSize); + +private: + void fileChanged(const QString &path); + bool m_recompile_shaders; +#ifndef QT_NO_FILESYSTEMWATCHER + QFileSystemWatcher m_watcher; +#endif + QString m_fragment_file; + QDateTime m_fragment_file_last_modified; + + QScopedPointer<QOpenGLShaderProgram> m_program; + QScopedPointer<QOpenGLShader> m_vertex_shader; + QScopedPointer<QOpenGLShader> m_fragment_shader; + QOpenGLVertexArrayObject m_vao; + QOpenGLBuffer m_vertex_buffer; + GLuint m_vertex_coord_pos; +}; + +#endif //FRAGMENT_TOY_H diff --git a/examples/opengl/qopenglwindow/main.cpp b/examples/opengl/qopenglwindow/main.cpp new file mode 100644 index 0000000000..19ac9e8a15 --- /dev/null +++ b/examples/opengl/qopenglwindow/main.cpp @@ -0,0 +1,159 @@ +/**************************************************************************** +** +** 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 <QtGui/QOpenGLWindow> +#include <QtGui/QScreen> +#include <QtGui/QPainter> +#include <QtGui/QGuiApplication> +#include <QtGui/QMatrix4x4> +#include <QtGui/QStaticText> + +#include "background_renderer.h" + +static QPainterPath painterPathForTriangle() +{ + static const QPointF bottomLeft(-1.0, -1.0); + static const QPointF top(0.0, 1.0); + static const QPointF bottomRight(1.0, -1.0); + + QPainterPath path(bottomLeft); + path.lineTo(top); + path.lineTo(bottomRight); + path.closeSubpath(); + return path; +} + +class OpenGLWindow : public QOpenGLWindow +{ + Q_OBJECT + +public: + // Use NoPartialUpdate. This means that all the rendering goes directly to + // the window surface, no additional framebuffer object stands in the + // middle. This is fine since we will clear the entire framebuffer on each + // paint. Under the hood this means that the behavior is equivalent to the + // manual makeCurrent - perform OpenGL calls - swapBuffers loop that is + // typical in pure QWindow-based applications. + OpenGLWindow() + : QOpenGLWindow(QOpenGLWindow::NoPartialUpdate) + , m_fragment_toy("background.frag") + , m_text_layout("The triangle and this text is rendered with QPainter") + { + m_view.lookAt(QVector3D(3,1,1), + QVector3D(0,0,0), + QVector3D(0,1,0)); + connect(this, SIGNAL(frameSwapped()), this, SLOT(update())); + + QLinearGradient gradient(QPointF(-1,-1), QPointF(1,1)); + gradient.setColorAt(0, Qt::red); + gradient.setColorAt(1, Qt::green); + + m_brush = QBrush(gradient); + } + +protected: + void paintGL() Q_DECL_OVERRIDE + { + m_fragment_toy.draw(size()); + + QPainter p(this); + p.setWorldTransform(m_window_normalised_matrix.toTransform()); + + QMatrix4x4 mvp = m_projection * m_view * m_model_triangle; + p.setTransform(mvp.toTransform(), true); + + p.fillPath(painterPathForTriangle(), m_brush); + + QTransform text_transform = (m_window_painter_matrix * m_view * m_model_text).toTransform(); + p.setTransform(text_transform, false); + p.setPen(QPen(Qt::white)); + m_text_layout.prepare(text_transform); + qreal x = - (m_text_layout.size().width() / 2); + qreal y = 0; + p.drawStaticText(x, y, m_text_layout); + + m_model_triangle.rotate(-1, 0, 1, 0); + m_model_text.rotate(1, 0, 1, 0); + } + + void resizeGL(int w, int h) Q_DECL_OVERRIDE + { + m_window_normalised_matrix.setToIdentity(); + m_window_normalised_matrix.translate(w / 2.0, h / 2.0); + m_window_normalised_matrix.scale(w / 2.0, -h / 2.0); + + m_window_painter_matrix.setToIdentity(); + m_window_painter_matrix.translate(w / 2.0, h / 2.0); + + m_text_layout.setTextWidth(std::max(w * 0.2, 80.0)); + + m_projection.setToIdentity(); + m_projection.perspective(45.f, qreal(w) / qreal(h), 0.1f, 100.f); + + } + +private: + QMatrix4x4 m_window_normalised_matrix; + QMatrix4x4 m_window_painter_matrix; + QMatrix4x4 m_projection; + QMatrix4x4 m_view; + QMatrix4x4 m_model_triangle; + QMatrix4x4 m_model_text; + QBrush m_brush; + + FragmentToy m_fragment_toy; + QStaticText m_text_layout; +}; + +int main (int argc, char **argv) +{ + QGuiApplication app(argc, argv); + + OpenGLWindow window; + QSurfaceFormat fmt; + fmt.setDepthBufferSize(24); + fmt.setStencilBufferSize(8); + window.setFormat(fmt); + window.show(); + + return app.exec(); +} + +#include "main.moc" diff --git a/examples/opengl/qopenglwindow/qopenglwindow.pro b/examples/opengl/qopenglwindow/qopenglwindow.pro new file mode 100644 index 0000000000..dad4b58158 --- /dev/null +++ b/examples/opengl/qopenglwindow/qopenglwindow.pro @@ -0,0 +1,15 @@ +TEMPLATE = app +TARGET = qopenglwindow +INCLUDEPATH += . + +RESOURCES += shaders.qrc + +SOURCES += \ + main.cpp \ + background_renderer.cpp + +HEADERS += \ + background_renderer.h + +target.path = $$[QT_INSTALL_EXAMPLES]/opengl/qopenglwindow +INSTALLS += target diff --git a/examples/opengl/qopenglwindow/shaders.qrc b/examples/opengl/qopenglwindow/shaders.qrc new file mode 100644 index 0000000000..64eefe2c70 --- /dev/null +++ b/examples/opengl/qopenglwindow/shaders.qrc @@ -0,0 +1,5 @@ +<!DOCTYPE RCC><RCC version="1.0"> +<qresource> + <file>background.frag</file> +</qresource> +</RCC> diff --git a/src/gui/kernel/kernel.pri b/src/gui/kernel/kernel.pri index 2aeea6288b..bc167cea57 100644 --- a/src/gui/kernel/kernel.pri +++ b/src/gui/kernel/kernel.pri @@ -69,7 +69,9 @@ HEADERS += \ kernel/qplatformscreenpageflipper.h \ kernel/qplatformsystemtrayicon.h \ kernel/qplatformsessionmanager.h \ - kernel/qpixelformat.h + kernel/qpixelformat.h \ + kernel/qpaintdevicewindow.h \ + kernel/qpaintdevicewindow_p.h SOURCES += \ kernel/qgenericpluginfactory.cpp \ @@ -121,17 +123,20 @@ SOURCES += \ kernel/qplatformsystemtrayicon.cpp \ kernel/qplatformsessionmanager.cpp \ kernel/qplatformmenu.cpp \ - kernel/qpixelformat.cpp + kernel/qpixelformat.cpp \ + kernel/qpaintdevicewindow.cpp contains(QT_CONFIG, opengl)|contains(QT_CONFIG, opengles2) { HEADERS += \ kernel/qplatformopenglcontext.h \ kernel/qopenglcontext.h \ - kernel/qopenglcontext_p.h + kernel/qopenglcontext_p.h \ + kernel/qopenglwindow.h SOURCES += \ kernel/qplatformopenglcontext.cpp \ - kernel/qopenglcontext.cpp + kernel/qopenglcontext.cpp \ + kernel/qopenglwindow.cpp } win32:HEADERS+=kernel/qwindowdefs_win.h diff --git a/src/gui/kernel/qopenglwindow.cpp b/src/gui/kernel/qopenglwindow.cpp new file mode 100644 index 0000000000..02bf4cb101 --- /dev/null +++ b/src/gui/kernel/qopenglwindow.cpp @@ -0,0 +1,615 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module 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 "qopenglwindow.h" +#include "qpaintdevicewindow_p.h" +#include <QtGui/QOpenGLFramebufferObject> +#include <QtGui/QOpenGLPaintDevice> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/private/qopengltextureblitter_p.h> +#include <QtGui/private/qopenglextensions_p.h> +#include <QtGui/QMatrix4x4> +#include <QtGui/QOffscreenSurface> + +QT_BEGIN_NAMESPACE + +/*! + \class QOpenGLWindow + \inmodule QtGui + \since 5.4 + \brief The QOpenGLWindow class is a convenience subclass of QWindow to perform OpenGL painting. + + QOpenGLWindow is an enhanced QWindow that allows easily creating windows that + perform OpenGL rendering using an API that is compatible with QOpenGLWidget + and is similar to the legacy QGLWidget. Unlike QOpenGLWidget, QOpenGLWindow + has no dependency on the widgets module and offers better performance. + + A typical application will subclass QOpenGLWindow and reimplement the following + virtual functions: + + \list + + \li initializeGL() to perform OpenGL resource initialization + + \li resizeGL() to set up the transformation matrices and other window size dependent resources + + \li paintGL() to issue OpenGL commands or draw using QPainter + + \endlist + + To schedule a repaint, call the update() function. Note that this will not + immediately result in a call to paintGL(). Calling update() multiple times in + a row will not change the behavior in any way. + + This is a slot so it can be connected to a \l QTimer::timeout() signal to + perform animation. Note however that in the modern OpenGL world it is a much + better choice to rely on synchronization to the vertical refresh rate of the + display. See \l{QSurfaceFormat::setSwapInterval()}{setSwapInterval()} on a + description of the swap interval. With a swap interval of \c 1, which is the + case on most systems by default, the + \l{QOpenGLContext::swapBuffers()}{swapBuffers()} call, that is executed + internally by QOpenGLWindow after each repaint, will block and wait for + vsync. This means that whenever the swap is done, an update can be scheduled + again by calling update(), without relying on timers. + + To request a specific configuration for the context, use setFormat() + like for any other QWindow. This allows, among others, requesting a + given OpenGL version and profile, or enabling depth and stencil + buffers. + + Unlike QWindow, QOpenGLWindow allows opening a painter on itself and perform + QPainter-based drawing. + + QOpenGLWindow supports multiple update behaviors. The default, + \c NoPartialUpdate is equivalent to a regular, OpenGL-based QWindow or the + legacy QGLWidget. In contrast, \c PartialUpdateBlit and \c PartialUpdateBlend are + more in line with QOpenGLWidget's way of working, where there is always an + extra, dedicated framebuffer object present. These modes allow, by + sacrificing some performance, redrawing only a smaller area on each paint and + having the rest of the content preserved from of the previous frame. This is + useful for applications than render incrementally using QPainter, because + this way they do not have to redraw the entire window content on each + paintGL() call. + + For more information on graphics in Qt, see \l {Graphics}. + */ + +/*! + \enum QOpenGLWindow::UpdateBehavior + + This enum describes the update strategy of the QOpenGLWindow. + + \value NoPartialUpdate Indicates that the entire window surface will + redrawn on each update and so no additional framebuffers are needed. + This is the setting used in most cases and is equivalent to how drawing + directly via QWindow would function. + + \value PartialUpdateBlit Indicates that the drawing performed in paintGL() + does not cover the entire window. In this case an extra framebuffer object + is created under the hood, and rendering performed in paintGL() will target + this framebuffer. This framebuffer is then blitted onto the window surface's + default framebuffer after each paint. This allows having QPainter-based drawing + code in paintGL() which only repaints a smaller area at a time, because, unlike + NoPartialUpdate, the previous content is preserved. + + \value PartialUpdateBlend Similar to PartialUpdateBlit, but instead of using + framebuffer blits, the contents of the extra framebuffer is rendered by + drawing a textured quad with blending enabled. This, unlike PartialUpdateBlit, + allows alpha blended content and works even when the glBlitFramebuffer is + not available. Performance-wise this setting is likely to be somewhat slower + than PartialUpdateBlit. + */ + +/*! + \fn void QOpenGLWindow::frameSwapped() + + This signal is emitted after the potentially blocking + \l{QOpenGLContext::swapBuffers()}{buffer swap} has been done. Applications + that wish to continuously repaint synchronized to the vertical refresh, + should issue an update() upon this signal. This allows for a much smoother + experience compared to the traditional usage of timers. +*/ + +// GLES2 builds won't have these constants with the suffixless names +#ifndef GL_READ_FRAMEBUFFER +#define GL_READ_FRAMEBUFFER 0x8CA8 +#endif +#ifndef GL_DRAW_FRAMEBUFFER +#define GL_DRAW_FRAMEBUFFER 0x8CA9 +#endif + +class QOpenGLWindowPaintDevice : public QOpenGLPaintDevice +{ +public: + QOpenGLWindowPaintDevice(QOpenGLWindow *window) : m_window(window) { } + void ensureActiveTarget() Q_DECL_OVERRIDE; + + QOpenGLWindow *m_window; +}; + +class QOpenGLWindowPrivate : public QPaintDeviceWindowPrivate +{ + Q_DECLARE_PUBLIC(QOpenGLWindow) +public: + QOpenGLWindowPrivate(QOpenGLWindow::UpdateBehavior updateBehavior) + : updateBehavior(updateBehavior) + , hasFboBlit(false) + { + } + + ~QOpenGLWindowPrivate() + { + Q_Q(QOpenGLWindow); + if (q->isValid()) { + q->makeCurrent(); // this works even when the platformwindow is destroyed + paintDevice.reset(0); + fbo.reset(0); + blitter.destroy(); + q->doneCurrent(); + } + } + + static QOpenGLWindowPrivate *get(QOpenGLWindow *w) { return w->d_func(); } + + void bindFBO() + { + if (updateBehavior > QOpenGLWindow::NoPartialUpdate) + fbo->bind(); + else + QOpenGLFramebufferObject::bindDefault(); + } + + void beginPaint(const QRegion ®ion) Q_DECL_OVERRIDE + { + Q_UNUSED(region); + Q_Q(QOpenGLWindow); + + if (!context) { + context.reset(new QOpenGLContext); + context->setFormat(q->requestedFormat()); + if (!context->create()) + qWarning("QOpenGLWindow::beginPaint: Failed to create context"); + if (!context->makeCurrent(q)) + qWarning("QOpenGLWindow::beginPaint: Failed to make context current"); + + paintDevice.reset(new QOpenGLWindowPaintDevice(q)); + if (updateBehavior == QOpenGLWindow::PartialUpdateBlit) + hasFboBlit = QOpenGLFramebufferObject::hasOpenGLFramebufferBlit(); + + q->initializeGL(); + } else { + context->makeCurrent(q); + } + + if (updateBehavior > QOpenGLWindow::NoPartialUpdate) { + if (!fbo || fbo->size() != q->size()) { + QOpenGLFramebufferObjectFormat fboFormat; + fboFormat.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + if (q->requestedFormat().samples() > 0) { + if (updateBehavior != QOpenGLWindow::PartialUpdateBlend) + fboFormat.setSamples(q->requestedFormat().samples()); + else + qWarning("QOpenGLWindow: PartialUpdateBlend does not support multisampling"); + } + fbo.reset(new QOpenGLFramebufferObject(q->size(), fboFormat)); + markWindowAsDirty(); + } + } else { + markWindowAsDirty(); + } + + const int deviceWidth = q->width() * q->devicePixelRatio(); + const int deviceHeight = q->height() * q->devicePixelRatio(); + paintDevice->setSize(QSize(deviceWidth, deviceHeight)); + context->functions()->glViewport(0, 0, deviceWidth, deviceHeight); + + context->functions()->glBindFramebuffer(GL_FRAMEBUFFER, context->defaultFramebufferObject()); + + q->paintUnderGL(); + + if (updateBehavior > QOpenGLWindow::NoPartialUpdate) + fbo->bind(); + } + + void endPaint() Q_DECL_OVERRIDE + { + Q_Q(QOpenGLWindow); + + if (updateBehavior > QOpenGLWindow::NoPartialUpdate) + fbo->release(); + + context->functions()->glBindFramebuffer(GL_FRAMEBUFFER, context->defaultFramebufferObject()); + + if (updateBehavior == QOpenGLWindow::PartialUpdateBlit && hasFboBlit) { + QOpenGLExtensions extensions(context.data()); + extensions.glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo->handle()); + extensions.glBindFramebuffer(GL_DRAW_FRAMEBUFFER, context->defaultFramebufferObject()); + extensions.glBlitFramebuffer(0, 0, q->width(), q->height(), + 0, 0, q->width(), q->height(), + GL_COLOR_BUFFER_BIT, GL_NEAREST); + } else if (updateBehavior > QOpenGLWindow::NoPartialUpdate) { + if (updateBehavior == QOpenGLWindow::PartialUpdateBlend) { + context->functions()->glEnable(GL_BLEND); + context->functions()->glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); + } + if (!blitter.isCreated()) + blitter.create(); + + QRect windowRect(QPoint(0, 0), q->size()); + QMatrix4x4 target = QOpenGLTextureBlitter::targetTransform(windowRect, windowRect); + blitter.bind(); + blitter.blit(fbo->texture(), target, QOpenGLTextureBlitter::OriginBottomLeft); + blitter.release(); + + if (updateBehavior == QOpenGLWindow::PartialUpdateBlend) + context->functions()->glDisable(GL_BLEND); + } + + q->paintOverGL(); + } + + void flush(const QRegion ®ion) Q_DECL_OVERRIDE + { + Q_UNUSED(region); + Q_Q(QOpenGLWindow); + context->swapBuffers(q); + emit q->frameSwapped(); + } + + QOpenGLWindow::UpdateBehavior updateBehavior; + bool hasFboBlit; + QScopedPointer<QOpenGLContext> context; + QScopedPointer<QOpenGLFramebufferObject> fbo; + QScopedPointer<QOpenGLWindowPaintDevice> paintDevice; + QOpenGLTextureBlitter blitter; + QColor backgroundColor; + QScopedPointer<QOffscreenSurface> offscreenSurface; +}; + +void QOpenGLWindowPaintDevice::ensureActiveTarget() +{ + QOpenGLWindowPrivate::get(m_window)->bindFBO(); +} + +/*! + Constructs a new QOpenGLWindow with the given \a parent and \a updateBehavior. + + \sa QOpenGLWindow::UpdateBehavior + */ +QOpenGLWindow::QOpenGLWindow(QOpenGLWindow::UpdateBehavior updateBehavior, QWindow *parent) + : QPaintDeviceWindow(*(new QOpenGLWindowPrivate(updateBehavior)), parent) +{ + setSurfaceType(QSurface::OpenGLSurface); +} + +/*! + \return the update behavior for this QOpenGLWindow. +*/ +QOpenGLWindow::UpdateBehavior QOpenGLWindow::updateBehavior() const +{ + Q_D(const QOpenGLWindow); + return d->updateBehavior; +} + +/*! + \return \c true if the window's OpenGL resources, like the context, have + been successfully initialized. Note that the return value is always \c false + until the window becomes exposed (shown). +*/ +bool QOpenGLWindow::isValid() const +{ + Q_D(const QOpenGLWindow); + return d->context && d->context->isValid(); +} + +/*! + Prepares for rendering OpenGL content for this window by making the + corresponding context current and binding the framebuffer object, if there is + one, in that context context. + + It is not necessary to call this function in most cases, because it is called + automatically before invoking paintGL(). It is provided nonetheless to support + advanced, multi-threaded scenarios where a thread different than the GUI or main + thread may want to update the surface or framebuffer contents. See QOpenGLContext + for more information on threading related issues. + + This function is suitable for calling also when the underlying platform window + is already destroyed. This means that it is safe to call this function from + a QOpenGLWindow subclass' destructor. If there is no native window anymore, + an offscreen surface is used instead. This ensures that OpenGL resource + cleanup operations in the destructor will always work, as long as + this function is called first. + + \sa QOpenGLContext, context(), paintGL(), doneCurrent() + */ +void QOpenGLWindow::makeCurrent() +{ + Q_D(QOpenGLWindow); + + if (!isValid()) + return; + + // The platform window may be destroyed at this stage and therefore + // makeCurrent() may not safely be called with 'this'. + if (handle()) { + d->context->makeCurrent(this); + } else { + if (!d->offscreenSurface) { + d->offscreenSurface.reset(new QOffscreenSurface); + d->offscreenSurface->setFormat(d->context->format()); + d->offscreenSurface->create(); + } + d->context->makeCurrent(d->offscreenSurface.data()); + } + + d->bindFBO(); +} + +/*! + 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(). + + \sa makeCurrent() + */ +void QOpenGLWindow::doneCurrent() +{ + Q_D(QOpenGLWindow); + + if (!isValid()) + return; + + d->context->doneCurrent(); +} + +/*! + \return The QOpenGLContext used by this window or \c 0 if not yet initialized. + */ +QOpenGLContext *QOpenGLWindow::context() const +{ + Q_D(const QOpenGLWindow); + return d->context.data(); +} + +/*! + The framebuffer object handle used by this window. + + When the update behavior is set to \c NoPartialUpdate, there is no separate + framebuffer object. In this case the returned value is the ID of the + default framebuffer. + + Otherwise the value of the ID of the framebuffer object or \c 0 if not + yet initialized. + */ +GLuint QOpenGLWindow::defaultFramebufferObject() const +{ + Q_D(const QOpenGLWindow); + if (d->updateBehavior > NoPartialUpdate && d->fbo) + return d->fbo->handle(); + else if (QOpenGLContext *ctx = QOpenGLContext::currentContext()) + return ctx->defaultFramebufferObject(); + else + return 0; +} + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + +/*! + 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. + + \note When used together with update behavior \c NoPartialUpdate, the returned + image may not contain the desired content when called after the front and back + buffers have been swapped (unless preserved swap is enabled in the underlying + windowing system interface). In this mode the function reads from the back + buffer and the contents of that may not match the content on the screen (the + front buffer). In this case the only place where this function can safely be + used is paintGL() or paintOverGL(). + */ +QImage QOpenGLWindow::grabFramebuffer() +{ + if (!isValid()) + return QImage(); + + makeCurrent(); + return qt_gl_read_framebuffer(size() * devicePixelRatio(), false, false); +} + +/*! + 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, in case + partial update mode is used, is not yet available at this stage, so avoid + issuing draw calls from here. Defer such calls to paintGL() instead. + + \sa paintGL(), resizeGL() + */ +void QOpenGLWindow::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, if there is one, + is bound too. + + \sa initializeGL(), paintGL() + */ +void QOpenGLWindow::resizeGL(int w, int h) +{ + Q_UNUSED(w); + Q_UNUSED(h); +} + +/*! + This virtual function is called whenever the window contents 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, if there is + one, 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. + + \note When using a partial update behavior, like \c PartialUpdateBlend, the + output of the previous paintGL() call is preserved and, after the additional + drawing perfomed in the current invocation of the function, the content is + blitted or blended over the content drawn directly to the window in + paintUnderGL(). + + \sa initializeGL(), resizeGL(), paintUnderGL(), paintOverGL(), UpdateBehavior + */ +void QOpenGLWindow::paintGL() +{ +} + +/*! + The virtual function is called before each invocation of paintGL(). + + When the update mode is set to \c NoPartialUpdate, there is no difference + between this function and paintGL(), performing rendering in either of them + leads to the same result. + + The difference becomes significant when using \c PartialUpdateBlend, where an + extra framebuffer object is used. There, paintGL() targets this additional + framebuffer object, which preserves its contents, while paintUnderGL() and + paintOverGL() target the default framebuffer, i.e. directly the window + surface, the contents of which is lost after each displayed frame. + + \note Avoid relying on this function when the update behavior is + \c PartialUpdateBlit. This mode involves blitting the extra framebuffer used by + paintGL() onto the default framebuffer after each invocation of paintGL(), + thus overwriting all drawing generated in this function. + + \sa paintGL(), paintOverGL(), UpdateBehavior + */ +void QOpenGLWindow::paintUnderGL() +{ +} + +/*! + This virtual function is called after each invocation of paintGL(). + + When the update mode is set to NoPartialUpdate, there is no difference + between this function and paintGL(), performing rendering in either of them + leads to the same result. + + Like paintUnderGL(), rendering in this function targets the default + framebuffer of the window, regardless of the update behavior. It gets called + after paintGL() has returned and the blit (PartialUpdateBlit) or quad drawing + (PartialUpdateBlend) has been done. + + \sa paintGL(), paintUnderGL(), UpdateBehavior + */ +void QOpenGLWindow::paintOverGL() +{ +} + +/*! + Paint \a event handler. Calls paintGL(). + + \sa paintGL() + */ +void QOpenGLWindow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + paintGL(); +} + +/*! + Resize \a event handler. Calls resizeGL(). + + \sa resizeGL() + */ +void QOpenGLWindow::resizeEvent(QResizeEvent *event) +{ + Q_UNUSED(event); + resizeGL(width(), height()); +} + +/*! + \internal + */ +int QOpenGLWindow::metric(PaintDeviceMetric metric) const +{ + Q_D(const QOpenGLWindow); + + switch (metric) { + case PdmDepth: + if (d->paintDevice) + return d->paintDevice->depth(); + break; + case PdmDevicePixelRatio: + if (d->paintDevice) + return d->paintDevice->devicePixelRatio(); + break; + default: + break; + } + return QPaintDeviceWindow::metric(metric); + +} + +/*! + \internal + */ +QPaintDevice *QOpenGLWindow::redirected(QPoint *) const +{ + Q_D(const QOpenGLWindow); + if (QOpenGLContext::currentContext() == d->context.data()) + return d->paintDevice.data(); + return 0; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qopenglwindow.h b/src/gui/kernel/qopenglwindow.h new file mode 100644 index 0000000000..294bd90116 --- /dev/null +++ b/src/gui/kernel/qopenglwindow.h @@ -0,0 +1,100 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QOPENGLWINDOW_H +#define QOPENGLWINDOW_H + +#include <QtGui/QPaintDeviceWindow> +#include <QtGui/QOpenGLContext> +#include <QtGui/QImage> + +QT_BEGIN_NAMESPACE + +class QOpenGLWindowPrivate; + +class Q_GUI_EXPORT QOpenGLWindow : public QPaintDeviceWindow +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QOpenGLWindow) + +public: + enum UpdateBehavior { + NoPartialUpdate, + PartialUpdateBlit, + PartialUpdateBlend + }; + + explicit QOpenGLWindow(UpdateBehavior updateBehavior = NoPartialUpdate, QWindow *parent = 0); + + UpdateBehavior updateBehavior() const; + bool isValid() const; + + void makeCurrent(); + void doneCurrent(); + + QOpenGLContext *context() const; + + GLuint defaultFramebufferObject() const; + + QImage grabFramebuffer(); + +Q_SIGNALS: + void frameSwapped(); + +protected: + virtual void initializeGL(); + virtual void resizeGL(int w, int h); + virtual void paintGL(); + virtual void paintUnderGL(); + virtual void paintOverGL(); + + void paintEvent(QPaintEvent *event) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *event) Q_DECL_OVERRIDE; + int metric(PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + QPaintDevice *redirected(QPoint *) const Q_DECL_OVERRIDE; + +private: + Q_DISABLE_COPY(QOpenGLWindow) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/kernel/qpaintdevicewindow.cpp b/src/gui/kernel/qpaintdevicewindow.cpp new file mode 100644 index 0000000000..232e8b5d6d --- /dev/null +++ b/src/gui/kernel/qpaintdevicewindow.cpp @@ -0,0 +1,214 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module 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 "qpaintdevicewindow_p.h" + +#include <QtGui/QGuiApplication> +#include <QtGui/QScreen> + +QT_BEGIN_NAMESPACE + +/*! + \class QPaintDeviceWindow + \inmodule QtGui + \since 5.4 + \brief Convenience subclass of QWindow that is also a QPaintDevice. + + QPaintDeviceWindow is like a regular QWindow, with the added functionality + of being a paint device too. Whenever the content needs to be updated, + the virtual paintEvent() function is called. Subclasses, that reimplement + this function, can then simply open a QPainter on the window. + + \note This class cannot directly be used in applications. It rather serves + as a base for subclasses like QOpenGLWindow. + + \sa QOpenGLWindow +*/ + +/*! + Marks the entire window as dirty and schedules a repaint. + + \note Subsequent calls to this function before the next paint + event will get ignored. +*/ +void QPaintDeviceWindow::update() +{ + update(QRect(QPoint(0,0), size())); +} + +/*! + Marks the \a rect of the window as dirty and schedules a repaint. + + \note Subsequent calls to this function before the next paint + event will get ignored. +*/ +void QPaintDeviceWindow::update(const QRect &rect) +{ + Q_D(QPaintDeviceWindow); + d->dirtyRegion += rect; + d->triggerUpdate(); +} + +/*! + Marks the \a region of the window as dirty and schedules a repaint. + + \note Subsequent calls to this function before the next paint + event will get ignored. +*/ +void QPaintDeviceWindow::update(const QRegion ®ion) +{ + Q_D(QPaintDeviceWindow); + d->dirtyRegion += region; + d->triggerUpdate(); +} + +/*! + Handles paint events passed in the \a event parameter. + + The default implementation does nothing. Reimplement this function to + perform painting. If necessary, the dirty area is retrievable from + the \a event. +*/ +void QPaintDeviceWindow::paintEvent(QPaintEvent *event) +{ + Q_UNUSED(event); + // Do nothing +} + +/*! + \internal + */ +int QPaintDeviceWindow::metric(PaintDeviceMetric metric) const +{ + QScreen *screen = this->screen(); + if (!screen && QGuiApplication::primaryScreen()) + screen = QGuiApplication::primaryScreen(); + + switch (metric) { + case PdmWidth: + return width(); + case PdmWidthMM: + if (screen) + return width() * screen->physicalSize().width() / screen->geometry().width(); + break; + case PdmHeight: + return height(); + case PdmHeightMM: + if (screen) + return height() * screen->physicalSize().height() / screen->geometry().height(); + break; + case PdmDpiX: + if (screen) + return qRound(screen->logicalDotsPerInchX()); + break; + case PdmDpiY: + if (screen) + return qRound(screen->logicalDotsPerInchY()); + break; + case PdmPhysicalDpiX: + if (screen) + return qRound(screen->physicalDotsPerInchX()); + break; + case PdmPhysicalDpiY: + if (screen) + return qRound(screen->physicalDotsPerInchY()); + break; + case PdmDevicePixelRatio: + if (screen) + return screen->devicePixelRatio(); + break; + default: + break; + } + + return QPaintDevice::metric(metric); +} + +/*! + \internal + */ +void QPaintDeviceWindow::exposeEvent(QExposeEvent *exposeEvent) +{ + Q_UNUSED(exposeEvent); + Q_D(QPaintDeviceWindow); + if (isExposed()) { + d->markWindowAsDirty(); + // Do not rely on exposeEvent->region() as it has some issues for the + // time being, namely that it is sometimes in local coordinates, + // sometimes relative to the parent, depending on the platform plugin. + // We require local coords here. + d->doFlush(QRect(QPoint(0, 0), size())); + } +} + +/*! + \internal + */ +bool QPaintDeviceWindow::event(QEvent *event) +{ + Q_D(QPaintDeviceWindow); + + if (event->type() == QEvent::UpdateRequest) { + d->paintEventSent = false; + d->handleUpdateEvent(); + return true; + } + + return QWindow::event(event); +} + +/*! + \internal + */ +QPaintDeviceWindow::QPaintDeviceWindow(QPaintDeviceWindowPrivate &dd, QWindow *parent) + : QWindow(dd, parent) +{ +} + +/*! + \internal + */ +QPaintEngine *QPaintDeviceWindow::paintEngine() const +{ + return 0; +} + +QT_END_NAMESPACE diff --git a/src/gui/kernel/qpaintdevicewindow.h b/src/gui/kernel/qpaintdevicewindow.h new file mode 100644 index 0000000000..ee7d07254e --- /dev/null +++ b/src/gui/kernel/qpaintdevicewindow.h @@ -0,0 +1,85 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QPAINTDEVICEWINDOW_H +#define QPAINTDEVICEWINDOW_H + +#include <QtGui/QWindow> +#include <QtGui/QPaintDevice> + +QT_BEGIN_NAMESPACE + +class QPaintDeviceWindowPrivate; +class QPaintEvent; + +class Q_GUI_EXPORT QPaintDeviceWindow : public QWindow, public QPaintDevice +{ + Q_OBJECT + Q_DECLARE_PRIVATE(QPaintDeviceWindow) + +public: + void update(const QRect &rect); + void update(const QRegion ®ion); + + using QWindow::width; + using QWindow::height; + using QWindow::devicePixelRatio; + +public Q_SLOTS: + void update(); + +protected: + virtual void paintEvent(QPaintEvent *event); + + int metric(PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + void exposeEvent(QExposeEvent *) Q_DECL_OVERRIDE; + bool event(QEvent *event) Q_DECL_OVERRIDE; + + QPaintDeviceWindow(QPaintDeviceWindowPrivate &dd, QWindow *parent); + +private: + QPaintEngine *paintEngine() const Q_DECL_OVERRIDE; + Q_DISABLE_COPY(QPaintDeviceWindow) +}; + +QT_END_NAMESPACE + +#endif diff --git a/src/gui/kernel/qpaintdevicewindow_p.h b/src/gui/kernel/qpaintdevicewindow_p.h new file mode 100644 index 0000000000..65313eda87 --- /dev/null +++ b/src/gui/kernel/qpaintdevicewindow_p.h @@ -0,0 +1,130 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtGui module 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$ +** +****************************************************************************/ + +#ifndef QPAINTDEVICEWINDOW_P_H +#define QPAINTDEVICEWINDOW_P_H + +#include <QtGui/QPaintDeviceWindow> +#include <QtCore/QCoreApplication> +#include <QtGui/private/qwindow_p.h> +#include <QtGui/QPaintEvent> + +QT_BEGIN_NAMESPACE + +class Q_GUI_EXPORT QPaintDeviceWindowPrivate : public QWindowPrivate +{ + Q_DECLARE_PUBLIC(QPaintDeviceWindow) + +public: + QPaintDeviceWindowPrivate() : paintEventSent(false) { } + + virtual void beginPaint(const QRegion ®ion) + { + Q_UNUSED(region); + } + + virtual void endPaint() + { + } + + virtual void flush(const QRegion ®ion) + { + Q_UNUSED(region); + } + + bool paint(const QRegion ®ion) + { + Q_Q(QPaintDeviceWindow); + QRegion toPaint = region & dirtyRegion; + if (toPaint.isEmpty()) + return false; + + // Clear the region now. The overridden functions may call update(). + dirtyRegion -= toPaint; + + beginPaint(toPaint); + + QPaintEvent paintEvent(toPaint); + q->paintEvent(&paintEvent); + + endPaint(); + + return true; + } + + void triggerUpdate() + { + Q_Q(QPaintDeviceWindow); + if (!paintEventSent) { + QCoreApplication::postEvent(q, new QEvent(QEvent::UpdateRequest)); + paintEventSent = true; + } + } + + void doFlush(const QRegion ®ion) + { + QRegion toFlush = region; + if (paint(toFlush)) + flush(toFlush); + } + + void handleUpdateEvent() + { + if (dirtyRegion.isEmpty()) + return; + doFlush(dirtyRegion); + } + + void markWindowAsDirty() + { + Q_Q(QPaintDeviceWindow); + dirtyRegion += QRect(QPoint(0, 0), q->size()); + } + +private: + QRegion dirtyRegion; + bool paintEventSent; +}; + + +QT_END_NAMESPACE + +#endif //QPAINTDEVICEWINDOW_P_H diff --git a/tests/auto/gui/kernel/kernel.pro b/tests/auto/gui/kernel/kernel.pro index bbcdd91ea3..7d8af15b25 100644 --- a/tests/auto/gui/kernel/kernel.pro +++ b/tests/auto/gui/kernel/kernel.pro @@ -21,6 +21,7 @@ SUBDIRS=\ qwindow \ qguiapplication \ qpixelformat \ + qopenglwindow !qtHaveModule(widgets): SUBDIRS -= \ qmouseevent_modal \ @@ -28,3 +29,5 @@ SUBDIRS=\ !qtHaveModule(network): SUBDIRS -= \ qguieventloop + +!contains(QT_CONFIG, opengl(es2)?): SUBDIRS -= qopenglwindow diff --git a/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro b/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro new file mode 100644 index 0000000000..43a62b4811 --- /dev/null +++ b/tests/auto/gui/kernel/qopenglwindow/qopenglwindow.pro @@ -0,0 +1,8 @@ +CONFIG += testcase +TARGET = tst_qopenglwindow + +QT += core-private gui-private testlib + +SOURCES += tst_qopenglwindow.cpp + +win32:CONFIG+=insignificant_test # QTBUG-28264 diff --git a/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp b/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp new file mode 100644 index 0000000000..34f3e11c27 --- /dev/null +++ b/tests/auto/gui/kernel/qopenglwindow/tst_qopenglwindow.cpp @@ -0,0 +1,300 @@ +/**************************************************************************** +** +** 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 <QtGui/QOpenGLWindow> +#include <QtTest/QtTest> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QOpenGLContext> +#include <QtGui/QPainter> + +class tst_QOpenGLWindow : public QObject +{ + Q_OBJECT + +private slots: + void create(); + void basic(); + void painter(); + void partial_data(); + void partial(); + void underOver(); +}; + +void tst_QOpenGLWindow::create() +{ + QOpenGLWindow w; + QVERIFY(!w.isValid()); + + w.resize(640, 480); + w.show(); + + QTest::qWaitForWindowExposed(&w); + + QVERIFY(w.isValid()); +} + +class Window : public QOpenGLWindow +{ +public: + void reset() { + initCount = resizeCount = paintCount = 0; + } + + void initializeGL() Q_DECL_OVERRIDE { + ++initCount; + } + + void resizeGL(int w, int h) Q_DECL_OVERRIDE { + ++resizeCount; + QCOMPARE(w, size().width()); + QCOMPARE(h, size().height()); + } + + void paintGL() Q_DECL_OVERRIDE { + ++paintCount; + + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QVERIFY(ctx); + QCOMPARE(ctx, context()); + QOpenGLFunctions *f = ctx->functions(); + QVERIFY(f); + + f->glClearColor(1, 0, 0, 1); + f->glClear(GL_COLOR_BUFFER_BIT); + + img = grabFramebuffer(); + } + + int initCount; + int resizeCount; + int paintCount; + QImage img; +}; + +void tst_QOpenGLWindow::basic() +{ + Window w; + w.reset(); + w.resize(640, 480); + w.show(); + QTest::qWaitForWindowExposed(&w); + + // Check that the virtuals are invoked. + QCOMPARE(w.initCount, 1); + int resCount = w.resizeCount; + QVERIFY(resCount >= 1); + QVERIFY(w.paintCount >= 1); + + // Check that something has been drawn; + QCOMPARE(w.img.size(), w.size()); + QVERIFY(w.img.pixel(5, 5) == qRgb(255, 0, 0)); + + // Check that the viewport was properly set. + w.makeCurrent(); + GLint v[4] = { 0, 0, 0, 0 }; + w.context()->functions()->glGetIntegerv(GL_VIEWPORT, v); + QCOMPARE(v[0], 0); + QCOMPARE(v[1], 0); + QCOMPARE(v[2], GLint(w.width() * w.devicePixelRatio())); + QCOMPARE(v[3], GLint(w.height() * w.devicePixelRatio())); + w.doneCurrent(); + + // Check that a future resize triggers resizeGL. + w.resize(800, 600); + int maxWait = 1000; + while (w.resizeCount == resCount && maxWait-- >= 0) + QTest::qWait(10); + QVERIFY(w.resizeCount > resCount); +} + +class PainterWindow : public QOpenGLWindow +{ +public: + void paintGL() Q_DECL_OVERRIDE { + QOpenGLContext *ctx = QOpenGLContext::currentContext(); + QVERIFY(ctx); + QCOMPARE(ctx, context()); + QOpenGLFunctions *f = ctx->functions(); + QVERIFY(f); + + QPainter p(this); + p.beginNativePainting(); + f->glClearColor(1, 0, 0, 1); + f->glClear(GL_COLOR_BUFFER_BIT); + p.endNativePainting(); + p.fillRect(QRect(0, 0, 100, 100), Qt::blue); + p.end(); + + img = grabFramebuffer(); + } + + QImage img; +}; + +void tst_QOpenGLWindow::painter() +{ + PainterWindow w; + w.resize(400, 400); + w.show(); + QTest::qWaitForWindowExposed(&w); + + QCOMPARE(w.img.size(), w.size()); + QVERIFY(w.img.pixel(5, 5) == qRgb(0, 0, 255)); + QVERIFY(w.img.pixel(200, 5) == qRgb(255, 0, 0)); +} + +class PartialPainterWindow : public QOpenGLWindow +{ +public: + PartialPainterWindow(QOpenGLWindow::UpdateBehavior u) + : QOpenGLWindow(u), x(0) { } + + void paintGL() Q_DECL_OVERRIDE { + ++paintCount; + + QPainter p(this); + if (!x) + p.fillRect(QRect(0, 0, width(), height()), Qt::green); + + p.fillRect(QRect(x, 0, 10, 10), Qt::blue); + x += 20; + } + + int paintCount; + int x; +}; + +void tst_QOpenGLWindow::partial_data() +{ + QTest::addColumn<int>("behavior"); + QTest::newRow("blit") << int(QOpenGLWindow::PartialUpdateBlit); + QTest::newRow("blend") << int(QOpenGLWindow::PartialUpdateBlend); +} + +void tst_QOpenGLWindow::partial() +{ + QFETCH(int, behavior); + QOpenGLWindow::UpdateBehavior u = QOpenGLWindow::UpdateBehavior(behavior); + PartialPainterWindow w(u); + w.resize(800, 400); + w.show(); + QTest::qWaitForWindowExposed(&w); + + // Add a couple of small blue rects. + for (int i = 0; i < 10; ++i) { + w.paintCount = 0; + w.update(); + int maxWait = 1000; + while (w.paintCount == 0 && maxWait-- >= 0) + QTest::qWait(10); + } + + // Now since the painting went to an extra framebuffer, all the rects should + // be present since everything is preserved between the frames. + QImage img = w.grabFramebuffer(); + QCOMPARE(img.size(), w.size()); + QCOMPARE(img.pixel(5, 5), qRgb(0, 0, 255)); + QCOMPARE(img.pixel(15, 5), qRgb(0, 255, 0)); + QCOMPARE(img.pixel(25, 5), qRgb(0, 0, 255)); +} + +class PaintUnderOverWindow : public QOpenGLWindow +{ +public: + PaintUnderOverWindow() : QOpenGLWindow(PartialUpdateBlend), m_state(None) { } + enum State { + None, + PaintUnder, + Paint, + PaintOver, + Error + } m_state; + + void paintUnderGL() Q_DECL_OVERRIDE { + if (m_state == None || m_state == PaintOver) + m_state = PaintUnder; + else + m_state = Error; + + GLuint fbo = 0xFFFF; + QOpenGLContext::currentContext()->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &fbo); + QVERIFY(fbo == 0); + } + + void paintGL() Q_DECL_OVERRIDE { + if (m_state == PaintUnder) + m_state = Paint; + else + m_state = Error; + + // Using PartialUpdateBlend so paintGL() targets a user fbo, not the default. + GLuint fbo = 0xFFFF; + QOpenGLContext::currentContext()->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &fbo); + QVERIFY(fbo != 0); + QCOMPARE(fbo, defaultFramebufferObject()); + } + + void paintOverGL() Q_DECL_OVERRIDE { + if (m_state == Paint) + m_state = PaintOver; + else + m_state = Error; + + GLuint fbo = 0xFFFF; + QOpenGLContext::currentContext()->functions()->glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint *) &fbo); + QVERIFY(fbo == 0); + } +}; + +void tst_QOpenGLWindow::underOver() +{ + PaintUnderOverWindow w; + w.resize(400, 400); + w.show(); + QTest::qWaitForWindowExposed(&w); + + // under -> paint -> over -> under -> paint -> ... is the only acceptable sequence + QCOMPARE(w.m_state, PaintUnderOverWindow::PaintOver); +} + +#include <tst_qopenglwindow.moc> + +QTEST_MAIN(tst_QOpenGLWindow) |