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>
This commit is contained in:
Laszlo Agocs 2014-03-20 12:58:22 +01:00
parent b53e08e335
commit e48737ae77
16 changed files with 1950 additions and 6 deletions

View File

@ -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

View File

@ -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;
}

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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"

View File

@ -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

View File

@ -0,0 +1,5 @@
<!DOCTYPE RCC><RCC version="1.0">
<qresource>
<file>background.frag</file>
</qresource>
</RCC>

View File

@ -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

View File

@ -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 &region) 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 &region) 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

View File

@ -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

View File

@ -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 &region)
{
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

View File

@ -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 &region);
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

View File

@ -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 &region)
{
Q_UNUSED(region);
}
virtual void endPaint()
{
}
virtual void flush(const QRegion &region)
{
Q_UNUSED(region);
}
bool paint(const QRegion &region)
{
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 &region)
{
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

View File

@ -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

View File

@ -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

View File

@ -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)