diff options
author | Laszlo Agocs <[email protected]> | 2020-04-30 17:37:27 +0200 |
---|---|---|
committer | Eskil Abrahamsen Blomfeldt <[email protected]> | 2020-05-27 10:45:45 +0200 |
commit | 0d80dbd8c2cfc2a7d2a4d970b7acfc7fb5fb97a0 (patch) | |
tree | a6f487a8604e6212d27184b69debac87e0f3aca8 | |
parent | 6d3aae3cf47fbf21bd27eda7a249c2a23308156e (diff) |
Enable QQuickWidget with OpenGL over RHI
Also adapts to new NativeTexture format, where the actual
handle is passed around instead of a pointer to it.
[ChangeLog][QQuickWidget][Important Behavioral Changes] In
earlier versions, the returned value from QQuickWidget::quickWindow()
would persist for the life time of the widget. This is no
longer the case, so if you are connecting to its signals, make
sure you also connect to its destroyed() signal and update the
connections when it is destroyed.
Fixes: QTBUG-78638
Change-Id: I33cee8543ef1ff5d31555ed3ac18ba78c9c45102
Reviewed-by: Qt CI Bot <[email protected]>
Reviewed-by: Laszlo Agocs <[email protected]>
-rw-r--r-- | examples/quick/quickwidgets/qquickviewcomparison/main.cpp | 3 | ||||
-rw-r--r-- | examples/quick/quickwidgets/qquickviewcomparison/mainwindow.cpp | 2 | ||||
-rw-r--r-- | examples/quick/quickwidgets/quickwidget/main.cpp | 3 | ||||
-rw-r--r-- | src/quickwidgets/qquickwidget.cpp | 120 | ||||
-rw-r--r-- | src/quickwidgets/qquickwidget_p.h | 1 | ||||
-rw-r--r-- | tests/auto/.prev_CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/auto/CMakeLists.txt | 3 | ||||
-rw-r--r-- | tests/auto/auto.pro | 4 | ||||
-rw-r--r-- | tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp | 9 |
9 files changed, 102 insertions, 46 deletions
diff --git a/examples/quick/quickwidgets/qquickviewcomparison/main.cpp b/examples/quick/quickwidgets/qquickviewcomparison/main.cpp index 73db97eddb..f1fe397462 100644 --- a/examples/quick/quickwidgets/qquickviewcomparison/main.cpp +++ b/examples/quick/quickwidgets/qquickviewcomparison/main.cpp @@ -60,6 +60,9 @@ int main(int argc, char **argv) QApplication app(argc, argv); + // this example and QQuickWidget are only functional when rendering with OpenGL + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::OpenGLRhi); + QCoreApplication::setApplicationName("Qt QQuickView/QQuickWidget Comparison Example"); QCoreApplication::setOrganizationName("QtProject"); QCoreApplication::setApplicationVersion(QT_VERSION_STR); diff --git a/examples/quick/quickwidgets/qquickviewcomparison/mainwindow.cpp b/examples/quick/quickwidgets/qquickviewcomparison/mainwindow.cpp index 850ecd147b..2e45bc2df6 100644 --- a/examples/quick/quickwidgets/qquickviewcomparison/mainwindow.cpp +++ b/examples/quick/quickwidgets/qquickviewcomparison/mainwindow.cpp @@ -179,8 +179,8 @@ void MainWindow::updateView() connect(quickWidget, &QQuickWidget::statusChanged, this, &MainWindow::onStatusChangedWidget); connect(quickWidget, &QQuickWidget::sceneGraphError, this, &MainWindow::onSceneGraphError); quickWidget->setSource(source); - m_currentRootObject = quickWidget->rootObject(); switchTo(quickWidget); + m_currentRootObject = quickWidget->rootObject(); } if (m_currentRootObject) { diff --git a/examples/quick/quickwidgets/quickwidget/main.cpp b/examples/quick/quickwidgets/quickwidget/main.cpp index f28b2f1443..3bff5901f9 100644 --- a/examples/quick/quickwidgets/quickwidget/main.cpp +++ b/examples/quick/quickwidgets/quickwidget/main.cpp @@ -197,6 +197,9 @@ int main(int argc, char **argv) { QApplication app(argc, argv); + // this example and QQuickWidget are only functional when rendering with OpenGL + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::OpenGLRhi); + QCoreApplication::setApplicationName("Qt QQuickWidget Example"); QCoreApplication::setOrganizationName("QtProject"); QCoreApplication::setApplicationVersion(QT_VERSION_STR); diff --git a/src/quickwidgets/qquickwidget.cpp b/src/quickwidgets/qquickwidget.cpp index f3d26e4a73..221bdfaee6 100644 --- a/src/quickwidgets/qquickwidget.cpp +++ b/src/quickwidgets/qquickwidget.cpp @@ -44,6 +44,7 @@ #include "private/qquickitem_p.h" #include "private/qquickitemchangelistener_p.h" #include "private/qquickrendercontrol_p.h" +#include "private/qsgrhisupport_p.h" #include "private/qsgsoftwarerenderer_p.h" @@ -75,6 +76,9 @@ # include <QtCore/qt_windows.h> #endif +#include <QtQuick/qquickgraphicsdevice.h> +#include <QtQuick/qquickrendertarget.h> + QT_BEGIN_NAMESPACE // override setVisble to prevent accidental offscreen window being created @@ -102,16 +106,25 @@ private: QQuickWidget *m_quickWidget; }; -void QQuickWidgetPrivate::init(QQmlEngine* e) +void QQuickWidgetPrivate::initOffscreenWindow() { Q_Q(QQuickWidget); - - renderControl = new QQuickWidgetRenderControl(q); - offscreenWindow = new QQuickWindow(*new QQuickOffcreenWindowPrivate(),renderControl); + offscreenWindow = new QQuickWindow(*new QQuickOffcreenWindowPrivate(), renderControl); offscreenWindow->setTitle(QString::fromLatin1("Offscreen")); offscreenWindow->setObjectName(QString::fromLatin1("QQuickOffScreenWindow")); // Do not call create() on offscreenWindow. + QWidget::connect(offscreenWindow, SIGNAL(sceneGraphInitialized()), q, SLOT(createFramebufferObject())); + QWidget::connect(offscreenWindow, SIGNAL(sceneGraphInvalidated()), q, SLOT(destroyFramebufferObject())); +} + +void QQuickWidgetPrivate::init(QQmlEngine* e) +{ + Q_Q(QQuickWidget); + + renderControl = new QQuickWidgetRenderControl(q); + initOffscreenWindow(); + // Check if the Software Adaptation is being used auto sgRendererInterface = offscreenWindow->rendererInterface(); if (sgRendererInterface && sgRendererInterface->graphicsApi() == QSGRendererInterface::Software) @@ -126,6 +139,9 @@ void QQuickWidgetPrivate::init(QQmlEngine* e) qWarning("QQuickWidget is not supported on this platform."); } + if (QSGRhiSupport::instance()->rhiBackend() != QRhi::OpenGLES2) + qWarning("QQuickWidget is only supported on OpenGL. Use QQuickWindow::setSceneGraphBackend() to override the default."); + engine = e; if (!engine.isNull() && !engine.data()->incubationController()) @@ -135,8 +151,6 @@ void QQuickWidgetPrivate::init(QQmlEngine* e) q->setAcceptDrops(true); #endif - QWidget::connect(offscreenWindow, SIGNAL(sceneGraphInitialized()), q, SLOT(createFramebufferObject())); - QWidget::connect(offscreenWindow, SIGNAL(sceneGraphInvalidated()), q, SLOT(destroyFramebufferObject())); QObject::connect(renderControl, SIGNAL(renderRequested()), q, SLOT(triggerUpdate())); QObject::connect(renderControl, SIGNAL(sceneChanged()), q, SLOT(triggerUpdate())); } @@ -181,18 +195,28 @@ void QQuickWidgetPrivate::invalidateRenderControl() void QQuickWidgetPrivate::handleWindowChange() { + Q_Q(QQuickWidget); + 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. + // invalidate the scenegraph and destroy the context. QQuickRenderControl + // must be recreated because its RHI will contain a dangling pointer to + // the context. - invalidateRenderControl(); + delete offscreenWindow; + offscreenWindow = nullptr; + delete renderControl; + renderControl = new QQuickWidgetRenderControl(q); + initOffscreenWindow(); + + QObject::connect(renderControl, SIGNAL(renderRequested()), q, SLOT(triggerUpdate())); + QObject::connect(renderControl, SIGNAL(sceneChanged()), q, SLOT(triggerUpdate())); + + execute(); if (!useSoftwareRenderer) destroyContext(); } @@ -230,10 +254,10 @@ QQuickWidgetPrivate::~QQuickWidgetPrivate() #if QT_CONFIG(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 - delete offscreenWindow; delete resolvedFbo; delete fbo; + delete offscreenWindow; + delete renderControl; destroyContext(); #endif @@ -302,6 +326,8 @@ void QQuickWidgetPrivate::render(bool needsSync) QOpenGLContextPrivate::get(context)->defaultFboRedirect = fbo->handle(); + renderControl->beginFrame(); + if (needsSync) { renderControl->polishItems(); renderControl->sync(); @@ -309,6 +335,9 @@ void QQuickWidgetPrivate::render(bool needsSync) renderControl->render(); + renderControl->endFrame(); + + context->makeCurrent(offscreenSurface); if (resolvedFbo) { QRect rect(QPoint(0, 0), fbo->size()); QOpenGLFramebufferObject::blitFramebuffer(resolvedFbo, rect, fbo, rect); @@ -349,7 +378,6 @@ void QQuickWidgetPrivate::renderSceneGraph() return; if (!useSoftwareRenderer) { - QOpenGLContext *context = offscreenWindow->openglContext(); if (!context) { qWarning("QQuickWidget: Attempted to render scene with no context"); return; @@ -383,7 +411,13 @@ QImage QQuickWidgetPrivate::grabFramebuffer() context->makeCurrent(offscreenSurface); #endif } - return offscreenWindow->grabWindow(); + + + // grabWindow() does not work for the OpenGL + render control case, so we + // prefer the FBO's toImage() if available. When the software renderer + // is in use, however, there will be no FBO and we fall back to grabWindow() + // instead. + return fbo != nullptr ? fbo->toImage() : offscreenWindow->grabWindow(); } // Intentionally not overriding the QQuickWindow's focusObject. @@ -894,7 +928,7 @@ void QQuickWidgetPrivate::createContext() // 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(); + const bool reinit = context && !offscreenWindow->isSceneGraphInitialized(); if (!reinit) { if (context) @@ -929,8 +963,10 @@ void QQuickWidgetPrivate::createContext() } if (context->makeCurrent(offscreenSurface)) { - if (!offscreenWindow->openglContext()) - renderControl->initialize(context); + if (!offscreenWindow->isSceneGraphInitialized()) { + offscreenWindow->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(context)); + renderControl->initialize(); + } } else #endif qWarning("QQuickWidget: Failed to make context current"); @@ -971,30 +1007,28 @@ void QQuickWidget::createFramebufferObject() } #if QT_CONFIG(opengl) - QOpenGLContext *context = d->offscreenWindow->openglContext(); - - if (!context) { + if (!d->context) { qWarning("QQuickWidget: Attempted to create FBO with no context"); return; } QOpenGLContext *shareWindowContext = QWidgetPrivate::get(window())->shareContext(); - if (shareWindowContext && context->shareContext() != shareWindowContext && !qGuiApp->testAttribute(Qt::AA_ShareOpenGLContexts)) { - context->setShareContext(shareWindowContext); - context->setScreen(shareWindowContext->screen()); - if (!context->create()) + if (shareWindowContext && d->context->shareContext() != shareWindowContext && !qGuiApp->testAttribute(Qt::AA_ShareOpenGLContexts)) { + d->context->setShareContext(shareWindowContext); + d->context->setScreen(shareWindowContext->screen()); + if (!d->context->create()) qWarning("QQuickWidget: Failed to recreate context"); // The screen may be different so we must recreate the offscreen surface too. // Unlike QOpenGLContext, QOffscreenSurface's create() does not recreate so have to destroy() first. d->offscreenSurface->destroy(); - d->offscreenSurface->setScreen(context->screen()); + d->offscreenSurface->setScreen(d->context->screen()); d->offscreenSurface->create(); } - context->makeCurrent(d->offscreenSurface); + d->context->makeCurrent(d->offscreenSurface); int samples = d->requestedSamples; - if (!QOpenGLExtensions(context).hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) + if (!QOpenGLExtensions(d->context).hasOpenGLExtension(QOpenGLExtensions::FramebufferMultisample)) samples = 0; QOpenGLFramebufferObjectFormat format; @@ -1004,11 +1038,12 @@ void QQuickWidget::createFramebufferObject() // The default framebuffer for normal windows have sRGB support on OS X which leads to the Qt Quick text item // utilizing sRGB blending. To get identical results with QQuickWidget we have to have our framebuffer backed // by an sRGB texture. -#ifdef Q_OS_OSX - if (context->hasExtension("GL_ARB_framebuffer_sRGB") - && context->hasExtension("GL_EXT_texture_sRGB") - && context->hasExtension("GL_EXT_texture_sRGB_decode")) +#ifdef Q_OS_MACOS + if (d->context->hasExtension("GL_ARB_framebuffer_sRGB") + && d->context->hasExtension("GL_EXT_texture_sRGB") + && d->context->hasExtension("GL_EXT_texture_sRGB_decode")) { format.setInternalTextureFormat(GL_SRGB8_ALPHA8_EXT); + } #endif const QSize fboSize = size() * devicePixelRatioF(); @@ -1022,15 +1057,18 @@ void QQuickWidget::createFramebufferObject() // When compositing in the backingstore, sampling the sRGB texture would perform an // sRGB-linear conversion which is not what we want when the final framebuffer (the window's) // is sRGB too. Disable the conversion. -#ifdef Q_OS_OSX +#ifdef Q_OS_MACOS if (format.internalTextureFormat() == GL_SRGB8_ALPHA8_EXT) { - QOpenGLFunctions *funcs = context->functions(); + QOpenGLFunctions *funcs = d->context->functions(); funcs->glBindTexture(GL_TEXTURE_2D, d->fbo->texture()); funcs->glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_SRGB_DECODE_EXT, GL_SKIP_DECODE_EXT); } #endif - d->offscreenWindow->setRenderTarget(d->fbo); + GLuint textureId = d->fbo->texture(); + d->offscreenWindow->setRenderTarget( QQuickRenderTarget::fromNativeTexture({ textureId, 0 }, fboSize, samples)); + + d->renderControl->setSamples(samples); if (samples > 0) d->resolvedFbo = new QOpenGLFramebufferObject(fboSize); @@ -1247,7 +1285,7 @@ void QQuickWidget::resizeEvent(QResizeEvent *e) 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. - if (!d->fbo && !d->offscreenWindow->openglContext()) + if (!d->fbo && !d->offscreenWindow->isSceneGraphInitialized()) return; if (!d->fbo || d->fbo->size() != size() * devicePixelRatioF()) { needsSync = true; @@ -1260,8 +1298,7 @@ void QQuickWidget::resizeEvent(QResizeEvent *e) d->createContext(); } - QOpenGLContext *context = d->offscreenWindow->openglContext(); - if (!context) { + if (!d->context) { qWarning("QQuickWidget::resizeEvent() no OpenGL context"); return; } @@ -1351,7 +1388,7 @@ void QQuickWidget::showEvent(QShowEvent *) if (!d->useSoftwareRenderer) { d->createContext(); - if (d->offscreenWindow->openglContext()) { + if (d->offscreenWindow->isSceneGraphInitialized()) { shouldTriggerUpdate = false; d->render(true); // render() may have led to a QQuickWindow::update() call (for @@ -1707,6 +1744,11 @@ void QQuickWidget::setClearColor(const QColor &color) \warning Use the return value of this function with caution. In particular, do not ever attempt to show the QQuickWindow, and be very careful when using other QWindow-only APIs. + + \warning The offscreen window may be deleted (and recreated) during + the life time of the QQuickWidget, particularly when the widget is + moved to another QQuickWindow. If you need to know when the window + has been replaced, connect to its destroyed() signal. */ QQuickWindow *QQuickWidget::quickWindow() const { diff --git a/src/quickwidgets/qquickwidget_p.h b/src/quickwidgets/qquickwidget_p.h index 881f7f9220..0517a772e1 100644 --- a/src/quickwidgets/qquickwidget_p.h +++ b/src/quickwidgets/qquickwidget_p.h @@ -108,6 +108,7 @@ public: #endif void init(QQmlEngine* e = 0); + void initOffscreenWindow(); void ensureEngine() const; void handleWindowChange(); void invalidateRenderControl(); diff --git a/tests/auto/.prev_CMakeLists.txt b/tests/auto/.prev_CMakeLists.txt index 9ec371359b..c52566a954 100644 --- a/tests/auto/.prev_CMakeLists.txt +++ b/tests/auto/.prev_CMakeLists.txt @@ -13,3 +13,6 @@ endif() if(TARGET Qt::Gui AND (QT_FEATURE_opengl OR QT_FEATURE_opengles2 OR QT_FEATURE_opengles3)) add_subdirectory(particles) endif() +if(TARGET Qt::Gui AND TARGET Qt::Widgets AND (QT_FEATURE_opengl OR QT_FEATURE_opengles2 OR QT_FEATURE_opengles3)) + add_subdirectory(quickwidgets) +endif() diff --git a/tests/auto/CMakeLists.txt b/tests/auto/CMakeLists.txt index 222c735359..eb1c2d34df 100644 --- a/tests/auto/CMakeLists.txt +++ b/tests/auto/CMakeLists.txt @@ -15,3 +15,6 @@ endif() if(TARGET Qt::Gui AND (QT_FEATURE_opengl OR QT_FEATURE_opengles2 OR QT_FEATURE_opengles3)) add_subdirectory(particles) endif() +if(TARGET Qt::Gui AND TARGET Qt::Widgets AND (QT_FEATURE_opengl OR QT_FEATURE_opengles2 OR QT_FEATURE_opengles3)) + add_subdirectory(quickwidgets) +endif() diff --git a/tests/auto/auto.pro b/tests/auto/auto.pro index 2a22868390..d967b01d61 100644 --- a/tests/auto/auto.pro +++ b/tests/auto/auto.pro @@ -11,9 +11,7 @@ SUBDIRS=\ qtHaveModule(gui):qtConfig(opengl(es1|es2)?) { SUBDIRS += particles -# Disabled for Qt 6 until a conclusion on QQuickWidget is reached -# qtHaveModule(widgets): SUBDIRS += quickwidgets - + qtHaveModule(widgets): SUBDIRS += quickwidgets } # console applications not supported diff --git a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp index 8211400262..b47837179a 100644 --- a/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp +++ b/tests/auto/quickwidgets/qquickwidget/tst_qquickwidget.cpp @@ -152,6 +152,7 @@ private: tst_qquickwidget::tst_qquickwidget() { + QQuickWindow::setSceneGraphBackend(QSGRendererInterface::OpenGLRhi); } void tst_qquickwidget::showHide() @@ -379,8 +380,8 @@ void tst_qquickwidget::readback() QImage img = view->grabFramebuffer(); QVERIFY(!img.isNull()); - QCOMPARE(img.width(), view->width()); - QCOMPARE(img.height(), view->height()); + QCOMPARE(img.width(), qCeil(view->width() * view->devicePixelRatioF())); + QCOMPARE(img.height(), qCeil(view->height() * view->devicePixelRatioF())); QRgb pix = img.pixel(5, 5); QCOMPARE(pix, qRgb(255, 0, 0)); @@ -447,13 +448,15 @@ void tst_qquickwidget::reparentToNewWindow() window2.show(); QVERIFY(QTest::qWaitForWindowExposed(&window2)); - QSignalSpy afterRenderingSpy(qqw->quickWindow(), &QQuickWindow::afterRendering); qqw->setParent(&window2); + + QSignalSpy afterRenderingSpy(qqw->quickWindow(), &QQuickWindow::afterRendering); qqw->show(); QTRY_VERIFY(afterRenderingSpy.size() > 0); QImage img = qqw->grabFramebuffer(); + QCOMPARE(img.pixel(5, 5), qRgb(255, 0, 0)); } |