diff options
| author | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2024-02-20 13:54:47 +0100 |
|---|---|---|
| committer | Tor Arne Vestbø <tor.arne.vestbo@qt.io> | 2024-06-01 00:24:34 +0200 |
| commit | eb4cb719257d3b57cd801273d4011579d8c81714 (patch) | |
| tree | 4cb2128bd66b97c59ea63b0034229aba6cccba26 /src/widgets/kernel/qwidget.cpp | |
| parent | e4ef0f03e6f1fddc397980fd7fbf6f6b829f16d9 (diff) | |
widgets: Use per-surface-format RHI support and compositor
The RHI support and compositor in QPlatformBackingStore were
tied to the surface format of the top level window owning
the backing store.
This meant that inserting an RHI-enabled widget (QRhiWidget,
QOpenGLWidget, QQuickWidget, QWebEngineView) into the widget
hierarchy required recreating the top level window with a
matching surface format that could support the RHI composition.
It also meant that we could not have two RHI enabled widgets
with different surface format requirements (Metal and OpenGL
for example) in the same top level widget hierarchy.
The recreation of the window had various visual side effects,
such as temporarily switching out of full screen state, or the
widget rendering a frame of black, as well as more serious
problems such as not correctly restoring the window geometry.
In addition, if client code had pulled out the winId() of the
window, and did not invalidate these references on window
destruction via QEvent::WinIdChange or QEvent::PlatformSurface,
the client would reference stale window handles. Although
this is a programming error (QWidget::winId() specifically
mentions this requirement), we should avoid recreation if
we can.
We were already supporting flushing the backingstore to
individual native child widgets, but always did so via a
single RHI managed by the platform backingstore. By
expanding QPlatformBackingStore to keep one set of RHI
support and compositor per surface format, we can refine
the logic in QWidget and QWidgetRepaintManager to not
require recreating the top level. Native child widgets
are then flushed independently, including any RHI textures
and raster content that overlaps with the widget.
We still assume that a single RHI support and compositor
can be be used for multiple windows, as long as those
windows have the same surface format. In the future, if
needed, we can refine this to use one set per surface
format e.g.
Fixes: QTBUG-119221
Fixes: QTBUG-121181
Fixes: QTBUG-120096
Task-number: QTBUG-115652
Task-number: QTBUG-108344
Task-number: QTBUG-113557
Task-number: QTBUG-119309
Change-Id: I2635ed3d20c2fb76eab3b8130007dd656a0b93e5
Reviewed-by: Laszlo Agocs <laszlo.agocs@qt.io>
Diffstat (limited to 'src/widgets/kernel/qwidget.cpp')
| -rw-r--r-- | src/widgets/kernel/qwidget.cpp | 118 |
1 files changed, 73 insertions, 45 deletions
diff --git a/src/widgets/kernel/qwidget.cpp b/src/widgets/kernel/qwidget.cpp index 365323933b5..46e0d2c76c7 100644 --- a/src/widgets/kernel/qwidget.cpp +++ b/src/widgets/kernel/qwidget.cpp @@ -1029,10 +1029,13 @@ void QWidgetPrivate::createRecursively() QRhi *QWidgetPrivate::rhi() const { - if (QWidgetRepaintManager *repaintManager = maybeRepaintManager()) - return repaintManager->rhi(); - else + Q_Q(const QWidget); + if (auto *backingStore = q->backingStore()) { + auto *window = windowHandle(WindowHandleMode::Closest); + return backingStore->handle()->rhi(window); + } else { return nullptr; + } } /*! @@ -1112,8 +1115,15 @@ static bool q_evaluateRhiConfigRecursive(const QWidget *w, QPlatformBackingStore } for (const QObject *child : w->children()) { if (const QWidget *childWidget = qobject_cast<const QWidget *>(child)) { - if (q_evaluateRhiConfigRecursive(childWidget, outConfig, outType)) + if (q_evaluateRhiConfigRecursive(childWidget, outConfig, outType)) { + static bool optOut = qEnvironmentVariableIsSet("QT_WIDGETS_NO_CHILD_RHI"); + // Native child widgets should not trigger RHI for its parent + // hierarchy, but will still flush the native child using RHI. + if (!optOut && childWidget->testAttribute(Qt::WA_NativeWindow)) + continue; + return true; + } } } return false; @@ -1356,19 +1366,19 @@ void QWidgetPrivate::create() QBackingStore *store = q->backingStore(); usesRhiFlush = false; - if (!store) { - if (q->windowType() != Qt::Desktop) { - if (q->isWindow()) { - q->setBackingStore(new QBackingStore(win)); - QPlatformBackingStoreRhiConfig rhiConfig; - usesRhiFlush = q_evaluateRhiConfig(q, &rhiConfig, nullptr); - topData()->backingStore->handle()->setRhiConfig(rhiConfig); - } - } else { - q->setAttribute(Qt::WA_PaintOnScreen, true); + if (q->windowType() == Qt::Desktop) { + q->setAttribute(Qt::WA_PaintOnScreen, true); + } else { + if (!store && q->isWindow()) + q->setBackingStore(new QBackingStore(win)); + + QPlatformBackingStoreRhiConfig rhiConfig; + usesRhiFlush = q_evaluateRhiConfig(q, &rhiConfig, nullptr); + if (usesRhiFlush && q->backingStore()) { + // Trigger creation of support infrastructure up front, + // now that we have a specific RHI configuration. + q->backingStore()->handle()->createRhi(win, rhiConfig); } - } else if (win->handle()) { - usesRhiFlush = q_evaluateRhiConfig(q, nullptr, nullptr); } setWindowModified_helper(); @@ -10673,6 +10683,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) const bool wasCreated = testAttribute(Qt::WA_WState_Created); QWidget *oldtlw = window(); Q_ASSERT(oldtlw); + QWidget *oldParentWithWindow = d->closestParentWidgetWithWindowHandle(); if (f & Qt::Window) // Frame geometry likely changes, refresh. d->data.fstrut_dirty = true; @@ -10715,7 +10726,8 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // texture-based widgets need a pre-notification when their associated top-level window changes // This is not under the wasCreated/newParent conditions above in order to also play nice with QDockWidget. - if (oldtlw->d_func()->usesRhiFlush && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) + const bool oldParentUsesRhiFlush = oldParentWithWindow ? oldParentWithWindow->d_func()->usesRhiFlush : false; + if (oldParentUsesRhiFlush && ((!parent && parentWidget()) || (parent && parent->window() != oldtlw))) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowAboutToChangeInternal); // If we get parented into another window, children will be folded @@ -10796,7 +10808,7 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) // texture-based widgets need another event when their top-level window // changes (more precisely, has already changed at this point) - if (oldtlw->d_func()->usesRhiFlush && oldtlw != window()) + if (oldParentUsesRhiFlush && oldtlw != window()) qSendWindowChangeToTextureChildrenRecursively(this, QEvent::WindowChangeInternal); if (!wasCreated) { @@ -10824,33 +10836,47 @@ void QWidget::setParent(QWidget *parent, Qt::WindowFlags f) if (d->extra && d->extra->hasWindowContainer) QWindowContainer::parentWasChanged(this); - QWidget *newtlw = window(); - if (oldtlw != newtlw) { + QWidget *newParentWithWindow = d->closestParentWidgetWithWindowHandle(); + if (newParentWithWindow && newParentWithWindow != oldParentWithWindow) { + // Check if the native parent now needs to switch to RHI + qCDebug(lcWidgetPainting) << "Evaluating whether reparenting of" << this + << "into" << parent << "requires RHI enablement for" << newParentWithWindow; + + QPlatformBackingStoreRhiConfig rhiConfig; QSurface::SurfaceType surfaceType = QSurface::RasterSurface; - // Only evaluate the reparented subtree. While it might be tempting to - // do it on newtlw instead, the performance implications of that are + + // First evaluate whether the reparented widget uses RHI. + // We do this as a separate step because the performance + // implications of always checking the native parent are // problematic when it comes to large widget trees. - if (q_evaluateRhiConfig(this, nullptr, &surfaceType)) { - const bool wasUsingRhiFlush = newtlw->d_func()->usesRhiFlush; - newtlw->d_func()->usesRhiFlush = true; - bool recreate = false; - if (QWindow *w = newtlw->windowHandle()) { - if (w->surfaceType() != surfaceType || !wasUsingRhiFlush) - recreate = true; - } - // QTBUG-115652: Besides the toplevel the nativeParentWidget()'s QWindow must be checked as well. - if (QWindow *w = d->windowHandle(QWidgetPrivate::WindowHandleMode::Closest)) { - if (w->surfaceType() != surfaceType) - recreate = true; - } - if (recreate) { - const auto windowStateBeforeDestroy = newtlw->windowState(); - const auto visibilityBeforeDestroy = newtlw->isVisible(); - newtlw->destroy(); - newtlw->create(); - Q_ASSERT(newtlw->windowHandle()); - newtlw->windowHandle()->setWindowStates(windowStateBeforeDestroy); - QWidgetPrivate::get(newtlw)->setVisible(visibilityBeforeDestroy); + if (q_evaluateRhiConfig(this, &rhiConfig, &surfaceType)) { + // Then check whether the native parent requires RHI + // as a result. It may not, if this widget is a native + // window, and can handle its own RHI flushing. + if (q_evaluateRhiConfig(newParentWithWindow, nullptr, nullptr)) { + // Finally, check whether we need to recreate the + // native parent to enable RHI flushing. + auto *existingWindow = newParentWithWindow->windowHandle(); + auto existingSurfaceType = existingWindow->surfaceType(); + if (existingSurfaceType != surfaceType) { + qCDebug(lcWidgetPainting) + << "Recreating" << existingWindow + << "with current type" << existingSurfaceType + << "to support" << surfaceType; + const auto windowStateBeforeDestroy = newParentWithWindow->windowState(); + const auto visibilityBeforeDestroy = newParentWithWindow->isVisible(); + newParentWithWindow->destroy(); + newParentWithWindow->create(); + Q_ASSERT(newParentWithWindow->windowHandle()); + newParentWithWindow->windowHandle()->setWindowStates(windowStateBeforeDestroy); + QWidgetPrivate::get(newParentWithWindow)->setVisible(visibilityBeforeDestroy); + } else if (auto *backingStore = newParentWithWindow->backingStore()) { + // If we don't recreate we still need to make sure the native parent + // widget has a RHI config that the reparented widget can use. + backingStore->handle()->createRhi(existingWindow, rhiConfig); + // And that it knows it's now flushing with RHI + QWidgetPrivate::get(newParentWithWindow)->usesRhiFlush = true; + } } } } @@ -12308,8 +12334,10 @@ QBackingStore *QWidget::backingStore() const if (extra && extra->backingStore) return extra->backingStore; - QWidgetRepaintManager *repaintManager = d->maybeRepaintManager(); - return repaintManager ? repaintManager->backingStore() : nullptr; + if (!isWindow()) + return window()->backingStore(); + + return nullptr; } void QWidgetPrivate::getLayoutItemMargins(int *left, int *top, int *right, int *bottom) const |
