diff options
Diffstat (limited to 'src/widgets')
-rw-r--r-- | src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp | 109 | ||||
-rw-r--r-- | src/widgets/graphicsview/qgraphicsview.cpp | 2 | ||||
-rw-r--r-- | src/widgets/kernel/kernel.pri | 2 | ||||
-rw-r--r-- | src/widgets/kernel/qopenglwidget.cpp | 852 | ||||
-rw-r--r-- | src/widgets/kernel/qopenglwidget.h (renamed from src/widgets/kernel/qopenglwidget_p.h) | 73 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget.cpp | 63 | ||||
-rw-r--r-- | src/widgets/kernel/qwidget_p.h | 13 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetbackingstore.cpp | 100 | ||||
-rw-r--r-- | src/widgets/kernel/qwidgetbackingstore_p.h | 16 | ||||
-rw-r--r-- | src/widgets/widgets/qabstractscrollarea.cpp | 4 |
10 files changed, 1080 insertions, 154 deletions
diff --git a/src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp b/src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp new file mode 100644 index 0000000000..bc279e4406 --- /dev/null +++ b/src/widgets/doc/snippets/code/doc_gui_widgets_qopenglwidget.cpp @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the documentation of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:BSD$ +** You may use this file under the terms of the BSD license as follows: +** +** "Redistribution and use in source and binary forms, with or without +** modification, are permitted provided that the following conditions are +** met: +** * Redistributions of source code must retain the above copyright +** notice, this list of conditions and the following disclaimer. +** * Redistributions in binary form must reproduce the above copyright +** notice, this list of conditions and the following disclaimer in +** the documentation and/or other materials provided with the +** distribution. +** * Neither the name of Digia Plc and its Subsidiary(-ies) nor the names +** of its contributors may be used to endorse or promote products derived +** from this software without specific prior written permission. +** +** +** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +//! [0] +class MyGLWidget : public QOpenGLWidget +{ +public: + MyGLWidget(QWidget *parent) : QOpenGLWidget(parent) { } + +protected: + void initializeGL() + { + // Set up the rendering context, load shaders and other resources, etc.: + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClearColor(1.0f, 1.0f, 1.0f, 1.0f); + ... + } + + void resizeGL(int w, int h) + { + // Update projection matrix and other size related settings: + m_projection.setToIdentity(); + m_projection.perspective(45.0f, w / float(h), 0.01f, 100.0f); + ... + } + + void paintGL() + { + // Draw the scene: + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glClear(GL_COLOR_BUFFER_BIT); + ... + } + +}; +//! [0] + +//! [1] +class MyGLWidget : public QOpenGLWidget, protected QOpenGLFunctions +{ + ... + void initializeGL() + { + initializeOpenGLFunctions(); + glClearColor(...); + ... + } + ... +}; +//! [1] + +//! [2] +QOpenGLWidget *widget = new QOpenGLWidget(parent); +QSurfaceFormat format; +format.setDepthBufferSize(24); +format.setStencilBufferSize(8); +format.setVersion(3, 2); +format.setProfile(QSurfaceFormat::CoreProfile); +widget->setFormat(format); // must be called before the widget or its parent window gets shown +//! [2] + +//! [3] + ... + void paintGL() + { + QOpenGLFunctions_3_2_Core *f = QOpenGLContext::currentContext()->versionFunctions<QOpenGLFunctions_3_2_Core>(); + ... + f->glDrawArraysInstanced(...); + ... + } + ... +//! [3] diff --git a/src/widgets/graphicsview/qgraphicsview.cpp b/src/widgets/graphicsview/qgraphicsview.cpp index 9cd684a408..7bdfae5fec 100644 --- a/src/widgets/graphicsview/qgraphicsview.cpp +++ b/src/widgets/graphicsview/qgraphicsview.cpp @@ -2762,7 +2762,7 @@ void QGraphicsView::setupViewport(QWidget *widget) return; } - const bool isGLWidget = widget->inherits("QGLWidget"); + const bool isGLWidget = widget->inherits("QGLWidget") || widget->inherits("QOpenGLWidget"); d->accelerateScrolling = !(isGLWidget); diff --git a/src/widgets/kernel/kernel.pri b/src/widgets/kernel/kernel.pri index a9b8627beb..88c1e2595b 100644 --- a/src/widgets/kernel/kernel.pri +++ b/src/widgets/kernel/kernel.pri @@ -78,6 +78,6 @@ wince*: { } contains(QT_CONFIG, opengl) { - HEADERS += kernel/qopenglwidget_p.h + HEADERS += kernel/qopenglwidget.h SOURCES += kernel/qopenglwidget.cpp } diff --git a/src/widgets/kernel/qopenglwidget.cpp b/src/widgets/kernel/qopenglwidget.cpp index 307d0bb909..907f69dfbd 100644 --- a/src/widgets/kernel/qopenglwidget.cpp +++ b/src/widgets/kernel/qopenglwidget.cpp @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -39,146 +39,892 @@ ** ****************************************************************************/ -#include "qopenglwidget_p.h" -#include <QOpenGLContext> -#include <QtWidgets/private/qwidget_p.h> - -#include <QOpenGLFramebufferObject> -#include <QOpenGLFunctions> -#include <QWindow> -#include <qpa/qplatformwindow.h> -#include <QDebug> +#include "qopenglwidget.h" +#include <QtGui/QOpenGLContext> +#include <QtGui/QOpenGLFramebufferObject> +#include <QtGui/QOffscreenSurface> +#include <QtGui/QOpenGLFunctions> +#include <QtGui/QWindow> #include <QtGui/QGuiApplication> #include <QtGui/QScreen> +#include <QtGui/QOpenGLPaintDevice> +#include <QtGui/qpa/qplatformwindow.h> +#include <QtGui/qpa/qplatformintegration.h> +#include <QtGui/private/qguiapplication_p.h> +#include <QtGui/private/qopenglextensions_p.h> +#include <QtGui/private/qfont_p.h> +#include <QtWidgets/private/qwidget_p.h> QT_BEGIN_NAMESPACE +/*! + \class QOpenGLWidget + \inmodule QtWidgets + \since 5.4 + + \brief The QOpenGLWidget class is a widget for rendering OpenGL graphics. + + QOpenGLWidget provides functionality for displaying OpenGL graphics + integrated into a Qt application. It is very simple to use: Make + your class inherit from it and use the subclass like any other + QWidget, except that you have the choice between using QPainter and + standard OpenGL rendering commands. + + QOpenGLWidget provides three convenient virtual functions that you + can reimplement in your subclass to perform the typical OpenGL + tasks: + + \list + \li paintGL() - Renders the OpenGL scene. Gets called whenever the widget + needs to be updated. + \li resizeGL() - Sets up the OpenGL viewport, projection, etc. Gets + called whenever the widget has been resized (and also when it + is shown for the first time because all newly created widgets get a + resize event automatically). + \li initializeGL() - Sets up the OpenGL resources and state. Gets called + once before the first time resizeGL() or paintGL() is called. + \endlist + + If you need to trigger a repaint from places other than paintGL() (a + typical example is when using \l{QTimer}{timers} to animate scenes), + you should call the widget's update() function to schedule an update. + + Your widget's OpenGL rendering context is made current when + paintGL(), resizeGL(), or initializeGL() is called. If you need to + call the standard OpenGL API functions from other places (e.g. in + your widget's constructor or in your own paint functions), you + must call makeCurrent() first. + + All rendering happens into an OpenGL framebuffer + object. makeCurrent() ensure that it is bound in the context. Keep + this in mind when creating and binding additional framebuffer + objects in the rendering code in paintGL(). Never re-bind the + framebuffer with ID 0. Instead, call defaultFramebufferObject() to + get the ID that should be bound. + + QOpenGLWidget allows using different OpenGL versions and profiles + when the platform supports it. Just set the requested format via + setFormat(). Keep in mind however that having multiple QOpenGLWidget + instances in the same window requires that they all use the same + format, or at least formats that do not make the contexts + non-sharable. + + \section1 Painting Techniques + + As described above, subclass QOpenGLWidget to render pure 3D content in the + following way: + + \list + + \li Reimplement the initializeGL() and resizeGL() functions to + set up the OpenGL state and provide a perspective transformation. + + \li Reimplement paintGL() to paint the 3D scene, calling only + OpenGL functions. + + \endlist + + It is also possible to draw 2D graphics onto a QOpenGLWidget subclass using QPainter: + + \list + + \li In paintGL(), instead of issuing OpenGL commands, construct a QPainter + object for use on the widget. + + \li Draw primitives using QPainter's member functions. + + \li Direct OpenGL commands can still be issued. However, you must make sure + these are enclosed by a call to the painter's beginNativePainting() and + endNativePainting(). + + \endlist + + When performing drawing using QPainter only, it is also possible to perform + the painting like it is done for ordinary widgets: by reimplementing paintEvent(). + + \list + + \li Reimplement the paintEvent() function. + + \li Construct a QPainter object targeting the widget. Either pass the widget to the + constructor or the QPainter::begin() function. + + \li Draw primitives using QPainter's member functions. + + \li Painting finishes then the QPainter instance is destroyed. Alternatively, + call QPainter::end() explicitly. + + \endlist + + \section1 OpenGL function calls, headers and QOpenGLFunctions + + When making OpenGL function calls, it is strongly recommended to avoid calling + the functions directly. Instead, prefer using QOpenGLFunctions (when making + portable applications) or the versioned variants (for example, + QOpenGLFunctions_3_2_Core and similar, when targeting modern, desktop-only + OpenGL). This way the application will work correctly in all Qt build + configurations, including the ones that perform dynamic OpenGL implementation + loading which means applications are not directly linking to an GL + implementation and thus direct function calls are not feasible. + + In paintGL() the current context is always accessible by caling + QOpenGLContext::currentContext(). From this context an already initialized, + ready-to-be-used QOpenGLFunctions instance is retrievable by calling + QOpenGLContext::functions(). An alternative to prefixing every GL call is to + inherit from QOpenGLFunctions and call + QOpenGLFunctions::initializeOpenGLFunctions() in initializeGL(). + + As for the OpenGL headers, note that in most cases there will be no need to + directly include any headers like GL.h. The OpenGL-related Qt headers will + include qopengl.h which will in turn include an appropriate header for the + system. This might be an OpenGL ES 3.x or 2.0 header, the highest version that + is available, or a system-provided gl.h. In addition, a copy of the extension + headers (called glext.h on some systems) is provided as part of Qt both for + OpenGL and OpenGL ES. These will get included automatically on platforms where + feasible. This means that constants and function pointer typedefs from ARB, + EXT, OES extensions are automatically available. + + \section1 Code examples + + To get started, the simplest QOpenGLWidget subclass could like like the following: + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 0 + + Alternatively, the prefixing of each and every OpenGL call can be avoidided by deriving + from QOpenGLFunctions instead: + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 1 + + To get a context compatible with a given OpenGL version or profile, or to + request depth and stencil buffers, call setFormat(): + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 2 + + With OpenGL 3.0+ contexts, when portability is not important, the versioned + QOpenGLFunctions variants give easy access to all the modern OpenGL functions + available in a given version: + + \snippet code/doc_gui_widgets_qopenglwidget.cpp 3 + + \section1 Relation to QGLWidget + + The legacy QtOpenGL module (classes prefixed with QGL) provides a widget + called QGLWidget. QOpenGLWidget is intended to be a modern replacement for + it. Therefore, especially in new applications, the general recommendation is + to use QOpenGLWidget. + + While the API is very similar, there is an important difference between the + two: QOpenGLWidget always renders offscreen, using framebuffer + objects. QGLWidget on the other hand uses a native window and surface. The + latter causes issues when using it in complex user interfaces since, depending + on the platform, such native child widgets may have various limitations, + regarding stacking orders for example. QOpenGLWidget avoids this by not + creating a separate native window. + + \section1 Threading + + Performing offscreen rendering on worker threads, for example to generate + textures that are then used in the GUI/main thread in paintGL(), are supported + by exposing the widget's QOpenGLContext so that additional contexts sharing + with it can be created on each thread. + + Drawing directly to the QOpenGLWidget's framebuffer outside the GUI/main + thread is possible by reimplementing paintEvent() to do nothing. The context's + thread affinity has to be changed via QObject::moveToThread(). After that, + makeCurrent() and doneCurrent() are usable on the worker thread. Be careful to + move the context back to the GUI/main thread afterwards. + + Unlike QGLWidget, triggering a buffer swap just for the QOpenGLWidget is not + possible since there is no real, onscreen native surface for it. Instead, it + is up to the widget stack to manage composition and buffer swaps on the gui + thread. When a thread is done updating the framebuffer, call update() \b{on + the GUI/main thread} to schedule composition. + + Extra care has to be taken to avoid using the framebuffer when the GUI/main + thread is performing compositing. The signals aboutToCompose() and + frameSwapped() will be emitted when the composition is starting and + ending. They are emitted on the GUI/main thread. This means that by using a + direct connection aboutToCompose() can block the GUI/main thread until the + worker thread has finished its rendering. After that, the worker thread must + perform no further rendering until the frameSwapped() signal is emitted. If + this is not acceptable, the worker thread has to implement a double buffering + mechanism. This involves drawing using an alternative render target, that is + fully controlled by the thread, e.g. an additional framebuffer object, and + blitting to the QOpenGLWidget's framebuffer at a suitable time. + + \section1 Context sharing + + When multiple QOpenGLWidgets are added as children to the same top-level + widget, their contexts will share with each other. This does not apply for + QOpenGLWidget instances that belong to different windows. + + This means that all QOpenGLWidgets in the same window can access each other's + sharable resources, like textures, and there is no need for an extra "global + share" context, as was the case with QGLWidget. + + Note that QOpenGLWidget expects a standard conformant implementation of + resource sharing when it comes to the underlying graphics drivers. For + example, some drivers, in particular for mobile and embedded hardware, have + issues with setting up sharing between an existing context and others that are + created later. Some other drivers may behave in unexpected ways when trying to + utilize shared resources between different threads. + + \section1 Limitations + + Putting other widgets underneath and making the QOpenGLWidget transparent will + not lead to the expected results: The widgets underneath will not be + visible. This is because in practice the QOpenGLWidget is drawn before all + other regular, non-OpenGL widgets, and so see-through type of solutions are + not feasible. Other type of layouts, like having widgets on top of the + QOpenGLWidget, will function as expected. + + When absolutely necessary, this limitation can be overcome by setting the + Qt::WA_AlwaysStackOnTop attribute on the QOpenGLWidget. Be aware however that + this breaks stacking order, for example it will not be possible to have other + widgets on top of the QOpenGLWidget, so it should only be used in situations + where a semi-transparent QOpenGLWidget with other widgets visible underneath + is required. + + \e{OpenGL is a trademark of Silicon Graphics, Inc. in the United States and other + countries.} + + \sa QOpenGLFunctions +*/ + +/*! + \fn void QOpenGLWidget::aboutToCompose() + + This signal is emitted when the widget's top-level window is about to begin + composing the textures of its QOpenGLWidget children and the other widgets. +*/ + +/*! + \fn void QOpenGLWidget::frameSwapped() + + This signal is emitted after the widget's top-level window has finished + composition and returned from its potentially blocking + QOpenGLContext::swapBuffers() call. +*/ + +/*! + \fn void QOpenGLWidget::aboutToResize() + + This signal is emitted when the widget's size is changed and therefore the + framebuffer object is going to be recreated. +*/ + +/*! + \fn void QOpenGLWidget::resized() + + This signal is emitted right after the framebuffer object has been recreated + due to resizing the widget. +*/ + +class QOpenGLWidgetPaintDevice : public QOpenGLPaintDevice +{ +public: + QOpenGLWidgetPaintDevice(QOpenGLWidget *widget) : w(widget) { } + void ensureActiveTarget() Q_DECL_OVERRIDE; + +private: + QOpenGLWidget *w; +}; + class QOpenGLWidgetPrivate : public QWidgetPrivate { Q_DECLARE_PUBLIC(QOpenGLWidget) public: QOpenGLWidgetPrivate() - : fbo(0), uninitialized(true) + : context(0), + fbo(0), + resolvedFbo(0), + surface(0), + initialized(false), + fakeHidden(false), + paintDevice(0), + inBackingStorePaint(false) + { + } + + ~QOpenGLWidgetPrivate() { + reset(); } - GLuint textureId() const { return fbo ? fbo->texture() : 0; } - const QSurface *surface() const { return q_func()->window()->windowHandle(); } - QSurface *surface() { return q_func()->window()->windowHandle(); } + void reset(); + void recreateFbo(); + + GLuint textureId() const Q_DECL_OVERRIDE; + void initialize(); + void invokeUserPaint(); + void render(); - QOpenGLContext context; - QOpenGLFramebufferObject *fbo; - bool uninitialized; + QImage grabFramebuffer() Q_DECL_OVERRIDE; + void beginBackingStorePainting() Q_DECL_OVERRIDE { inBackingStorePaint = true; } + void endBackingStorePainting() Q_DECL_OVERRIDE { inBackingStorePaint = false; } + void beginCompose() Q_DECL_OVERRIDE; + void endCompose() Q_DECL_OVERRIDE; + void resizeViewportFramebuffer() Q_DECL_OVERRIDE; - int w,h; + QOpenGLContext *context; + QOpenGLFramebufferObject *fbo; + QOpenGLFramebufferObject *resolvedFbo; + QOffscreenSurface *surface; + bool initialized; + bool fakeHidden; + QOpenGLPaintDevice *paintDevice; + bool inBackingStorePaint; + QSurfaceFormat requestedFormat; }; +void QOpenGLWidgetPaintDevice::ensureActiveTarget() +{ + QOpenGLWidgetPrivate *d = static_cast<QOpenGLWidgetPrivate *>(QWidgetPrivate::get(w)); + if (!d->initialized) + return; + + if (QOpenGLContext::currentContext() != d->context) + w->makeCurrent(); + else + d->fbo->bind(); +} + +GLuint QOpenGLWidgetPrivate::textureId() const +{ + return resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0); +} + +void QOpenGLWidgetPrivate::reset() +{ + delete paintDevice; + paintDevice = 0; + delete fbo; + fbo = 0; + delete resolvedFbo; + resolvedFbo = 0; + delete context; + context = 0; + delete surface; + surface = 0; + initialized = fakeHidden = inBackingStorePaint = false; +} + +void QOpenGLWidgetPrivate::recreateFbo() +{ + Q_Q(QOpenGLWidget); + + emit q->aboutToResize(); + + context->makeCurrent(surface); + + delete fbo; + delete resolvedFbo; + + int samples = get(q->window())->shareContext()->format().samples(); + QOpenGLExtensions *extfuncs = static_cast<QOpenGLExtensions *>(context->functions()); + if (!extfuncs->hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) + samples = 0; + + QOpenGLFramebufferObjectFormat format; + format.setAttachment(QOpenGLFramebufferObject::CombinedDepthStencil); + format.setSamples(samples); + + const QSize deviceSize = q->size() * q->devicePixelRatio(); + fbo = new QOpenGLFramebufferObject(deviceSize, format); + if (samples > 0) + resolvedFbo = new QOpenGLFramebufferObject(deviceSize); + + fbo->bind(); + + paintDevice->setSize(deviceSize); + + emit q->resized(); +} + +void QOpenGLWidgetPrivate::beginCompose() +{ + Q_Q(QOpenGLWidget); + emit q->aboutToCompose(); +} + +void QOpenGLWidgetPrivate::endCompose() +{ + Q_Q(QOpenGLWidget); + emit q->frameSwapped(); +} + void QOpenGLWidgetPrivate::initialize() { Q_Q(QOpenGLWidget); - if (!uninitialized) + if (initialized) + return; + + // Get our toplevel's context with which we will share in order to make the + // texture usable by the underlying window's backingstore. + QOpenGLContext *shareContext = get(q->window())->shareContext(); + if (!shareContext) { + qWarning("QOpenGLWidget: Cannot be used without a context shared with the toplevel."); return; - context.setShareContext(get(q->window())->shareContext()); - context.setFormat(surface()->format()); - context.create(); - context.makeCurrent(surface()); + } + + QScopedPointer<QOpenGLContext> ctx(new QOpenGLContext); + ctx->setShareContext(shareContext); + ctx->setFormat(requestedFormat); + if (!ctx->create()) { + qWarning("QOpenGLWidget: Failed to create context"); + return; + } + + // The top-level window's surface is not good enough since it causes way too + // much trouble with regards to the QSurfaceFormat for example. So just like + // in QQuickWidget, use a dedicated QOffscreenSurface. + surface = new QOffscreenSurface; + surface->setFormat(ctx->format()); + surface->create(); + + if (!ctx->makeCurrent(surface)) { + qWarning("QOpenGLWidget: Failed to make context current"); + return; + } + + paintDevice = new QOpenGLWidgetPaintDevice(q); + paintDevice->setSize(q->size() * q->devicePixelRatio()); + paintDevice->setDevicePixelRatio(q->devicePixelRatio()); + + context = ctx.take(); + initialized = true; + q->initializeGL(); - uninitialized = false; } +void QOpenGLWidgetPrivate::invokeUserPaint() +{ + Q_Q(QOpenGLWidget); + QOpenGLFunctions *f = QOpenGLContext::currentContext()->functions(); + f->glViewport(0, 0, q->width() * q->devicePixelRatio(), q->height() * q->devicePixelRatio()); + + q->paintGL(); + + if (resolvedFbo) { + QRect rect(QPoint(0, 0), fbo->size()); + QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect); + } +} + +void QOpenGLWidgetPrivate::render() +{ + Q_Q(QOpenGLWidget); + + if (fakeHidden || !initialized) + return; + + q->makeCurrent(); + invokeUserPaint(); + context->functions()->glFlush(); +} + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + +QImage QOpenGLWidgetPrivate::grabFramebuffer() +{ + Q_Q(QOpenGLWidget); + if (!initialized) + return QImage(); + + render(); + q->makeCurrent(); + QImage res = qt_gl_read_framebuffer(q->size() * q->devicePixelRatio(), false, false); + + return res; +} + +void QOpenGLWidgetPrivate::resizeViewportFramebuffer() +{ + Q_Q(QOpenGLWidget); + if (!initialized) + return; + + if (!fbo || q->size() != fbo->size()) + recreateFbo(); +} + +/*! + Constructs a widget which is a child of \a parent, with widget flags set to \a f. + */ QOpenGLWidget::QOpenGLWidget(QWidget *parent, Qt::WindowFlags f) : QWidget(*(new QOpenGLWidgetPrivate), parent, f) { Q_D(QOpenGLWidget); - d->setRenderToTexture(); + if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)) + d->setRenderToTexture(); + else + qWarning("QOpenGLWidget is not supported on this platform."); } +/*! + Destroys the widget + */ QOpenGLWidget::~QOpenGLWidget() { } +/*! + Sets the requested surface \a format. + + \note Requesting an alpha buffer via this function will not lead to the desired results + and should be avoided. Instead, use Qt::WA_AlwaysStackOnTop to enable semi-transparent + QOpenGLWidget instances with other widgets visible underneath. Keep in mind however that + this breaks the stacking order, so it will no longer be possible to have other widgets + on top of the QOpenGLWidget. + + \sa format(), Qt::WA_AlwaysStackOnTop + */ +void QOpenGLWidget::setFormat(const QSurfaceFormat &format) +{ + Q_UNUSED(format); + Q_D(QOpenGLWidget); + if (d->initialized) { + qWarning("QOpenGLWidget: Already initialized, setting the format has no effect"); + return; + } + + d->requestedFormat = format; +} + +/*! + Returns the context and surface format used by this widget and its toplevel + window. + + After the widget and its toplevel have both been created, resized and shown, + this function will return the actual format of the context. This may differ + from the requested format if the request could not be fulfilled by the + platform. It is also possible to get larger color buffer sizes than + requested. + + When the widget's window and the related OpenGL resources are not yet + initialized, the return value is the format that has been set via + setFormat(). + + \sa setFormat(), context() + */ +QSurfaceFormat QOpenGLWidget::format() const +{ + Q_D(const QOpenGLWidget); + return d->initialized ? d->context->format() : d->requestedFormat; +} + +/*! + \return \e true if the widget and OpenGL resources, like the context, have + been successfully initialized. Note that the return value is always false + until the widget is shown. +*/ bool QOpenGLWidget::isValid() const { Q_D(const QOpenGLWidget); - return d->context.isValid(); + return d->initialized && d->context->isValid(); } +/*! + Prepares for rendering OpenGL content for this widget by making the + corresponding context current and binding the framebuffer object in that + context. + + It is not necessary to call this function in most cases, because it + is called automatically before invoking paintGL(). + + \sa context(), paintGL(), doneCurrent() + */ void QOpenGLWidget::makeCurrent() { Q_D(QOpenGLWidget); - d->context.makeCurrent(d->surface()); + if (!d->initialized) { + qWarning("QOpenGLWidget: Cannot make uninitialized widget current"); + return; + } + + d->context->makeCurrent(d->surface); d->fbo->bind(); } +/*! + Releases the context. + + It is not necessary to call this function in most cases, since the + widget will make sure the context is bound and released properly + when invoking paintGL(). + */ void QOpenGLWidget::doneCurrent() { Q_D(QOpenGLWidget); - d->context.doneCurrent(); + if (!d->initialized) + return; + + d->context->doneCurrent(); } -QSurfaceFormat QOpenGLWidget::format() const +/*! + \return The QOpenGLContext used by this widget or \c 0 if not yet initialized. + + \note The context and the framebuffer object used by the widget changes when + reparenting the widget via setParent(). + + \sa QOpenGLContext::setShareContext(), defaultFramebufferObject() + */ +QOpenGLContext *QOpenGLWidget::context() const { Q_D(const QOpenGLWidget); - return d->surface()->format(); + return d->context; } +/*! + \return The framebuffer object handle or \c 0 if not yet initialized. + + \note The framebuffer object belongs to the context returned by context() + and may not be accessible from other contexts. + + \note The context and the framebuffer object used by the widget changes when + reparenting the widget via setParent(). In addition, the framebuffer object + changes on each resize. + + \sa context() + */ GLuint QOpenGLWidget::defaultFramebufferObject() const { Q_D(const QOpenGLWidget); return d->fbo ? d->fbo->handle() : 0; } +/*! + This virtual function is called once before the first call to + paintGL() or resizeGL(). Reimplement it in a subclass. + + This function should set up any required OpenGL resources and state. + + There is no need to call makeCurrent() because this has already been + done when this function is called. Note however that the framebuffer + is not yet available at this stage, so avoid issuing draw calls from + here. Defer such calls to paintGL() instead. + + \sa paintGL(), resizeGL() +*/ void QOpenGLWidget::initializeGL() { - } +/*! + This virtual function is called whenever the widget has been + resized. Reimplement it in a subclass. The new size is passed in + \a w and \a h. + + There is no need to call makeCurrent() because this has already been + done when this function is called. Additionally, the framebuffer is + also bound. + + \sa initializeGL(), paintGL() +*/ void QOpenGLWidget::resizeGL(int w, int h) { Q_UNUSED(w); Q_UNUSED(h); } +/*! + This virtual function is called whenever the widget needs to be + painted. Reimplement it in a subclass. + + There is no need to call makeCurrent() because this has already + been done when this function is called. + + Before invoking this function, the context and the framebuffer are + bound, and the viewport is set up by a call to glViewport(). No + other state is set and no clearing or drawing is performed by the + framework. + + \sa initializeGL(), resizeGL() +*/ void QOpenGLWidget::paintGL() { } -void QOpenGLWidget::updateGL() +/*! + \internal + + Handles resize events that are passed in the \a event parameter. + Calls the virtual function resizeGL(). +*/ +void QOpenGLWidget::resizeEvent(QResizeEvent *e) { Q_D(QOpenGLWidget); - if (d->uninitialized || !d->surface()) + + if (e->size().isEmpty()) { + d->fakeHidden = true; + return; + } + d->fakeHidden = false; + + d->initialize(); + if (!d->initialized) return; - makeCurrent(); - paintGL(); - d->context.functions()->glFlush(); - doneCurrent(); - update(); + d->recreateFbo(); + resizeGL(width(), height()); + d->invokeUserPaint(); + d->context->functions()->glFlush(); } +/*! + Handles paint events. -void QOpenGLWidget::resizeEvent(QResizeEvent *) + Calling QWidget::update() will lead to sending a paint event \a e, + and thus invoking this function. (NB this is asynchronous and will + happen at some point after returning from update()). This function + will then, after some preparation, call the virtual paintGL() to + update the contents of the QOpenGLWidget's framebuffer. The widget's + top-level window will then composite the framebuffer's texture with + the rest of the window. +*/ +void QOpenGLWidget::paintEvent(QPaintEvent *e) { + Q_UNUSED(e); Q_D(QOpenGLWidget); - d->w = width(); - d->h = height(); - d->initialize(); + if (!d->initialized) + return; - d->context.makeCurrent(d->surface()); - delete d->fbo; // recreate when resized - d->fbo = new QOpenGLFramebufferObject(size() * devicePixelRatio(), QOpenGLFramebufferObject::CombinedDepthStencil); - d->fbo->bind(); - QOpenGLFunctions *funcs = d->context.functions(); - resizeGL(width(), height()); - paintGL(); - funcs->glFlush(); + if (updatesEnabled()) + d->render(); +} + +/*! + Renders and returns a 32-bit RGB image of the framebuffer. + + \note This is a potentially expensive operation because it relies on glReadPixels() + to read back the pixels. This may be slow and can stall the GPU pipeline. +*/ +QImage QOpenGLWidget::grabFramebuffer() +{ + Q_D(QOpenGLWidget); + return d->grabFramebuffer(); +} + +/*! + \internal +*/ +int QOpenGLWidget::metric(QPaintDevice::PaintDeviceMetric metric) const +{ + Q_D(const QOpenGLWidget); + if (d->inBackingStorePaint) + return QWidget::metric(metric); + + QScreen *screen = window()->windowHandle()->screen(); + if (!screen && QGuiApplication::primaryScreen()) + screen = QGuiApplication::primaryScreen(); + + const float dpmx = qt_defaultDpiX() * 100. / 2.54; + const float dpmy = qt_defaultDpiY() * 100. / 2.54; + + switch (metric) { + case PdmWidth: + return width(); + case PdmHeight: + return height(); + case PdmDepth: + return 32; + case PdmWidthMM: + if (screen) + return width() * screen->physicalSize().width() / screen->geometry().width(); + else + return width() * 1000 / dpmx; + case PdmHeightMM: + if (screen) + return height() * screen->physicalSize().height() / screen->geometry().height(); + else + return height() * 1000 / dpmy; + case PdmNumColors: + return 0; + case PdmDpiX: + if (screen) + return qRound(screen->logicalDotsPerInchX()); + else + return qRound(dpmx * 0.0254); + case PdmDpiY: + if (screen) + return qRound(screen->logicalDotsPerInchY()); + else + return qRound(dpmy * 0.0254); + case PdmPhysicalDpiX: + if (screen) + return qRound(screen->physicalDotsPerInchX()); + else + return qRound(dpmx * 0.0254); + case PdmPhysicalDpiY: + if (screen) + return qRound(screen->physicalDotsPerInchY()); + else + return qRound(dpmy * 0.0254); + case PdmDevicePixelRatio: + if (screen) + return screen->devicePixelRatio(); + else + return 1.0; + default: + qWarning("QOpenGLWidget::metric(): unknown metric %d", metric); + return 0; + } +} + +/*! + \internal +*/ +QPaintDevice *QOpenGLWidget::redirected(QPoint *p) const +{ + Q_D(const QOpenGLWidget); + if (d->inBackingStorePaint) + return QWidget::redirected(p); + + return d->paintDevice; +} + +/*! + \internal +*/ +QPaintEngine *QOpenGLWidget::paintEngine() const +{ + Q_D(const QOpenGLWidget); + // QWidget needs to "punch a hole" into the backingstore. This needs the + // normal paint engine and device, not the GL one. So in this mode, behave + // like a normal widget. + if (d->inBackingStorePaint) + return QWidget::paintEngine(); + + if (!d->initialized) + return 0; + + return d->paintDevice->paintEngine(); } -void QOpenGLWidget::paintEvent(QPaintEvent *) +/*! + \internal +*/ +bool QOpenGLWidget::event(QEvent *e) { - qWarning("QOpenGLWidget does not support paintEvent() yet."); - return; + Q_D(QOpenGLWidget); + switch (e->type()) { + case QEvent::WindowChangeInternal: + if (d->initialized) + d->reset(); + // FALLTHROUGH + case QEvent::Show: // reparenting may not lead to a resize so reinitalize on Show too + if (!d->initialized && !size().isEmpty() && window() && window()->windowHandle()) { + d->initialize(); + if (d->initialized) + d->recreateFbo(); + } + break; + default: + break; + } + return QWidget::event(e); } QT_END_NAMESPACE diff --git a/src/widgets/kernel/qopenglwidget_p.h b/src/widgets/kernel/qopenglwidget.h index 1c7f0bfeec..28d802e1f4 100644 --- a/src/widgets/kernel/qopenglwidget_p.h +++ b/src/widgets/kernel/qopenglwidget.h @@ -1,6 +1,6 @@ /**************************************************************************** ** -** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Copyright (C) 2014 Digia Plc and/or its subsidiary(-ies). ** Contact: http://www.qt-project.org/legal ** ** This file is part of the QtWidgets module of the Qt Toolkit. @@ -39,21 +39,11 @@ ** ****************************************************************************/ -// -// W A R N I N G -// ------------- -// -// This file is not part of the Qt API. It may change from version to version -// without notice, or even be removed. -// -// We mean it. -// #ifndef QOPENGLWIDGET_H #define QOPENGLWIDGET_H -#include <QWidget> -#include <QSurfaceFormat> - +#include <QtWidgets/QWidget> +#include <QtGui/QSurfaceFormat> #include <QtGui/qopengl.h> QT_BEGIN_NAMESPACE @@ -66,68 +56,43 @@ class Q_WIDGETS_EXPORT QOpenGLWidget : public QWidget Q_DECLARE_PRIVATE(QOpenGLWidget) public: - explicit QOpenGLWidget(QWidget* parent=0, - Qt::WindowFlags f=0); - -// This API is not finalized yet. The commented-out functions below are -// QGLWidget functions that have not been implemented for QOpenGLWidget. -// Some of them may not end up in the final version (which is planned for a -// future release of Qt). - -// explicit QOpenGLWidget(const QSurfaceFormat& format, QWidget* parent=0, -// Qt::WindowFlags f=0); + explicit QOpenGLWidget(QWidget* parent = 0, Qt::WindowFlags f = 0); ~QOpenGLWidget(); -// void qglClearColor(const QColor& c) const; + void setFormat(const QSurfaceFormat &format); + QSurfaceFormat format() const; bool isValid() const; -// bool isSharing() const; void makeCurrent(); void doneCurrent(); -// void swapBuffers(); - - QSurfaceFormat format() const; + QOpenGLContext *context() const; GLuint defaultFramebufferObject() const; -// QPixmap renderPixmap(int w = 0, int h = 0, bool useContext = false); - QImage grabFrameBuffer(bool withAlpha = false); + QImage grabFramebuffer(); -// static QImage convertToGLFormat(const QImage& img); - -// QPaintEngine *paintEngine() const; - -// void drawTexture(const QRectF &target, GLuint textureId, GLenum textureTarget = GL_TEXTURE_2D); -// void drawTexture(const QPointF &point, GLuint textureId, GLenum textureTarget = GL_TEXTURE_2D); - -public Q_SLOTS: - void updateGL(); +Q_SIGNALS: + void aboutToCompose(); + void frameSwapped(); + void aboutToResize(); + void resized(); protected: -// bool event(QEvent *); virtual void initializeGL(); virtual void resizeGL(int w, int h); virtual void paintGL(); -// void setAutoBufferSwap(bool on); -// bool autoBufferSwap() const; + void paintEvent(QPaintEvent *e) Q_DECL_OVERRIDE; + void resizeEvent(QResizeEvent *e) Q_DECL_OVERRIDE; + bool event(QEvent *e) Q_DECL_OVERRIDE; - void paintEvent(QPaintEvent*); - void resizeEvent(QResizeEvent*); + int metric(QPaintDevice::PaintDeviceMetric metric) const Q_DECL_OVERRIDE; + QPaintDevice *redirected(QPoint *p) const Q_DECL_OVERRIDE; + QPaintEngine *paintEngine() const Q_DECL_OVERRIDE; -// virtual void glInit(); -// virtual void glDraw(); - -// QOpenGLWidget(QOpenGLWidgetPrivate &dd, -// const QGLFormat &format = QGLFormat(), -// QWidget *parent = 0, -// const QOpenGLWidget* shareWidget = 0, -// Qt::WindowFlags f = 0); private: Q_DISABLE_COPY(QOpenGLWidget) - - }; QT_END_NAMESPACE diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index e692a985eb..43ce9a82f7 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -5492,18 +5492,22 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP //paint the background if ((asRoot || q->autoFillBackground() || onScreen || q->testAttribute(Qt::WA_StyledBackground)) && !q->testAttribute(Qt::WA_OpaquePaintEvent) && !q->testAttribute(Qt::WA_NoSystemBackground)) { + beginBackingStorePainting(); QPainter p(q); paintBackground(&p, toBePainted, (asRoot || onScreen) ? flags | DrawAsRoot : 0); + endBackingStorePainting(); } if (!sharedPainter) setSystemClip(pdev, toBePainted.translated(offset)); if (!onScreen && !asRoot && !isOpaque && q->testAttribute(Qt::WA_TintedBackground)) { + beginBackingStorePainting(); QPainter p(q); QColor tint = q->palette().window().color(); tint.setAlphaF(qreal(.6)); p.fillRect(toBePainted.boundingRect(), tint); + endBackingStorePainting(); } } @@ -5513,24 +5517,30 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP << "geometry ==" << QRect(q->mapTo(q->window(), QPoint(0, 0)), q->size()); #endif + bool grabbed = false; +#ifndef QT_NO_OPENGL if (renderToTexture) { // This widget renders into a texture which is composed later. We just need to // punch a hole in the backingstore, so the texture will be visible. -#ifndef QT_NO_OPENGL - QPainter p(q); - - if (backingStore) { - p.setCompositionMode(QPainter::CompositionMode_Source); - p.fillRect(q->rect(), Qt::transparent); - } else { - // We are not drawing to a backingstore: fall back to QImage - p.drawImage(q->rect(), grabFramebuffer()); + if (!q->testAttribute(Qt::WA_AlwaysStackOnTop)) { + beginBackingStorePainting(); + QPainter p(q); + if (backingStore) { + p.setCompositionMode(QPainter::CompositionMode_Source); + p.fillRect(q->rect(), Qt::transparent); + } else { + // We are not drawing to a backingstore: fall back to QImage + p.drawImage(q->rect(), grabFramebuffer()); + grabbed = true; + } + endBackingStorePainting(); } -#endif - } else { + } +#endif // QT_NO_OPENGL + + if (!grabbed) { //actually send the paint event - QPaintEvent e(toBePainted); - QCoreApplication::sendSpontaneousEvent(q, &e); + sendPaintEvent(toBePainted); } // Native widgets need to be marked dirty on screen so painting will be done in correct context @@ -5586,6 +5596,13 @@ void QWidgetPrivate::drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QP } } +void QWidgetPrivate::sendPaintEvent(const QRegion &toBePainted) +{ + Q_Q(QWidget); + QPaintEvent e(toBePainted); + QCoreApplication::sendSpontaneousEvent(q, &e); +} + void QWidgetPrivate::render(QPaintDevice *target, const QPoint &targetOffset, const QRegion &sourceRegion, QWidget::RenderFlags renderFlags) { @@ -11968,7 +11985,7 @@ QOpenGLContext *QWidgetPrivate::shareContext() const } QWidgetPrivate *that = const_cast<QWidgetPrivate *>(this); if (!extra->topextra->shareContext) { - QOpenGLContext *ctx = new QOpenGLContext(); + QOpenGLContext *ctx = new QOpenGLContext; ctx->setShareContext(QOpenGLContextPrivate::globalShareContext()); ctx->setFormat(extra->topextra->window->format()); ctx->create(); @@ -11978,6 +11995,24 @@ QOpenGLContext *QWidgetPrivate::shareContext() const #endif // QT_NO_OPENGL } +#ifndef QT_NO_OPENGL +void QWidgetPrivate::sendComposeStatus(QWidget *w, bool end) +{ + QWidgetPrivate *wd = QWidgetPrivate::get(w); + if (!wd->textureChildSeen) + return; + if (end) + wd->endCompose(); + else + wd->beginCompose(); + for (int i = 0; i < wd->children.size(); ++i) { + w = qobject_cast<QWidget *>(wd->children.at(i)); + if (w && !w->isWindow() && !w->isHidden() && QWidgetPrivate::get(w)->textureChildSeen) + sendComposeStatus(w, end); + } +} +#endif // QT_NO_OPENGL + Q_WIDGETS_EXPORT QWidgetData *qt_qwidget_data(QWidget *widget) { return widget->data; diff --git a/src/widgets/kernel/qwidget_p.h b/src/widgets/kernel/qwidget_p.h index 6520832943..011d456a39 100644 --- a/src/widgets/kernel/qwidget_p.h +++ b/src/widgets/kernel/qwidget_p.h @@ -61,6 +61,7 @@ #include "QtGui/qregion.h" #include "QtGui/qinputmethod.h" #include "QtGui/qopengl.h" +#include "QtGui/qsurfaceformat.h" #include "QtWidgets/qsizepolicy.h" #include "QtWidgets/qstyle.h" #include "QtWidgets/qapplication.h" @@ -392,6 +393,7 @@ public: QWidget::RenderFlags renderFlags); void drawWidget(QPaintDevice *pdev, const QRegion &rgn, const QPoint &offset, int flags, QPainter *sharedPainter = 0, QWidgetBackingStore *backingStore = 0); + void sendPaintEvent(const QRegion &toBePainted); void paintSiblingsRecursive(QPaintDevice *pdev, const QObjectList& children, int index, @@ -627,7 +629,11 @@ public: #ifndef QT_NO_OPENGL virtual GLuint textureId() const { return 0; } - virtual QImage grabFramebuffer() const { return QImage(); } + virtual QImage grabFramebuffer() { return QImage(); } + virtual void beginBackingStorePainting() { } + virtual void endBackingStorePainting() { } + virtual void beginCompose() { } + virtual void endCompose() { } void setRenderToTexture() { renderToTexture = true; setTextureChildSeen(); } void setTextureChildSeen() { @@ -642,6 +648,11 @@ public: get(parent)->setTextureChildSeen(); } } + static void sendComposeStatus(QWidget *w, bool end); + // When using a QOpenGLWidget as viewport with QAbstractScrollArea, resize events are + // filtered away from the widget. This is fine for QGLWidget but bad for QOpenGLWidget + // since the fbo must be resized. We need an alternative way to notify. + virtual void resizeViewportFramebuffer() { } #endif // Variables. diff --git a/src/widgets/kernel/qwidgetbackingstore.cpp b/src/widgets/kernel/qwidgetbackingstore.cpp index 9d024fd359..6f635611f0 100644 --- a/src/widgets/kernel/qwidgetbackingstore.cpp +++ b/src/widgets/kernel/qwidgetbackingstore.cpp @@ -74,7 +74,8 @@ extern QRegion qt_dirtyRegion(QWidget *); * \a region is the region to be updated in \a widget coordinates. */ void QWidgetBackingStore::qt_flush(QWidget *widget, const QRegion ®ion, QBackingStore *backingStore, - QWidget *tlw, const QPoint &tlwOffset, QPlatformTextureList *widgetTextures) + QWidget *tlw, const QPoint &tlwOffset, QPlatformTextureList *widgetTextures, + QWidgetBackingStore *widgetBackingStore) { #ifdef QT_NO_OPENGL Q_UNUSED(widgetTextures); @@ -92,33 +93,29 @@ void QWidgetBackingStore::qt_flush(QWidget *widget, const QRegion ®ion, QBack QWidgetBackingStore::showYellowThing(widget, region, flushUpdate * 10, false); #endif - //The performance hit by doing this should be negligible. However, be aware that - //using this FPS when you have > 1 windowsurface can give you inaccurate FPS + if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen)) + return; static bool fpsDebug = qgetenv("QT_DEBUG_FPS").toInt(); if (fpsDebug) { - static QTime time = QTime::currentTime(); - static int frames = 0; - - frames++; - - if(time.elapsed() > 5000) { - double fps = double(frames * 1000) /time.restart(); - fprintf(stderr,"FPS: %.1f\n",fps); - frames = 0; + if (!widgetBackingStore->perfFrames++) + widgetBackingStore->perfTime.start(); + if (widgetBackingStore->perfTime.elapsed() > 5000) { + double fps = double(widgetBackingStore->perfFrames * 1000) / widgetBackingStore->perfTime.restart(); + qDebug("FPS: %.1f\n", fps); + widgetBackingStore->perfFrames = 0; } } - if (tlw->testAttribute(Qt::WA_DontShowOnScreen) || widget->testAttribute(Qt::WA_DontShowOnScreen)) - return; - QPoint offset = tlwOffset; if (widget != tlw) offset += widget->mapTo(tlw, QPoint()); #ifndef QT_NO_OPENGL - if (widgetTextures) + if (widgetTextures) { + widget->window()->d_func()->sendComposeStatus(widget->window(), false); backingStore->handle()->composeAndFlush(widget->windowHandle(), region, offset, widgetTextures, tlw->d_func()->shareContext()); - else + widget->window()->d_func()->sendComposeStatus(widget->window(), true); + } else #endif backingStore->flush(region, widget->windowHandle(), offset); } @@ -270,7 +267,7 @@ void QWidgetBackingStore::unflushPaint(QWidget *widget, const QRegion &rgn) return; const QPoint offset = widget->mapTo(tlw, QPoint()); - qt_flush(widget, rgn, tlwExtra->backingStoreTracker->store, tlw, offset); + qt_flush(widget, rgn, tlwExtra->backingStoreTracker->store, tlw, offset, 0, tlw->d_func()->maybeBackingStore()); } #endif // QT_NO_PAINT_DEBUG @@ -519,6 +516,8 @@ void QWidgetBackingStore::markDirty(const QRegion &rgn, QWidget *widget, const QPoint offset = widget->mapTo(tlw, QPoint()); if (QWidgetPrivate::get(widget)->renderToTexture) { + if (!widget->d_func()->inDirtyList) + addDirtyRenderToTextureWidget(widget); if (!updateRequestSent || updateTime == UpdateNow) sendUpdateRequest(tlw, updateTime); return; @@ -613,6 +612,8 @@ void QWidgetBackingStore::markDirty(const QRect &rect, QWidget *widget, } if (QWidgetPrivate::get(widget)->renderToTexture) { + if (!widget->d_func()->inDirtyList) + addDirtyRenderToTextureWidget(widget); if (!updateRequestSent || updateTime == UpdateNow) sendUpdateRequest(tlw, updateTime); return; @@ -710,6 +711,7 @@ void QWidgetBackingStore::removeDirtyWidget(QWidget *w) dirtyWidgetsRemoveAll(w); dirtyOnScreenWidgetsRemoveAll(w); + dirtyRenderToTextureWidgets.removeAll(w); resetWidget(w); QWidgetPrivate *wd = w->d_func(); @@ -744,7 +746,8 @@ QWidgetBackingStore::QWidgetBackingStore(QWidget *topLevel) widgetTextures(0), fullUpdatePending(0), updateRequestSent(0), - textureListWatcher(0) + textureListWatcher(0), + perfFrames(0) { store = tlw->backingStore(); Q_ASSERT(store); @@ -755,9 +758,11 @@ QWidgetBackingStore::QWidgetBackingStore(QWidget *topLevel) QWidgetBackingStore::~QWidgetBackingStore() { - for (int c = 0; c < dirtyWidgets.size(); ++c) { + for (int c = 0; c < dirtyWidgets.size(); ++c) resetWidget(dirtyWidgets.at(c)); - } + for (int c = 0; c < dirtyRenderToTextureWidgets.size(); ++c) + resetWidget(dirtyRenderToTextureWidgets.at(c)); + #ifndef QT_NO_OPENGL delete dirtyOnScreenWidgets; #endif @@ -944,7 +949,7 @@ void QWidgetBackingStore::sync(QWidget *exposedWidget, const QRegion &exposedReg // Nothing to repaint. if (!isDirty() && store->size().isValid()) { - qt_flush(exposedWidget, exposedRegion, store, tlw, tlwOffset, widgetTextures); + qt_flush(exposedWidget, exposedRegion, store, tlw, tlwOffset, widgetTextures, this); return; } @@ -961,7 +966,8 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatfo { QWidgetPrivate *wd = QWidgetPrivate::get(widget); if (wd->renderToTexture) - widgetTextures->appendTexture(wd->textureId(), QRect(widget->mapTo(tlw, QPoint()), widget->size())); + widgetTextures->appendTexture(wd->textureId(), QRect(widget->mapTo(tlw, QPoint()), widget->size()), + widget->testAttribute(Qt::WA_AlwaysStackOnTop)); for (int i = 0; i < wd->children.size(); ++i) { QWidget *w = qobject_cast<QWidget *>(wd->children.at(i)); @@ -1125,13 +1131,47 @@ void QWidgetBackingStore::doSync() #endif if (toClean.isEmpty()) { - // Nothing to repaint. However, we might have newly exposed areas on the - // screen if this function was called from sync(QWidget *, QRegion)), so - // we have to make sure those are flushed. + // Nothing to repaint. However renderToTexture widgets are handled + // specially, they are not in the regular dirty list, in order to + // prevent triggering unnecessary backingstore painting when only the + // OpenGL content changes. Check if we have such widgets in the special + // dirty list. + for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i) { + QWidget *w = dirtyRenderToTextureWidgets.at(i); + w->d_func()->sendPaintEvent(w->rect()); + resetWidget(w); + } + dirtyRenderToTextureWidgets.clear(); + + // We might have newly exposed areas on the screen if this function was + // called from sync(QWidget *, QRegion)), so we have to make sure those + // are flushed. We also need to composite the renderToTexture widgets. flush(); + return; } + // There is something other dirty than the renderToTexture widgets. + // Now it is time to include the renderToTexture ones among the others. + if (widgetTextures && widgetTextures->count()) { + for (int i = 0; i < widgetTextures->count(); ++i) { + const QRect rect = widgetTextures->geometry(i); // mapped to the tlw already + dirty += rect; + toClean += rect; + } + } + // The dirtyRenderToTextureWidgets list is useless here, so just reset. As + // unintuitive as it is, we need to send paint events to renderToTexture + // widgets always when something (any widget) needs to be updated, even if + // the renderToTexture widget itself is clean, i.e. there was no update() + // call for it. This is because changing any widget will cause a flush and + // so a potentially blocking buffer swap for the window, and skipping paints + // for the renderToTexture widgets would make it impossible to have smoothly + // animated content in them. + for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i) + resetWidget(dirtyRenderToTextureWidgets.at(i)); + dirtyRenderToTextureWidgets.clear(); + #ifndef QT_NO_GRAPHICSVIEW if (tlw->d_func()->extra->proxyWidget) { updateStaticContentsSize(); @@ -1200,15 +1240,17 @@ void QWidgetBackingStore::flush(QWidget *widget) { if (!dirtyOnScreen.isEmpty()) { QWidget *target = widget ? widget : tlw; - qt_flush(target, dirtyOnScreen, store, tlw, tlwOffset, widgetTextures); + qt_flush(target, dirtyOnScreen, store, tlw, tlwOffset, widgetTextures, this); dirtyOnScreen = QRegion(); + if (widgetTextures && widgetTextures->count()) + return; } if (!dirtyOnScreenWidgets || dirtyOnScreenWidgets->isEmpty()) { #ifndef QT_NO_OPENGL if (widgetTextures && widgetTextures->count()) { QWidget *target = widget ? widget : tlw; - qt_flush(target, QRegion(), store, tlw, tlwOffset, widgetTextures); + qt_flush(target, QRegion(), store, tlw, tlwOffset, widgetTextures, this); } #endif return; @@ -1218,7 +1260,7 @@ void QWidgetBackingStore::flush(QWidget *widget) QWidget *w = dirtyOnScreenWidgets->at(i); QWidgetPrivate *wd = w->d_func(); Q_ASSERT(wd->needsFlush); - qt_flush(w, *wd->needsFlush, store, tlw, tlwOffset); + qt_flush(w, *wd->needsFlush, store, tlw, tlwOffset, 0, this); *wd->needsFlush = QRegion(); } dirtyOnScreenWidgets->clear(); diff --git a/src/widgets/kernel/qwidgetbackingstore_p.h b/src/widgets/kernel/qwidgetbackingstore_p.h index 88404ce205..0da85e33d5 100644 --- a/src/widgets/kernel/qwidgetbackingstore_p.h +++ b/src/widgets/kernel/qwidgetbackingstore_p.h @@ -133,6 +133,7 @@ private: QRegion dirty; // needsRepaint QRegion dirtyFromPreviousSync; QVector<QWidget *> dirtyWidgets; + QVector<QWidget *> dirtyRenderToTextureWidgets; QVector<QWidget *> *dirtyOnScreenWidgets; QList<QWidget *> staticWidgets; QPlatformTextureList *widgetTextures; @@ -143,6 +144,8 @@ private: QPoint tlwOffset; QPlatformTextureListWatcher *textureListWatcher; + QElapsedTimer perfTime; + int perfFrames; void sendUpdateRequest(QWidget *widget, UpdateTime updateTime); @@ -150,7 +153,8 @@ private: static void unflushPaint(QWidget *widget, const QRegion &rgn); static void qt_flush(QWidget *widget, const QRegion ®ion, QBackingStore *backingStore, QWidget *tlw, const QPoint &tlwOffset, - QPlatformTextureList *widgetTextures = 0); + QPlatformTextureList *widgetTextures, + QWidgetBackingStore *widgetBackingStore); void doSync(); bool bltRect(const QRect &rect, int dx, int dy, QWidget *widget); @@ -184,6 +188,16 @@ private: } } + inline void addDirtyRenderToTextureWidget(QWidget *widget) + { + if (widget && !widget->d_func()->inDirtyList && !widget->data->in_destructor) { + QWidgetPrivate *widgetPrivate = widget->d_func(); + Q_ASSERT(widgetPrivate->renderToTexture); + dirtyRenderToTextureWidgets.append(widget); + widgetPrivate->inDirtyList = true; + } + } + inline void dirtyWidgetsRemoveAll(QWidget *widget) { int i = 0; diff --git a/src/widgets/widgets/qabstractscrollarea.cpp b/src/widgets/widgets/qabstractscrollarea.cpp index e1e933cdd8..e830ac3c76 100644 --- a/src/widgets/widgets/qabstractscrollarea.cpp +++ b/src/widgets/widgets/qabstractscrollarea.cpp @@ -1191,6 +1191,10 @@ bool QAbstractScrollArea::viewportEvent(QEvent *e) case QEvent::DragMove: case QEvent::DragLeave: #endif + // QOpenGLWidget needs special support because it has to know + // its size has changed, so that it can resize its fbo. + if (e->type() == QEvent::Resize) + QWidgetPrivate::get(viewport())->resizeViewportFramebuffer(); return QFrame::event(e); case QEvent::LayoutRequest: #ifndef QT_NO_GESTURES |