diff options
author | Laszlo Agocs <laszlo.agocs@qt.io> | 2021-10-25 15:21:31 +0200 |
---|---|---|
committer | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2022-03-11 21:25:00 +0100 |
commit | 68a4c5da9a080101cccd8a3b2edb1c908da0ca8e (patch) | |
tree | e89a8c02539147d30cb7529441d1a9e055163760 /src/widgets/kernel/qwidgetrepaintmanager.cpp | |
parent | 8ccf1080fcb131940939369271e6828b178b8e9e (diff) |
Compose render-to-texture widgets through QRhi
QPlatformTextureList holds a QRhiTexture instead of GLuint. A
QPlatformBackingStore now optionally can own a QRhi and a
QRhiSwapChain for the associated window. Non-GL rendering must use
this QRhi everywhere, whereas GL (QOpenGLWidget) can choose to still
rely on resource sharing between contexts. A widget tells that it
wants QRhi and the desired configuration in a new virtual function in
QWidgetPrivate returning a QPlatformBackingStoreRhiConfig. This is
evaluated (among a top-level's all children) upon create() before
creating the repaint manager and the QWidgetWindow.
In QOpenGLWidget what do request is obvious: it will request an
OpenGL-based QRhi. QQuickWidget (or a potential future QRhiWidget)
will be more interesting: it needs to honor the standard Qt Quick
env.vars. and QQuickWindow APIs (or, in whatever way the user
configured the QRhiWidget), and so will set up the config struct
accordingly.
In addition, the rhiconfig and surface type is (re)evaluated when
(re)parenting a widget to a new tlw. If needed, this will now trigger
a destroy - create on the tlw. This should be be safe to do in
setParent. When multiple child widgets report an enabled rhiconfig,
the first one (the first child encountered) wins. So e.g. attempting
to have a QOpenGLWidget and a Vulkan-based QQuickWidget in the same
top-level window will fail one of the widgets (it likely won't
render).
RasterGLSurface is no longer used by widgets. Rather, the appropriate
surface type is chosen.
The rhi support in the backingstore is usable without widgets as well.
To make rhiFlush() functional, one needs to call setRhiConfig() after
creating the QBackingStore. (like QWidget does to top-level windows)
Most of the QT_NO_OPENGL ifdefs are eliminated all over the place.
Everything with QRhi is unconditional code at compile time, except the
actual initialization.
Having to plumb the widget tlw's shareContext (or, now, the QRhi)
through QWindowPrivate is no longer needed. The old approach does not
scale: to implement composeAndFlush (now rhiFlush) we need more than
just a QRhi object, and this way we no longer pollute everything
starting from the widget level (QWidget's topextra -> QWidgetWindow ->
QWindowPrivate) just to send data around.
The BackingStoreOpenGLSupport interface and the QtGui - QtOpenGL split
is all gone. Instead, there is a QBackingStoreDefaultCompositor in
QtGui which is what the default implementations of composeAndFlush and
toTexture call. (overriding composeAndFlush and co. f.ex. in eglfs
should continue working mostly as-is, apart from adapting to the
texture list changes and getting the native OpenGL texture id out of
the QRhiTexture)
As QQuickWidget is way too complicated to just port as-is, an rhi
manual test (rhiwidget) is introduced as a first step, in ordewr to
exercise a simple, custom render-to-texture widget that does something
using a (not necessarily OpenGL-backed) QRhi and acts as fully
functional QWidget (modeled after QOpenGLWidget). This can also form
the foundation of a potential future QRhiWidget.
It is also possible to force the QRhi-based flushing always,
regardless of the presence of render-to-texture widgets. To exercise
this, set the env.var. QT_WIDGETS_RHI=1. This picks a
platform-specific default, and can be overridden with
QT_WIDGETS_RHI_BACKEND. (in sync with Qt Quick) This can eventually be
extended to query the platform plugin as well to check if the platform
plugin prefers to always do flushes with a 3D API.
QOpenGLWidget should work like before from the user's perspective, while
internally it has to do some things differently to play nice and prevent
regressions with the new rendering architecture. To exercise this
better, the qopenglwidget example gets a new tab-based view (that could
perhaps replace the example's main window later on?). The openglwidget
manual test is made compatible with Qt 6, and gets a counterpart in form
of the dockedopenglwidget manual test, which is a modified version of
the cube example that features dock widgets. This is relevant in
particular because render-to-texture widgets within a QDockWidget has
its own specific quirks, with logic taking this into account, hence
testing is essential.
For existing applications there are two important consequences with
this patch in place:
- Once the rhi-based composition is enabled, it stays active for the
lifetime of the top-level window.
- Dynamically creating and parenting the first render-to-texture
widget to an already created tlw will destroy and recreate the tlw
(and the underlying window). The visible effects of this depend on the
platform. (e.g. the window may disappear and reappear on some,
whereas with other windowing systems it is not noticeable at all -
this is not really different from similar situtions with reparenting
or when moving windows between screens, so should be acceptable in
practice)
- On iOS raster windows are flushed with Metal (and rhi) from now on
(previously this was through OpenGL by making flush() call
composeAndFlush().
Change-Id: Id05bd0f7a26fa845f8b7ad8eedda3b0e78ab7a4e
Reviewed-by: Tor Arne Vestbø <tor.arne.vestbo@qt.io>
Diffstat (limited to 'src/widgets/kernel/qwidgetrepaintmanager.cpp')
-rw-r--r-- | src/widgets/kernel/qwidgetrepaintmanager.cpp | 109 |
1 files changed, 35 insertions, 74 deletions
diff --git a/src/widgets/kernel/qwidgetrepaintmanager.cpp b/src/widgets/kernel/qwidgetrepaintmanager.cpp index 16ff8ea721..1094c2c3b0 100644 --- a/src/widgets/kernel/qwidgetrepaintmanager.cpp +++ b/src/widgets/kernel/qwidgetrepaintmanager.cpp @@ -65,12 +65,11 @@ QT_BEGIN_NAMESPACE -#ifndef QT_NO_OPENGL Q_GLOBAL_STATIC(QPlatformTextureList, qt_dummy_platformTextureList) // Watches one or more QPlatformTextureLists for changes in the lock state and // triggers a backingstore sync when all the registered lists turn into -// unlocked state. This is essential when a custom composeAndFlush() +// unlocked state. This is essential when a custom rhiFlush() // implementation in a platform plugin is not synchronous and keeps // holding on to the textures for some time even after returning from there. class QPlatformTextureListWatcher : public QObject @@ -105,7 +104,6 @@ private: QHash<QPlatformTextureList *, bool> m_locked; QWidgetRepaintManager *m_repaintManager; }; -#endif // --------------------------------------------------------------------------- @@ -374,7 +372,6 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update qCInfo(lcWidgetPainting) << "Sending update request to" << widget << "with" << updateTime; -#ifndef QT_NO_OPENGL // Having every repaint() leading to a sync/flush is bad as it causes // compositing and waiting for vsync each and every time. Change to // UpdateLater, except for approx. once per frame to prevent starvation in @@ -392,7 +389,6 @@ void QWidgetRepaintManager::sendUpdateRequest(QWidget *widget, UpdateTime update updateTime = UpdateLater; } } -#endif switch (updateTime) { case UpdateLater: @@ -571,7 +567,6 @@ bool QWidgetRepaintManager::bltRect(const QRect &rect, int dx, int dy, QWidget * // --------------------------------------------------------------------------- -#ifndef QT_NO_OPENGL static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, QPlatformTextureList *widgetTextures, QList<QWidget *> *nativeChildren) @@ -580,7 +575,7 @@ static void findTextureWidgetsRecursively(QWidget *tlw, QWidget *widget, if (wd->renderToTexture) { QPlatformTextureList::Flags flags = wd->textureListFlags(); const QRect rect(widget->mapTo(tlw, QPoint()), widget->size()); - widgetTextures->appendTexture(widget, wd->textureId(), rect, wd->clipRect(), flags); + widgetTextures->appendTexture(widget, wd->texture(), rect, wd->clipRect(), flags); } for (int i = 0; i < wd->children.size(); ++i) { @@ -624,33 +619,12 @@ static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget) } } - if (QWidgetPrivate::get(widget)->textureChildSeen) { - // No render-to-texture widgets in the (sub-)tree due to hidden or native - // children. Returning null results in using the normal backingstore flush path - // without OpenGL-based compositing. This is very desirable normally. However, - // some platforms cannot handle switching between the non-GL and GL paths for - // their windows so it has to be opt-in. - static bool switchableWidgetComposition = - QGuiApplicationPrivate::instance()->platformIntegration() - ->hasCapability(QPlatformIntegration::SwitchableWidgetComposition); - if (!switchableWidgetComposition) - return qt_dummy_platformTextureList(); - } + if (QWidgetPrivate::get(widget)->textureChildSeen) + return qt_dummy_platformTextureList(); return nullptr; } -#else - -static QPlatformTextureList *widgetTexturesFor(QWidget *tlw, QWidget *widget) -{ - Q_UNUSED(tlw); - Q_UNUSED(widget); - return nullptr; -} - -#endif // QT_NO_OPENGL - // --------------------------------------------------------------------------- /*! @@ -725,7 +699,6 @@ bool QWidgetPrivate::shouldDiscardSyncRequest() const bool QWidgetRepaintManager::syncAllowed() { -#ifndef QT_NO_OPENGL QTLWExtra *tlwExtra = tlw->d_func()->maybeTopData(); if (textureListWatcher && !textureListWatcher->isLocked()) { textureListWatcher->deleteLater(); @@ -744,7 +717,6 @@ bool QWidgetRepaintManager::syncAllowed() if (skipSync) // cannot compose due to widget textures being in use return false; } -#endif return true; } @@ -857,7 +829,6 @@ void QWidgetRepaintManager::paintAndFlush() } dirtyWidgets.clear(); -#ifndef QT_NO_OPENGL // Find all render-to-texture child widgets (including self). // The search is cut at native widget boundaries, meaning that each native child widget // has its own list for the subtree below it. @@ -865,13 +836,12 @@ void QWidgetRepaintManager::paintAndFlush() tlwExtra->widgetTextures.clear(); findAllTextureWidgetsRecursively(tlw, tlw); qt_window_private(tlw->windowHandle())->compositing = false; // will get updated in flush() -#endif if (toClean.isEmpty()) { // 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 + // texture content changes. Check if we have such widgets in the special // dirty list. QVarLengthArray<QWidget *, 16> paintPending; const int numPaintPending = dirtyRenderToTextureWidgets.count(); @@ -903,7 +873,6 @@ void QWidgetRepaintManager::paintAndFlush() return; } -#ifndef QT_NO_OPENGL for (const auto &tl : tlwExtra->widgetTextures) { for (int i = 0; i < tl->count(); ++i) { QWidget *w = static_cast<QWidget *>(tl->source(i)); @@ -920,7 +889,6 @@ void QWidgetRepaintManager::paintAndFlush() for (int i = 0; i < dirtyRenderToTextureWidgets.count(); ++i) resetWidget(dirtyRenderToTextureWidgets.at(i)); dirtyRenderToTextureWidgets.clear(); -#endif #if QT_CONFIG(graphicsview) if (tlw->d_func()->extra->proxyWidget) { @@ -1046,12 +1014,10 @@ void QWidgetRepaintManager::flush() // Render-to-texture widgets are not in topLevelNeedsFlush so flush if we have not done it above. if (!flushed && !hasNeedsFlushWidgets) { -#ifndef QT_NO_OPENGL if (!tlw->d_func()->topData()->widgetTextures.empty()) { if (QPlatformTextureList *widgetTextures = widgetTexturesFor(tlw, tlw)) flush(tlw, QRegion(), widgetTextures); } -#endif } if (!hasNeedsFlushWidgets) @@ -1073,12 +1039,7 @@ void QWidgetRepaintManager::flush() */ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion ®ion, QPlatformTextureList *widgetTextures) { -#ifdef QT_NO_OPENGL - Q_UNUSED(widgetTextures); - Q_ASSERT(!region.isEmpty()); -#else Q_ASSERT(!region.isEmpty() || widgetTextures); -#endif Q_ASSERT(widget); Q_ASSERT(tlw); @@ -1091,8 +1052,6 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion ®ion, QPlatf return; } - qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget; - static bool fpsDebug = qEnvironmentVariableIntValue("QT_DEBUG_FPS"); if (fpsDebug) { if (!perfFrames++) @@ -1108,40 +1067,37 @@ void QWidgetRepaintManager::flush(QWidget *widget, const QRegion ®ion, QPlatf if (widget != tlw) offset += widget->mapTo(tlw, QPoint()); - QRegion effectiveRegion = region; -#ifndef QT_NO_OPENGL - const bool compositionWasActive = widget->d_func()->renderToTextureComposeActive; - if (!widgetTextures) { - widget->d_func()->renderToTextureComposeActive = false; - // Detect the case of falling back to the normal flush path when no - // render-to-texture widgets are visible anymore. We will force one - // last flush to go through the OpenGL-based composition to prevent - // artifacts. The next flush after this one will use the normal path. - if (compositionWasActive) + if (widget->d_func()->usesRhiFlush) { + QRhi *rhi = store->handle()->rhi(); + qCDebug(lcWidgetPainting) << "Flushing" << region << "of" << widget + << "with QRhi" << rhi + << "to window" << widget->windowHandle(); + if (!widgetTextures) widgetTextures = qt_dummy_platformTextureList; - } else { - widget->d_func()->renderToTextureComposeActive = true; - } - // When changing the composition status, make sure the dirty region covers - // the entire widget. Just having e.g. the shown/hidden render-to-texture - // widget's area marked as dirty is incorrect when changing flush paths. - if (compositionWasActive != widget->d_func()->renderToTextureComposeActive) - effectiveRegion = widget->rect(); - - // re-test since we may have been forced to this path via the dummy texture list above - if (widgetTextures) { + qt_window_private(tlw->windowHandle())->compositing = true; - widget->window()->d_func()->sendComposeStatus(widget->window(), false); + QWidgetPrivate *widgetWindowPrivate = widget->window()->d_func(); + widgetWindowPrivate->sendComposeStatus(widget->window(), false); // A window may have alpha even when the app did not request // WA_TranslucentBackground. Therefore the compositor needs to know whether the app intends // to rely on translucency, in order to decide if it should clear to transparent or opaque. const bool translucentBackground = widget->testAttribute(Qt::WA_TranslucentBackground); - store->handle()->composeAndFlush(widget->windowHandle(), effectiveRegion, offset, - widgetTextures, translucentBackground); - widget->window()->d_func()->sendComposeStatus(widget->window(), true); - } else -#endif - store->flush(effectiveRegion, widget->windowHandle(), offset); + + QPlatformBackingStore::FlushResult flushResult; + flushResult = store->handle()->rhiFlush(widget->windowHandle(), + region, + offset, + widgetTextures, + translucentBackground); + widgetWindowPrivate->sendComposeStatus(widget->window(), true); + if (flushResult == QPlatformBackingStore::FlushFailedDueToLostDevice) { + store->handle()->graphicsDeviceReportedLost(); + widget->update(); + } + } else { + qCInfo(lcWidgetPainting) << "Flushing" << region << "of" << widget; + store->flush(region, widget->windowHandle(), offset); + } } // --------------------------------------------------------------------------- @@ -1353,6 +1309,11 @@ void QWidgetPrivate::invalidateBackingStore_resizeHelper(const QPoint &oldPos, c } } +QRhi *QWidgetRepaintManager::rhi() const +{ + return store->handle()->rhi(); +} + QT_END_NAMESPACE #include "qwidgetrepaintmanager.moc" |