From 6efca58ab943bbd8c91c8f12ad47484677e2cf60 Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Thu, 8 Dec 2011 13:24:17 +0100 Subject: [PATCH] Implement "headless mode" for hidden QQuickCanvases When all views are hidden, we stop the rendering thread, kill the OpenGL context and all scene graph content. The entire scenegraph is recreated based on the QML scene when a view is shown again. Change-Id: I734619d9f29263a5cdecbcc9b88c3808d1d64a7f Reviewed-by: Kim M. Kalland --- src/quick/items/qquickcanvas.cpp | 51 +++++++++++++- src/quick/items/qquickwindowmanager.cpp | 18 +++-- src/quick/scenegraph/qsgcontext.cpp | 7 +-- tests/auto/qtquick2/qquickcanvas/data/Headless.qml | 33 +++++++++ tests/auto/qtquick2/qquickcanvas/data/colors.png | Bin 0 -> 1655 bytes tests/auto/qtquick2/qquickcanvas/qquickcanvas.pro | 8 ++- .../qtquick2/qquickcanvas/tst_qquickcanvas.cpp | 73 ++++++++++++++++++++ 7 files changed, 172 insertions(+), 18 deletions(-) create mode 100644 tests/auto/qtquick2/qquickcanvas/data/Headless.qml create mode 100644 tests/auto/qtquick2/qquickcanvas/data/colors.png diff --git a/src/quick/items/qquickcanvas.cpp b/src/quick/items/qquickcanvas.cpp index 596db6c..0f281a3 100644 --- a/src/quick/items/qquickcanvas.cpp +++ b/src/quick/items/qquickcanvas.cpp @@ -199,10 +199,22 @@ void QQuickCanvasPrivate::polishItems() updateFocusItemTransform(); } +void forceUpdate(QQuickItem *item) +{ + if (item->flags() & QQuickItem::ItemHasContents) + item->update(); + QQuickItemPrivate::get(item)->dirty(QQuickItemPrivate::ChildrenUpdateMask); + + QList items = item->childItems(); + for (int i=0; iappendChildNode(QQuickItemPrivate::get(rootItem)->itemNode()); renderer = context->createRenderer(); @@ -273,9 +285,9 @@ void QQuickCanvasPrivate::init(QQuickCanvas *c) q->setSurfaceType(QWindow::OpenGLSurface); q->setFormat(context->defaultSurfaceFormat()); - QObject::connect(context, SIGNAL(initialized()), q, SIGNAL(sceneGraphInitialized())); - QObject::connect(context, SIGNAL(invalidated()), q, SIGNAL(sceneGraphInvalidated())); - QObject::connect(context, SIGNAL(invalidated()), q, SLOT(cleanupSceneGraph())); + QObject::connect(context, SIGNAL(initialized()), q, SIGNAL(sceneGraphInitialized()), Qt::DirectConnection); + QObject::connect(context, SIGNAL(invalidated()), q, SIGNAL(sceneGraphInvalidated()), Qt::DirectConnection); + QObject::connect(context, SIGNAL(invalidated()), q, SLOT(cleanupSceneGraph()), Qt::DirectConnection); // ### TODO: remove QSGEngine engine = new QSGEngine(); @@ -1686,11 +1698,42 @@ void QQuickCanvas::cleanupSceneGraph() } /*! - \fn void QSGEngine::sceneGraphInitialized(); + Returns the opengl context used for rendering. + + If the scene graph is not ready, this function will return 0. + + \sa sceneGraphInitialized(), sceneGraphInvalidated() + */ + +QOpenGLContext *QQuickCanvas::openglContext() const +{ + Q_D(const QQuickCanvas); + if (d->context->isReady()) + return d->context->glContext(); + return 0; +} + + +/*! + \fn void QSGContext::sceneGraphInitialized() This signal is emitted when the scene graph has been initialized. This signal will be emitted from the scene graph rendering thread. + + */ + + +/*! + \fn void QSGContext::sceneGraphInvalidated() + + This signal is emitted when the scene graph has been invalidated. + + This signal implies that the opengl rendering context used + has been invalidated and all user resources tied to that context + should be released. + + This signal will be emitted from the scene graph rendering thread. */ /*! diff --git a/src/quick/items/qquickwindowmanager.cpp b/src/quick/items/qquickwindowmanager.cpp index 6c75294..ea17cae 100644 --- a/src/quick/items/qquickwindowmanager.cpp +++ b/src/quick/items/qquickwindowmanager.cpp @@ -452,7 +452,7 @@ void QQuickRenderThreadSingleContextWindowManager::handleRemovedWindows() while (m_removed_windows.size()) { QQuickCanvas *canvas = m_removed_windows.takeLast(); #ifdef THREAD_DEBUG - printf(" RenderThread: removing %p\n", canvas); + printf(" RenderThread: removing %p\n", canvas); #endif QQuickCanvasPrivate::get(canvas)->cleanupNodesOnShutdown(); @@ -527,8 +527,13 @@ void QQuickRenderThreadSingleContextWindowManager::run() printf("QML Rendering Thread Started\n"); #endif - if (!gl) - initialize(); + lock(); + Q_ASSERT(!gl); + initialize(); + // Wake GUI as it is waiting for the GL context to have appeared, as + // an indication that the render thread is now running. + wake(); + unlock(); while (!shouldExit) { lock(); @@ -895,10 +900,6 @@ void QQuickRenderThreadSingleContextWindowManager::paint(QQuickCanvas *canvas) printf("GUI: paint called: %p\n", canvas); #endif - return; - - - lockInGui(); exhaustSyncEvent(); @@ -963,7 +964,10 @@ void QQuickRenderThreadSingleContextWindowManager::startRendering() renderThreadAwakened = false; inSync = false; + lockInGui(); start(); // Start the render thread... + wait(); + unlockInGui(); // Animations will now be driven from the rendering thread. if (animationTimer >= 0) { diff --git a/src/quick/scenegraph/qsgcontext.cpp b/src/quick/scenegraph/qsgcontext.cpp index a81571e..5af7c76 100644 --- a/src/quick/scenegraph/qsgcontext.cpp +++ b/src/quick/scenegraph/qsgcontext.cpp @@ -87,9 +87,7 @@ class QSGContextPrivate : public QObjectPrivate { public: QSGContextPrivate() - : rootNode(0) - , renderer(0) - , gl(0) + : gl(0) , distanceFieldCacheManager(0) , flashMode(qmlFlashMode()) , distanceFieldDisabled(qmlDisableDistanceField()) @@ -101,9 +99,6 @@ public: { } - QSGRootNode *rootNode; - QSGRenderer *renderer; - QOpenGLContext *gl; QHash materials; diff --git a/tests/auto/qtquick2/qquickcanvas/data/Headless.qml b/tests/auto/qtquick2/qquickcanvas/data/Headless.qml new file mode 100644 index 0000000..2e09cb1 --- /dev/null +++ b/tests/auto/qtquick2/qquickcanvas/data/Headless.qml @@ -0,0 +1,33 @@ +import QtQuick 2.0 +import QtQuick.Window 2.0 as Window + +Window.Window { + + width: 300 + height: 200 + visible: true + + Text { + anchors.left: parent.left + anchors.top: parent.top + text: "Testing headless mode" + } + + Rectangle { + anchors.centerIn: parent + width: 100 + height: 50 + rotation: -30 + gradient: Gradient { + GradientStop { position: 0; color: "lightsteelblue" } + GradientStop { position: 1; color: "black" } + } + } + + Image { + source: "colors.png" + anchors.bottom: parent.bottom + anchors.right: parent.right + } + +} diff --git a/tests/auto/qtquick2/qquickcanvas/data/colors.png b/tests/auto/qtquick2/qquickcanvas/data/colors.png new file mode 100644 index 0000000000000000000000000000000000000000..dfb62f3d64e95a26d2ea1e87065e7892fa7814a3 GIT binary patch literal 1655 zcmZuydpy%?7@t&fDVMV4vO+|Cv_2vZwqhn*<$7YdtQ>7ka+_twI&or-NX?}XhV)6t ztj^hlwL>Q*TWE2T^RtzEPPJ*_%yEj&@8{Hi=ktEv`}cXC@AJIx`@9!|0{qtNnd`w| zu=NByE(GkEP+hkcY~9JO17N2`5AnmoZa=kr15Vb)9P-1#K0~Fpy`cnL(M`sO(?Rhe zsA^#yDj@|rX9)hjI>0(TZFj4poa0g$Y|R1z=Y51Ztg3FOStJ(CX?O z*^xN(xT@hJ^8JS?pX5l)h>I6-iF!=pDdjCY2e#x@jscOiOHG6=v+ z1_elx?C9izwhAsrD}ViBGr1|=*iA09_ja;Ae)VF}u5lYQ%f`iR@i1Z+?PLhafFYgw6Md*mZu7Q^VwF^Q^pS_-F`agpn);VIn7Qv%*NH8Vo%RZ+n?|@w7)v?{_q3jqY_bNQl?#0 z>op#?a_SwUU8E`xBpRH))4x!v-=vp>LkJtx@4}6vPH&&v9%o$i;2YDzxk!V{gfv{b;Kabo z(!3tE(u>*a@%VHZQg~?`7u8JFc~JjmAf-bYdylz^R>VcZE|)qFzY%PkstSGNo30@< zZ@j`^E<^s*`osp+DYWm@PJv~HGrTtR`zqpP+|`D$;<09BR*L~naahKM86HfX;wx*9 z;Bg8^8R?4bl^Zc+pR@D31<`!^^6bOjUij7=`CGt(`})c+deJQD{m)H&;g!^ujOC^2 zw<0rcyVkv}2PN5)V>SAIE%}vGfJI#?X9T}p8b))*uvTp{pCw46ft`sh)WGvtY=?uH zZDf}(Q-h0*lhoVU_k_`cxfru+CbMKIK@b{sgR0|IiRJDR1-IlEXp~leHgPKLWTpJ^ zJ!nrOj~$aP(v1{eo1<()?j=2U(kvd>35Bng>XS?8u!`)Wuox!u4wk6JoOn~>m`J{y z)_s0U06KH=yG_y`g{lSKs+sX2qA(8ef-EbP1zB!==1k>OabZCL1Pc@F>P(gXY ztWv6e9?=~Ogco;3w9BIDtp6$!`jb(olA9o`FKeFWu^Dr_#<34|I*V-oM`n-zEcufWg4?D~@$ zvA)~L^0f+Jw&ud~(d<($h@}Z*+w`i4Df7#M7;3NR>gCJJsCY{NmX8mg;=*Sq1;o zAKaI{8rI|5+|%1ngZlMk0Xg0M?7I+iNb45r9b+6w5=ubo&8cxVUrBV|@U(&KQJ4%X z^hxBz1&BV#d9?q`sT~jqFWZpi_r#13X}XSP?Lmhm-B@vV4A?lP<(`EAJ^T9Hy&->~ x$B1?qG+l|*ELu!z8*v-RAbd22fMK%Fuxz4#;h6n&F!(FL2)+Th+t}#y{{lV;B)findChild("item"); QVERIFY(item); QCOMPARE(item->canvas(), canvas); + + delete canvas; } void tst_qquickcanvas::clearColor() @@ -616,6 +620,75 @@ void tst_qquickcanvas::animationsWhileHidden() // Running animaiton should cause it to become visible again shortly. QTRY_VERIFY(canvas->visible()); + + delete canvas; +} + + +class SceneGraphListener : public QObject +{ + Q_OBJECT + +public: + SceneGraphListener() + : wasInitialized(false) + , wasInvalidated(false) + { + } + + bool wasInitialized; + bool wasInvalidated; + +public slots: + void initialized() { wasInitialized = true; } + void invalidated() { wasInvalidated = true; } +}; + +void tst_qquickcanvas::headless() +{ + QDeclarativeEngine engine; + QDeclarativeComponent component(&engine); + component.loadUrl(TESTDATA("Headless.qml")); + QObject* created = component.create(); + + QQuickCanvas* canvas = qobject_cast(created); + QVERIFY(canvas); + + QTest::qWaitForWindowShown(canvas); + QVERIFY(canvas->visible()); + + SceneGraphListener listener; + connect(canvas, SIGNAL(sceneGraphInitialized()), &listener, SLOT(initialized()), Qt::DirectConnection); + connect(canvas, SIGNAL(sceneGraphInvalidated()), &listener, SLOT(invalidated()), Qt::DirectConnection); + + // Verify that the canvas is alive and kicking + QVERIFY(canvas->openglContext() != 0); + + // Store the visual result + QImage originalContent = canvas->grabFrameBuffer(); + + // Hide the canvas and verify signal emittion and GL context deletion + canvas->hide(); + QVERIFY(listener.wasInvalidated); + QVERIFY(canvas->openglContext() == 0); + + // Destroy the native windowing system buffers + canvas->destroy(); + QVERIFY(canvas->handle() == 0); + + // Show and verify that we are back and running + canvas->show(); + QTest::qWaitForWindowShown(canvas); + + QVERIFY(listener.wasInitialized); + QVERIFY(canvas->openglContext() != 0); + + // Verify that the visual output is the same + QImage newContent = canvas->grabFrameBuffer(); + + QCOMPARE(originalContent, newContent); + + } QTEST_MAIN(tst_qquickcanvas) -- 1.7.2.5