diff options
Diffstat (limited to 'src/quickwidgets/qquickwidget.cpp')
-rw-r--r-- | src/quickwidgets/qquickwidget.cpp | 206 |
1 files changed, 160 insertions, 46 deletions
diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index d6de552017..de3692afb0 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -59,9 +59,11 @@ #include <QtGui/private/qguiapplication_p.h> #include <QtGui/qpa/qplatformintegration.h> +#ifndef QT_NO_OPENGL #include <QtGui/QOpenGLContext> #include <QtGui/QOpenGLFunctions> #include <QtGui/private/qopenglextensions_p.h> +#endif #include <QtGui/QPainter> #include <QtQuick/QSGRendererInterface> @@ -74,7 +76,9 @@ QT_BEGIN_NAMESPACE +#ifndef QT_NO_OPENGL extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); +#endif class QQuickWidgetRenderControl : public QQuickRenderControl { @@ -104,18 +108,17 @@ void QQuickWidgetPrivate::init(QQmlEngine* e) useSoftwareRenderer = true; if (!useSoftwareRenderer) { +#ifndef QT_NO_OPENGL if (QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::RasterGLSurface)) setRenderToTexture(); else +#endif qWarning("QQuickWidget is not supported on this platform."); } engine = e; - if (engine.isNull()) - engine = new QQmlEngine(q); - - if (!engine.data()->incubationController()) + if (!engine.isNull() && !engine.data()->incubationController()) engine.data()->setIncubationController(offscreenWindow->incubationController()); #ifndef QT_NO_DRAGANDDROP @@ -128,8 +131,19 @@ void QQuickWidgetPrivate::init(QQmlEngine* e) QObject::connect(renderControl, SIGNAL(sceneChanged()), q, SLOT(triggerUpdate())); } +void QQuickWidgetPrivate::ensureEngine() const +{ + Q_Q(const QQuickWidget); + if (!engine.isNull()) + return; + + engine = new QQmlEngine(const_cast<QQuickWidget*>(q)); + engine.data()->setIncubationController(offscreenWindow->incubationController()); +} + void QQuickWidgetPrivate::invalidateRenderControl() { +#ifndef QT_NO_OPENGL if (!useSoftwareRenderer) { if (!context) // this is not an error, could be called before creating the context, or multiple times return; @@ -140,13 +154,25 @@ void QQuickWidgetPrivate::invalidateRenderControl() return; } } +#endif renderControl->invalidate(); } void QQuickWidgetPrivate::handleWindowChange() { + if (offscreenWindow->isPersistentSceneGraph() && qGuiApp->testAttribute(Qt::AA_ShareOpenGLContexts)) + return; + + // In case of !isPersistentSceneGraph or when we need a new context due to + // the need to share resources with the new window's context, we must both + // invalidate the scenegraph and destroy the context. With + // QQuickRenderControl destroying the context must be preceded by an + // invalidate to prevent being left with dangling context references in the + // rendercontrol. + invalidateRenderControl(); + if (!useSoftwareRenderer) destroyContext(); } @@ -157,9 +183,11 @@ QQuickWidgetPrivate::QQuickWidgetPrivate() , offscreenWindow(0) , offscreenSurface(0) , renderControl(0) +#ifndef QT_NO_OPENGL , fbo(0) , resolvedFbo(0) , context(0) +#endif , resizeMode(QQuickWidget::SizeViewToRootObject) , initialSize(0,0) , eventPending(false) @@ -178,6 +206,7 @@ QQuickWidgetPrivate::~QQuickWidgetPrivate() delete renderControl; delete offscreenWindow; } else { +#ifndef QT_NO_OPENGL // context and offscreenSurface are current at this stage, if the context was created. Q_ASSERT(!context || (QOpenGLContext::currentContext() == context && context->surface() == offscreenSurface)); delete renderControl; // always delete the rendercontrol first @@ -186,16 +215,14 @@ QQuickWidgetPrivate::~QQuickWidgetPrivate() delete fbo; destroyContext(); +#endif } } void QQuickWidgetPrivate::execute() { Q_Q(QQuickWidget); - if (!engine) { - qWarning() << "QQuickWidget: invalid qml engine."; - return; - } + ensureEngine(); if (root) { delete root; @@ -217,19 +244,21 @@ void QQuickWidgetPrivate::execute() } } -void QQuickWidgetPrivate::itemGeometryChanged(QQuickItem *resizeItem, const QRectF &newGeometry, const QRectF &oldGeometry) +void QQuickWidgetPrivate::itemGeometryChanged(QQuickItem *resizeItem, QQuickGeometryChange change, + const QRectF &diff) { Q_Q(QQuickWidget); if (resizeItem == root && resizeMode == QQuickWidget::SizeViewToRootObject) { // wait for both width and height to be changed resizetimer.start(0,q); } - QQuickItemChangeListener::itemGeometryChanged(resizeItem, newGeometry, oldGeometry); + QQuickItemChangeListener::itemGeometryChanged(resizeItem, change, diff); } void QQuickWidgetPrivate::render(bool needsSync) { if (!useSoftwareRenderer) { +#ifndef QT_NO_OPENGL // createFramebufferObject() bails out when the size is empty. In this case // we cannot render either. if (!fbo) @@ -259,6 +288,7 @@ void QQuickWidgetPrivate::render(bool needsSync) static_cast<QOpenGLExtensions *>(context->functions())->flushShared(); QOpenGLContextPrivate::get(context)->defaultFboRedirect = 0; +#endif } else { //Software Renderer if (needsSync) { @@ -268,7 +298,7 @@ void QQuickWidgetPrivate::render(bool needsSync) QQuickWindowPrivate *cd = QQuickWindowPrivate::get(offscreenWindow); auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(cd->renderer); - if (softwareRenderer) { + if (softwareRenderer && !softwareImage.isNull()) { softwareRenderer->setCurrentPaintDevice(&softwareImage); renderControl->render(); @@ -313,10 +343,12 @@ void QQuickWidgetPrivate::renderSceneGraph() QImage QQuickWidgetPrivate::grabFramebuffer() { if (!useSoftwareRenderer) { +#ifndef QT_NO_OPENGL if (!context) return QImage(); context->makeCurrent(offscreenSurface); +#endif } return renderControl->grab(); } @@ -386,6 +418,36 @@ QObject *QQuickWidgetPrivate::focusObject() entire purpose of QQuickWidget is to render Quick scenes without a separate native window, hence making it a native widget should always be avoided. + \section1 Scene graph and context persistency + + QQuickWidget honors QQuickWindow::isPersistentSceneGraph(), meaning that + applications can decide - by calling + QQuickWindow::setPersistentSceneGraph() on the window returned from the + quickWindow() function - to let scenegraph nodes and other Qt Quick scene + related resources be released whenever the widget becomes hidden. By default + persistency is enabled, just like with QQuickWindow. + + When running with the OpenGL backend of the scene graph, QQuickWindow + offers the possibility to disable persistent OpenGL contexts as well. This + setting is currently ignored by QQuickWidget and the context is always + persistent. The OpenGL context is thus not destroyed when hiding the + widget. The context is destroyed only when the widget is destroyed or when + the widget gets reparented into another top-level widget's child hierarchy. + However, some applications, in particular those that have their own + graphics resources due to performing custom OpenGL rendering in the Qt + Quick scene, may wish to disable the latter since they may not be prepared + to handle the loss of the context when moving a QQuickWidget into another + window. Such applications can set the + QCoreApplication::AA_ShareOpenGLContexts attribute. For a discussion on the + details of resource initialization and cleanup, refer to the QOpenGLWidget + documentation. + + \note QQuickWidget offers less fine-grained control over its internal + OpenGL context than QOpenGLWidget, and there are subtle differences, most + notably that disabling the persistent scene graph will lead to destroying + the context on a window change regardless of the presence of + QCoreApplication::AA_ShareOpenGLContexts. + \section1 Limitations Putting other widgets underneath and making the QQuickWidget transparent will not lead @@ -406,6 +468,13 @@ QObject *QQuickWidgetPrivate::focusObject() Qt::WA_TranslucentBackground on the top-level window, request an alpha channel, and change the Qt Quick Scenegraph's clear color to Qt::transparent via setClearColor(). + \section1 Support when not using OpenGL + + In addition to OpenGL, the \c software backend of Qt Quick also supports + QQuickWidget. Other backends, for example the Direct 3D 12 one, are not + compatible however and attempting to construct a QQuickWidget will lead to + problems. + \sa {Exposing Attributes of C++ Types to QML}, {Qt Quick Widgets Example}, QQuickView */ @@ -452,7 +521,6 @@ QQuickWidget::QQuickWidget(QQmlEngine* engine, QWidget *parent) { setMouseTracking(true); setFocusPolicy(Qt::StrongFocus); - Q_ASSERT(engine); d_func()->init(engine); } @@ -507,8 +575,8 @@ void QQuickWidget::setContent(const QUrl& url, QQmlComponent *component, QObject d->component = component; if (d->component && d->component->isError()) { - QList<QQmlError> errorList = d->component->errors(); - foreach (const QQmlError &error, errorList) { + const QList<QQmlError> errorList = d->component->errors(); + for (const QQmlError &error : errorList) { QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), 0).warning() << error; } @@ -538,7 +606,8 @@ QUrl QQuickWidget::source() const QQmlEngine* QQuickWidget::engine() const { Q_D(const QQuickWidget); - return d->engine ? const_cast<QQmlEngine *>(d->engine.data()) : 0; + d->ensureEngine(); + return const_cast<QQmlEngine *>(d->engine.data()); } /*! @@ -551,7 +620,8 @@ QQmlEngine* QQuickWidget::engine() const QQmlContext* QQuickWidget::rootContext() const { Q_D(const QQuickWidget); - return d->engine ? d->engine.data()->rootContext() : 0; + d->ensureEngine(); + return d->engine.data()->rootContext(); } /*! @@ -596,7 +666,7 @@ QQmlContext* QQuickWidget::rootContext() const QQuickWidget::Status QQuickWidget::status() const { Q_D(const QQuickWidget); - if (!d->engine) + if (!d->engine && !d->source.isEmpty()) return QQuickWidget::Error; if (!d->component) @@ -622,11 +692,12 @@ QList<QQmlError> QQuickWidget::errors() const if (d->component) errs = d->component->errors(); - if (!d->engine) { + if (!d->engine && !d->source.isEmpty()) { QQmlError error; error.setDescription(QLatin1String("QQuickWidget: invalid qml engine.")); errs << error; - } else if (d->component && d->component->status() == QQmlComponent::Ready && !d->root) { + } + if (d->component && d->component->status() == QQmlComponent::Ready && !d->root) { QQmlError error; error.setDescription(QLatin1String("QQuickWidget: invalid root object.")); errs << error; @@ -694,9 +765,14 @@ void QQuickWidgetPrivate::updateSize() q->resize(newSize); } } else if (resizeMode == QQuickWidget::SizeRootObjectToView) { - if (!qFuzzyCompare(q->width(), root->width())) + bool needToUpdateWidth = !qFuzzyCompare(q->width(), root->width()); + bool needToUpdateHeight = !qFuzzyCompare(q->height(), root->height()); + + if (needToUpdateWidth && needToUpdateHeight) + root->setSize(QSizeF(q->width(), q->height())); + else if (needToUpdateWidth) root->setWidth(q->width()); - if (!qFuzzyCompare(q->height(), root->height())) + else if (needToUpdateHeight) root->setHeight(q->height()); } } @@ -759,9 +835,11 @@ void QQuickWidgetPrivate::handleContextCreationFailure(const QSurfaceFormat &for // Never called by Software Rendering backend void QQuickWidgetPrivate::createContext() { +#ifndef QT_NO_OPENGL Q_Q(QQuickWidget); - // On hide-show we invalidate() but our context is kept. - // We nonetheless need to initialize() again. + + // On hide-show we may invalidate() (when !isPersistentSceneGraph) but our + // context is kept. We may need to initialize() again, though. const bool reinit = context && !offscreenWindow->openglContext(); if (!reinit) { @@ -795,9 +873,11 @@ void QQuickWidgetPrivate::createContext() offscreenSurface->create(); } - if (context->makeCurrent(offscreenSurface)) - renderControl->initialize(context); - else + if (context->makeCurrent(offscreenSurface)) { + if (!offscreenWindow->openglContext()) + renderControl->initialize(context); + } else +#endif qWarning("QQuickWidget: Failed to make context current"); } @@ -806,8 +886,10 @@ void QQuickWidgetPrivate::destroyContext() { delete offscreenSurface; offscreenSurface = 0; +#ifndef QT_NO_OPENGL delete context; context = 0; +#endif } void QQuickWidget::createFramebufferObject() @@ -819,6 +901,12 @@ void QQuickWidget::createFramebufferObject() if (size().isEmpty()) return; + // Even though this is just an offscreen window we should set the position on it, as it might be + // useful for an item to know the actual position of the scene. + // Note: The position will be update when we get a move event (see: updatePosition()). + const QPoint &globalPos = mapToGlobal(QPoint(0, 0)); + d->offscreenWindow->setGeometry(globalPos.x(), globalPos.y(), width(), height()); + if (d->useSoftwareRenderer) { const QSize imageSize = size() * devicePixelRatio(); d->softwareImage = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); @@ -826,6 +914,7 @@ void QQuickWidget::createFramebufferObject() return; } +#ifndef QT_NO_OPENGL QOpenGLContext *context = d->offscreenWindow->openglContext(); if (!context) { @@ -833,9 +922,10 @@ void QQuickWidget::createFramebufferObject() return; } - if (context->shareContext() != QWidgetPrivate::get(window())->shareContext()) { - context->setShareContext(QWidgetPrivate::get(window())->shareContext()); - context->setScreen(context->shareContext()->screen()); + QOpenGLContext *shareWindowContext = QWidgetPrivate::get(window())->shareContext(); + if (shareWindowContext && context->shareContext() != shareWindowContext) { + context->setShareContext(shareWindowContext); + context->setScreen(shareWindowContext->screen()); if (!context->create()) qWarning("QQuickWidget: Failed to recreate context"); // The screen may be different so we must recreate the offscreen surface too. @@ -884,11 +974,6 @@ void QQuickWidget::createFramebufferObject() } #endif - // Even though this is just an offscreen window we should set the position on it, as it might be - // useful for an item to know the actual position of the scene. - // Note: The position will be update when we get a move event (see: updatePosition()). - const QPoint &globalPos = mapToGlobal(QPoint(0, 0)); - d->offscreenWindow->setGeometry(globalPos.x(), globalPos.y(), width(), height()); d->offscreenWindow->setRenderTarget(d->fbo); if (samples > 0) @@ -898,6 +983,7 @@ void QQuickWidget::createFramebufferObject() // Having one would mean create() was called and platforms that only support // a single native window were in trouble. Q_ASSERT(!d->offscreenWindow->handle()); +#endif } void QQuickWidget::destroyFramebufferObject() @@ -909,10 +995,12 @@ void QQuickWidget::destroyFramebufferObject() return; } +#ifndef QT_NO_OPENGL delete d->fbo; d->fbo = 0; delete d->resolvedFbo; d->resolvedFbo = 0; +#endif } QQuickWidget::ResizeMode QQuickWidget::resizeMode() const @@ -930,8 +1018,8 @@ void QQuickWidget::continueExecute() disconnect(d->component, SIGNAL(statusChanged(QQmlComponent::Status)), this, SLOT(continueExecute())); if (d->component->isError()) { - QList<QQmlError> errorList = d->component->errors(); - foreach (const QQmlError &error, errorList) { + const QList<QQmlError> errorList = d->component->errors(); + for (const QQmlError &error : errorList) { QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), 0).warning() << error; } @@ -942,8 +1030,8 @@ void QQuickWidget::continueExecute() QObject *obj = d->component->create(); if (d->component->isError()) { - QList<QQmlError> errorList = d->component->errors(); - foreach (const QQmlError &error, errorList) { + const QList<QQmlError> errorList = d->component->errors(); + for (const QQmlError &error : errorList) { QMessageLogger(error.url().toString().toLatin1().constData(), error.line(), 0).warning() << error; } @@ -990,6 +1078,7 @@ void QQuickWidgetPrivate::setRootObject(QObject *obj) } } +#ifndef QT_NO_OPENGL GLuint QQuickWidgetPrivate::textureId() const { Q_Q(const QQuickWidget); @@ -1001,6 +1090,7 @@ GLuint QQuickWidgetPrivate::textureId() const return resolvedFbo ? resolvedFbo->texture() : (fbo ? fbo->texture() : 0); } +#endif /*! \internal @@ -1090,7 +1180,7 @@ void QQuickWidget::resizeEvent(QResizeEvent *e) createFramebufferObject(); } } else { - +#ifndef QT_NO_OPENGL if (d->context) { // Bail out when receiving a resize after scenegraph invalidation. This can happen // during hide - resize - show sequences and also during application exit. @@ -1112,7 +1202,7 @@ void QQuickWidget::resizeEvent(QResizeEvent *e) qWarning("QQuickWidget::resizeEvent() no OpenGL context"); return; } - +#endif } d->render(needsSync); @@ -1180,15 +1270,20 @@ void QQuickWidget::showEvent(QShowEvent *) d->createContext(); if (d->offscreenWindow->openglContext()) { d->render(true); - if (d->updatePending) { + // render() may have led to a QQuickWindow::update() call (for + // example, having a scene with a QQuickFramebufferObject::Renderer + // calling update() in its render()) which in turn results in + // renderRequested in the rendercontrol, ending up in + // triggerUpdate. In this case just calling update() is not + // acceptable, we need the full renderSceneGraph issued from + // timerEvent(). + if (!d->eventPending && d->updatePending) { d->updatePending = false; update(); } } else { triggerUpdate(); } - } else { - triggerUpdate(); } QWindowPrivate *offscreenPrivate = QWindowPrivate::get(d->offscreenWindow); if (!offscreenPrivate->visible) { @@ -1204,7 +1299,8 @@ void QQuickWidget::showEvent(QShowEvent *) void QQuickWidget::hideEvent(QHideEvent *) { Q_D(QQuickWidget); - d->invalidateRenderControl(); + if (!d->offscreenWindow->isPersistentSceneGraph()) + d->invalidateRenderControl(); QWindowPrivate *offscreenPrivate = QWindowPrivate::get(d->offscreenWindow); if (offscreenPrivate->visible) { offscreenPrivate->visible = false; @@ -1305,7 +1401,24 @@ bool QQuickWidget::event(QEvent *e) break; case QEvent::ScreenChangeInternal: - if (d->fbo || d->useSoftwareRenderer) { + if (QWindow *window = this->window()->windowHandle()) { + QScreen *newScreen = window->screen(); + + if (d->offscreenWindow) + d->offscreenWindow->setScreen(newScreen); + if (d->offscreenSurface) + d->offscreenSurface->setScreen(newScreen); +#ifndef QT_NO_OPENGL + if (d->context) + d->context->setScreen(newScreen); +#endif + } + + if (d->useSoftwareRenderer +#ifndef QT_NO_OPENGL + || d->fbo +#endif + ) { // This will check the size taking the devicePixelRatio into account // and recreate if needed. createFramebufferObject(); @@ -1490,7 +1603,8 @@ void QQuickWidget::paintEvent(QPaintEvent *event) painter.drawImage(rect(), d->softwareImage); } else { //Paint only the updated areas - for (auto targetRect : d->updateRegion.rects()) { + const auto rects = d->updateRegion.rects(); + for (auto targetRect : rects) { auto sourceRect = QRect(targetRect.topLeft() * devicePixelRatio(), targetRect.size() * devicePixelRatio()); painter.drawImage(targetRect, d->softwareImage, sourceRect); } |