QQuickWidget: Clean up if RHI goes away under our feet

The QQuickWidget doesn't normally own the RHI; the QWidgetRepaintManager
does, via QBackingStoreRhiSupport. If the top level widget is destroyed,
so is its QBackingStore, and the corresponding RHI. But the QQuickWidget
may outlive its top level parent, in which case it needs to update its
cached reference to the RHI, and do proper cleanup before it goes away.

QRhiWidget already does the same thing, for the same use-case.

This was observed when recreating the top level QWidget via destroy/create
as part of the RHI widget compositor logic.

Fixes: QTBUG-119760
Pick-to: 6.7 6.6 6.5
Change-Id: Ic44449abcfe4271660a3ac4e132d0c4a71a21b65
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io>
This commit is contained in:
Tor Arne Vestbø 2023-12-19 21:52:05 +01:00
parent 71471cce34
commit 0d342e8312
2 changed files with 36 additions and 1 deletions

View File

@ -655,6 +655,9 @@ QQuickWidget::~QQuickWidget()
delete d->root;
d->root = nullptr;
if (d->rhi)
d->rhi->removeCleanupCallback(this);
// NB! resetting graphics resources must be done from this destructor,
// *not* from the private class' destructor. This is due to how destruction
// works and due to the QWidget dtor (for toplevels) destroying the repaint
@ -1021,8 +1024,19 @@ void QQuickWidgetPrivate::initializeWithRhi()
if (rhi)
return;
if (QWidgetRepaintManager *repaintManager = tlwd->maybeRepaintManager())
if (QWidgetRepaintManager *repaintManager = tlwd->maybeRepaintManager()) {
rhi = repaintManager->rhi();
if (rhi) {
// We don't own the RHI, so make sure we clean up if it goes away
rhi->addCleanupCallback(q, [this](QRhi *rhi) {
if (this->rhi == rhi) {
invalidateRenderControl();
deviceLost = true;
this->rhi = nullptr;
}
});
}
}
if (!rhi) {
// The widget (and its parent chain, if any) may not be shown at
@ -1675,6 +1689,8 @@ bool QQuickWidget::event(QEvent *e)
}
case QEvent::WindowAboutToChangeInternal:
if (d->rhi)
d->rhi->removeCleanupCallback(this);
d->invalidateRenderControl();
d->deviceLost = true;
d->rhi = nullptr;

View File

@ -138,6 +138,7 @@ private slots:
#endif
void focusPreserved();
void accessibilityHandlesViewChange();
void cleanupRhi();
private:
QPointingDevice *device = QTest::createTouchDevice();
@ -1027,6 +1028,24 @@ void tst_qquickwidget::accessibilityHandlesViewChange()
(void)iface->child(0);
}
class CreateDestroyWidget : public QWidget
{
public:
using QWidget::create;
using QWidget::destroy;
};
void tst_qquickwidget::cleanupRhi()
{
CreateDestroyWidget topLevel;
QQuickWidget quickWidget(&topLevel);
quickWidget.setSource(testFileUrl("rectangle.qml"));
topLevel.show();
QVERIFY(QTest::qWaitForWindowExposed(&topLevel));
topLevel.destroy();
topLevel.create();
}
QTEST_MAIN(tst_qquickwidget)