From 6b0af9fe9fd9114bd579510010c09519928f91ce Mon Sep 17 00:00:00 2001
From: Gunnar Sletta <gunnar.sletta@nokia.com>
Date: Mon, 6 Jun 2011 08:05:56 +0200
Subject: [PATCH] A better threaded renderer

---
 src/declarative/items/qsgcanvas.cpp | 681 +++++++++++++++++++---------
 src/declarative/items/qsgcanvas.h   |   4 +-
 src/declarative/items/qsgcanvas_p.h |  91 +++-
 3 files changed, 548 insertions(+), 228 deletions(-)

diff --git a/src/declarative/items/qsgcanvas.cpp b/src/declarative/items/qsgcanvas.cpp
index 16bd8ce44c..963c19f25d 100644
--- a/src/declarative/items/qsgcanvas.cpp
+++ b/src/declarative/items/qsgcanvas.cpp
@@ -64,6 +64,8 @@ QT_BEGIN_NAMESPACE
 DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_NO_THREADED_RENDERER)
 DEFINE_BOOL_CONFIG_OPTION(qmlFixedAnimationStep, QML_FIXED_ANIMATION_STEP)
 
+extern Q_OPENGL_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
+
 /*
 Focus behavior
 ==============
@@ -79,10 +81,65 @@ a scope focused item that takes precedence over the item being added.  Otherwise
 the focus of the added tree is used.  In the case of of a tree of items being 
 added to a canvas for the first time, which may have a conflicted focus state (two
 or more items in one scope having focus set), the same rule is applied item by item - 
-thus the first item that has focus will get it (assuming the scope doesn't already 
+thus the first item that has focus will get it (assuming the scope doesn't already
 have a scope focused item), and the other items will have their focus cleared.
 */
 
+/*
+  Threaded Rendering
+  ==================
+
+  The threaded rendering uses a number of different variables to track potential
+  states used to handle resizing, initial paint, grabbing and driving animations
+  while ALWAYS keeping the GL context in the rendering thread and keeping the
+  overhead of normal one-shot paints and vblank driven animations at a minimum.
+
+  Resize, initial show and grab suffer slightly in this model as they are locked
+  to the rendering in the rendering thread, but this is a necessary evil for
+  the system to work.
+
+  Variables that are used:
+
+  Private::animationRunning: This is true while the animations are running, and only
+  written to inside locks.
+
+  RenderThread::isGuiBlocked: This is used to indicate that the GUI thread owns the
+  lock. This variable is an integer to allow for recursive calls to lockInGui()
+  without using a recursive mutex. See isGuiBlockPending.
+
+  RenderThread::isPaintComplete: This variable is cleared when rendering starts and
+  set once rendering is complete. It is monitored in the paintEvent(),
+  resizeEvent() and grab() functions to force them to wait for rendering to
+  complete.
+
+  RenderThread::isGuiBlockPending: This variable is set in the render thread just
+  before the sync event is sent to the GUI thread. It is used to avoid deadlocks
+  in the case where render thread waits while waiting for GUI to pick up the sync
+  event and GUI thread gets a resizeEvent, the initial paintEvent or a grab.
+  When this happens, we use the
+  exhaustSyncEvent() function to do the sync right there and mark the coming
+  sync event to be discarded. There can only ever be one sync incoming.
+
+  RenderThread::isRenderBlock: This variable is true when animations are not
+  running and the render thread has gone to sleep, waiting for more to do.
+
+  RenderThread::isExternalUpdatePending: This variable is set to false during
+  the sync phase in the GUI thread and to true in maybeUpdate(). It is an
+  indication to the render thread that another render pass needs to take
+  place, rather than the render thread going to sleep after completing its swap.
+
+  RenderThread::doGrab: This variable is set by the grab() function and
+  tells the renderer to do a grab after rendering is complete and before
+  swapping happens.
+
+  RenderThread::shouldExit: This variable is used to determine if the render
+  thread should do a nother pass. It is typically set as a result of show()
+  and unset as a result of hide() or during shutdown()
+
+  RenderThread::hasExited: Used by the GUI thread to synchronize the shutdown
+  after shouldExit has been set to true.
+ */
+
 // #define FOCUS_DEBUG
 // #define MOUSE_DEBUG
 // #define TOUCH_DEBUG
@@ -106,41 +163,6 @@ QSGRootItem::QSGRootItem()
 {
 }
 
-void QSGCanvasPrivate::stopRenderingThread()
-{
-    if (thread->isRunning()) {
-        mutex.lock();
-        exitThread = true;
-        wait.wakeOne();
-        wait.wait(&mutex);
-        exitThread = false;
-        mutex.unlock();
-        thread->wait();
-    }
-}
-
-void QSGCanvasPrivate::_q_animationStarted()
-{
-#ifdef THREAD_DEBUG
-    qWarning("AnimationDriver: Main Thread: started");
-#endif
-    mutex.lock();
-    animationRunning = true;
-    if (idle)
-        wait.wakeOne();
-    mutex.unlock();
-}
-
-void QSGCanvasPrivate::_q_animationStopped()
-{
-#ifdef THREAD_DEBUG
-    qWarning("AnimationDriver: Main Thread: stopped");
-#endif
-    mutex.lock();
-    animationRunning = false;
-    mutex.unlock();
-}
-
 void QSGCanvas::paintEvent(QPaintEvent *)
 {
     Q_D(QSGCanvas);
@@ -180,7 +202,9 @@ void QSGCanvas::paintEvent(QPaintEvent *)
         int syncTime = frameTimer.elapsed();
 #endif
 
-        d->renderSceneGraph();
+        d->renderSceneGraph(d->widgetSize);
+
+        swapBuffers();
 
 #ifdef FRAME_TIMING
         printf("FrameTimes, last=%d, animations=%d, polish=%d, makeCurrent=%d, sync=%d, sgrender=%d, readback=%d, total=%d\n",
@@ -198,6 +222,11 @@ void QSGCanvas::paintEvent(QPaintEvent *)
 
         if (d->animationDriver->isRunning())
             update();
+    } else {
+        if (isUpdatesEnabled()) {
+            d->thread->paint();
+            setUpdatesEnabled(false);
+        }
     }
 }
 
@@ -205,10 +234,7 @@ void QSGCanvas::resizeEvent(QResizeEvent *e)
 {
     Q_D(QSGCanvas);
     if (d->threadedRendering) {
-        d->mutex.lock();
-        QGLWidget::resizeEvent(e);
-        d->widgetSize = e->size();
-        d->mutex.unlock();
+        d->thread->resize(e->size());
     } else {
         d->widgetSize = e->size();
         d->viewportSize = d->widgetSize;
@@ -223,18 +249,14 @@ void QSGCanvas::showEvent(QShowEvent *e)
     QGLWidget::showEvent(e);
 
     if (d->threadedRendering) {
-        d->contextInThread = true;
-        doneCurrent();
         if (!d->animationDriver) {
             d->animationDriver = d->context->createAnimationDriver(this);
-            connect(d->animationDriver, SIGNAL(started()), this, SLOT(_q_animationStarted()), Qt::DirectConnection);
-            connect(d->animationDriver, SIGNAL(stopped()), this, SLOT(_q_animationStopped()), Qt::DirectConnection);
+            connect(d->animationDriver, SIGNAL(started()), d->thread, SLOT(animationStarted()), Qt::DirectConnection);
+            connect(d->animationDriver, SIGNAL(stopped()), d->thread, SLOT(animationStopped()), Qt::DirectConnection);
         }
         d->animationDriver->install();
-        d->mutex.lock();
-        d->thread->start();
-        d->wait.wait(&d->mutex);
-        d->mutex.unlock();
+        d->thread->startRenderThread();
+        setUpdatesEnabled(true);
     } else {
         makeCurrent();
 
@@ -252,8 +274,9 @@ void QSGCanvas::hideEvent(QHideEvent *e)
 {
     Q_D(QSGCanvas);
 
-    if (d->threadedRendering)
-        d->stopRenderingThread();
+    if (d->threadedRendering) {
+        d->thread->stopRenderThread();
+    }
 
     d->animationDriver->uninstall();
 
@@ -312,18 +335,14 @@ void QSGCanvasPrivate::polishItems()
 
 void QSGCanvasPrivate::syncSceneGraph()
 {
-    inSync = true;
     updateDirtyNodes();
-    inSync = false;
 }
 
 
-void QSGCanvasPrivate::renderSceneGraph()
+void QSGCanvasPrivate::renderSceneGraph(const QSize &size)
 {
-    QGLContext *glctx = const_cast<QGLContext *>(QGLContext::currentContext());
-
-    context->renderer()->setDeviceRect(QRect(QPoint(0, 0), viewportSize));
-    context->renderer()->setViewportRect(QRect(QPoint(0, 0), viewportSize));
+    context->renderer()->setDeviceRect(QRect(QPoint(0, 0), size));
+    context->renderer()->setViewportRect(QRect(QPoint(0, 0), size));
     context->renderer()->setProjectMatrixToDeviceRect();
 
     context->renderNextFrame();
@@ -332,127 +351,22 @@ void QSGCanvasPrivate::renderSceneGraph()
     sceneGraphRenderTime = frameTimer.elapsed();
 #endif
 
-
 #ifdef FRAME_TIMING
 //    int pixel;
 //    glReadPixels(0, 0, 1, 1, GL_RGBA, GL_UNSIGNED_BYTE, &pixel);
     readbackTime = frameTimer.elapsed();
 #endif
-
-    glctx->swapBuffers();
 }
 
 
+// ### Do we need this?
 void QSGCanvas::sceneGraphChanged()
 {
-    Q_D(QSGCanvas);
-    d->needsRepaint = true;
+//    Q_D(QSGCanvas);
+//    d->needsRepaint = true;
 }
 
 
-void QSGCanvasPrivate::runThread()
-{
-#ifdef THREAD_DEBUG
-    qWarning("QSGRenderer: Render thread running");
-#endif
-    Q_Q(QSGCanvas);
-
-    printf("QSGCanvas::runThread(), rendering in a thread...\n");
-
-    q->makeCurrent();
-    initializeSceneGraph();
-
-    QObject::connect(context->renderer(), SIGNAL(sceneGraphChanged()),
-                      q, SLOT(sceneGraphChanged()),
-                      Qt::DirectConnection);
-
-    mutex.lock();
-    wait.wakeOne(); // Wake the main thread waiting for us to start
-
-    while (true) {
-        QSize s;
-        s = widgetSize;
-
-        if (exitThread)
-            break;
-
-        if (s != viewportSize) {
-            glViewport(0, 0, s.width(), s.height());
-            viewportSize = s;
-        }
-
-#ifdef THREAD_DEBUG
-        qWarning("QSGRenderer: Render Thread: Waiting for main thread to stop");
-#endif
-        QCoreApplication::postEvent(q, new QEvent(QEvent::User));
-        wait.wait(&mutex);
-
-        if (exitThread) {
-#ifdef THREAD_DEBUG
-            qWarning("QSGRenderer: Render Thread: Shutting down...");
-#endif
-            break;
-        }
-
-#ifdef THREAD_DEBUG
-        qWarning("QSGRenderer: Render Thread: Main thread has stopped, syncing scene");
-#endif
-
-        // Do processing while main thread is frozen
-        syncSceneGraph();
-
-#ifdef THREAD_DEBUG
-        qWarning("QSGRenderer: Render Thread: Resuming main thread");
-#endif
-
-        // Read animationRunning while inside the locked section
-        bool continous = animationRunning;
-
-        wait.wakeOne();
-        mutex.unlock();
-
-        bool enterIdle = false;
-        if (needsRepaint) {
-#ifdef THREAD_DEBUG
-            qWarning("QSGRenderer: Render Thread: rendering scene");
-#endif
-            renderSceneGraph();
-            needsRepaint = false;
-        } else if (continous) {
-#ifdef THREAD_DEBUG
-            qWarning("QSGRenderer: Render Thread: waiting a while...");
-#endif
-            MyThread::doWait();
-        } else {
-            enterIdle = true;
-        }
-
-        mutex.lock();
-
-        if (enterIdle) {
-#ifdef THREAD_DEBUG
-            qWarning("QSGRenderer: Render Thread: Nothing has changed, going idle...");
-#endif
-            idle = true;
-            wait.wait(&mutex);
-            idle = false;
-#ifdef THREAD_DEBUG
-            qWarning("QSGRenderer: Render Thread: waking up from idle");
-#endif
-        }
-
-    }
-
-
-#ifdef THREAD_DEBUG
-    qWarning("QSGRenderer: Render Thread: shutting down, waking up main thread");
-#endif
-    wait.wakeOne();
-    mutex.unlock();
-
-    q->doneCurrent();
-}
-
 QSGCanvasPrivate::QSGCanvasPrivate()
     : rootItem(0)
     , activeFocusItem(0)
@@ -460,15 +374,10 @@ QSGCanvasPrivate::QSGCanvasPrivate()
     , hoverItem(0)
     , dirtyItemList(0)
     , context(0)
-    , contextInThread(false)
     , threadedRendering(false)
-    , exitThread(false)
     , animationRunning(false)
-    , idle(false)
-    , needsRepaint(true)
     , renderThreadAwakened(false)
-    , inSync(false)
-    , thread(new MyThread(this))
+    , thread(0)
     , animationDriver(0)
 {
     threadedRendering = !qmlNoThreadedRenderer();
@@ -495,6 +404,13 @@ void QSGCanvasPrivate::init(QSGCanvas *c)
     rootItemPrivate->flags |= QSGItem::ItemIsFocusScope;
 
     context = QSGContext::createDefaultContext();
+
+    if (threadedRendering) {
+        thread = new QSGCanvasRenderThread;
+        thread->renderer = q;
+        thread->d = this;
+    }
+
 }
 
 void QSGCanvasPrivate::sceneMouseEventForTransform(QGraphicsSceneMouseEvent &sceneEvent,
@@ -945,9 +861,10 @@ QSGCanvas::~QSGCanvas()
 {
     Q_D(QSGCanvas);
 
-    if (d->threadedRendering) {
-        d->stopRenderingThread();
+    if (d->threadedRendering && d->thread->isRunning()) {
+        d->thread->stopRenderThread();
         delete d->thread;
+        d->thread = 0;
     }
 
     // ### should we change ~QSGItem to handle this better?
@@ -1022,31 +939,24 @@ bool QSGCanvas::event(QEvent *e)
     Q_D(QSGCanvas);
 
     if (e->type() == QEvent::User) {
-        Q_ASSERT(d->threadedRendering);
+        if (!d->thread->syncAlreadyHappened)
+            d->thread->sync(false);
+
+        d->thread->syncAlreadyHappened = false;
 
-        d->mutex.lock();
+        if (d->animationRunning) {
 #ifdef THREAD_DEBUG
-        qWarning("QSGRenderer: Main Thread: Stopped");
+            qDebug("GUI: Advancing animations...\n");
 #endif
 
-        d->polishItems();
-
-        d->renderThreadAwakened = false;
-
-        d->wait.wakeOne();
+            d->animationDriver->advance();
 
-        // The thread is exited when the widget has been hidden. We then need to
-        // skip the waiting, otherwise we would be waiting for a wakeup that never
-        // comes.
-        if (d->thread->isRunning())
-            d->wait.wait(&d->mutex);
 #ifdef THREAD_DEBUG
-        qWarning("QSGRenderer: Main Thread: Resumed");
+            qDebug("GUI: Animations advanced...\n");
 #endif
-        d->mutex.unlock();
+        }
 
-        if (d->animationRunning)
-            d->animationDriver->advance();
+        return true;
     }
 
     switch (e->type()) {
@@ -1881,24 +1791,17 @@ void QSGCanvas::maybeUpdate()
 {
     Q_D(QSGCanvas);
 
-    if (d->threadedRendering) {
+    if (d->threadedRendering && d->thread && d->thread->isRunning()) {
         if (!d->renderThreadAwakened) {
-            d->renderThreadAwakened = true;
-            bool locked = d->mutex.tryLock();
-            if (d->idle && locked) {
 #ifdef THREAD_DEBUG
-                qWarning("QSGRenderer: now maybe I should update...");
+            printf("GUI: doing update...\n");
 #endif
-                d->wait.wakeOne();
-            } else if (d->inSync) {
-                // If we are in sync (on scene graph thread) someone has explicitely asked us
-                // to redraw, hence we tell the render loop to not go idle.
-                // The primary usecase for this is updatePaintNode() calling update() without
-                // changing the scene graph.
-                d->needsRepaint = true;
-            }
-            if (locked)
-                d->mutex.unlock();
+            d->renderThreadAwakened = true;
+            d->thread->lockInGui();
+            d->thread->isExternalUpdatePending = true;
+            if (d->thread->isRenderBlocked)
+                d->thread->wake();
+            d->thread->unlockInGui();
         }
     } else if (!d->animationDriver || !d->animationDriver->isRunning()) {
         update();
@@ -1929,6 +1832,372 @@ QSGEngine *QSGCanvas::sceneGraphEngine() const
     return 0;
 }
 
+
+/*!
+    Grabs the contents of the framebuffer and returns it as an image.
+
+    This function might not work if the view is not visible.
+
+    \warning Calling this function will cause performance problems.
+ */
+QImage QSGCanvas::grabFrameBuffer()
+{
+    Q_D(QSGCanvas);
+    if (d->threadedRendering)
+        return d->thread ? d->thread->grab() : QImage();
+    else {
+        // render a fresh copy of the scene graph in the current thread.
+        d->renderSceneGraph(size());
+        return QGLWidget::grabFrameBuffer(false);
+    }
+}
+
+
+void QSGCanvasRenderThread::run()
+{
+    qDebug("QML Rendering Thread Started");
+
+    renderer->makeCurrent();
+
+    if (!d->context->isReady())
+        d->initializeSceneGraph();
+
+    
+    while (!shouldExit) {
+        lock();
+
+        bool sizeChanged = false;
+        isExternalUpdatePending = false;
+
+        if (renderedSize != windowSize) {
+#ifdef THREAD_DEBUG
+            printf("                RenderThread: window has changed size...\n");
+#endif
+            glViewport(0, 0, windowSize.width(), windowSize.height());
+            sizeChanged = true;
+        }
+
+#ifdef THREAD_DEBUG
+        printf("                RenderThread: preparing to sync...\n");
+#endif
+
+        if (!isGuiBlocked) {
+            isGuiBlockPending = true;
+
+#ifdef THREAD_DEBUG
+            printf("                RenderThread: aquired sync lock...\n");
+#endif
+            QApplication::postEvent(renderer, new QEvent(QEvent::User));
+#ifdef THREAD_DEBUG
+            printf("                RenderThread: going to sleep...\n");
+#endif
+            wait();
+
+            isGuiBlockPending = false;
+        }
+
+#ifdef THREAD_DEBUG
+        printf("                RenderThread: Doing locked sync\n");
+#endif
+        d->syncSceneGraph();
+
+        // Wake GUI after sync to let it continue animating and event processing.
+        wake();
+        unlock();
+#ifdef THREAD_DEBUG
+        printf("                RenderThread: sync done\n");
+#endif
+
+
+
+#ifdef THREAD_DEBUG
+        printf("                RenderThread: rendering... %d x %d\n", windowSize.width(), windowSize.height());
+#endif
+
+        d->renderSceneGraph(windowSize);
+
+        // The content of the target buffer is undefined after swap() so grab needs
+        // to happen before swap();
+        if (doGrab) {
+#ifdef THREAD_DEBUG
+            printf("                RenderThread: doing a grab...\n");
+#endif
+            grabContent = qt_gl_read_framebuffer(windowSize, false, false);
+            doGrab = false;
+        }
+
+#ifdef THREAD_DEBUG
+        printf("                RenderThread: wait for swap...\n");
+#endif
+
+        renderer->swapBuffers();
+#ifdef THREAD_DEBUG
+        printf("                RenderThread: swap complete...\n");
+#endif
+
+        lock();
+        isPaintCompleted = true;
+        if (sizeChanged)
+            renderedSize = windowSize;
+
+        // Wake the GUI thread now that rendering is complete, to signal that painting
+        // is done, resizing is done or grabbing is completed. For grabbing, we're
+        // signalling this much later than needed (we could have done it before swap)
+        // but we don't want to lock an extra time.
+        wake();
+
+        if (!d->animationRunning && !isExternalUpdatePending) {
+#ifdef THREAD_DEBUG
+            printf("                RenderThread: nothing to do, going to sleep...\n");
+#endif
+            isRenderBlocked = true;
+            wait();
+            isRenderBlocked = false;
+        }
+
+        unlock();
+    }
+
+#ifdef THREAD_DEBUG
+    printf("                RenderThread: exited... Good Night!\n");
+#endif
+
+    renderer->doneCurrent();
+
+    lock();
+    hasExited = true;
+#ifdef THREAD_DEBUG
+    printf("                RenderThread: waking GUI for final sleep..\n");
+#endif
+    wake();
+    unlock();
+}
+
+
+
+void QSGCanvasRenderThread::exhaustSyncEvent()
+{
+    if (isGuiBlockPending) {
+        sync(true);
+        syncAlreadyHappened = true;
+    }
+}
+
+
+
+void QSGCanvasRenderThread::sync(bool guiAlreadyLocked)
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: sync - %s\n", guiAlreadyLocked ? "outside event" : "inside event");
+#endif
+    Q_ASSERT(d->threadedRendering);
+
+    if (!guiAlreadyLocked)
+        d->thread->lockInGui();
+
+    d->renderThreadAwakened = false;
+
+    d->polishItems();
+
+    d->thread->wake();
+    d->thread->wait();
+
+    if (!guiAlreadyLocked)
+        d->thread->unlockInGui();
+}
+
+
+
+
+/*!
+    Acquires the mutex for the GUI thread. The function uses the isGuiBlocked
+    variable to keep track of how many recursion levels the gui is locket with.
+    We only actually acquire the mutex for the first level to avoid deadlocking
+    ourselves.
+ */
+
+void QSGCanvasRenderThread::lockInGui()
+{
+    // We must avoid recursive locking in the GUI thread, hence we
+    // only lock when we are the first one to try to block.
+    if (!isGuiBlocked)
+        lock();
+
+    isGuiBlocked++;
+
+#ifdef THREAD_DEBUG
+    printf("GUI: aquired lock... %d\n", isGuiBlocked);
+#endif
+}
+
+
+
+void QSGCanvasRenderThread::unlockInGui()
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: releasing lock... %d\n", isGuiBlocked);
+#endif
+    --isGuiBlocked;
+    if (!isGuiBlocked)
+        unlock();
+}
+
+
+
+
+void QSGCanvasRenderThread::animationStarted()
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: animationStarted()\n");
+#endif
+
+    lockInGui();
+
+    d->animationRunning = true;
+
+    if (isRenderBlocked)
+        wake();
+
+    unlockInGui();
+}
+
+
+
+void QSGCanvasRenderThread::animationStopped()
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: animationStopped()...\n");
+#endif
+
+    lockInGui();
+    d->animationRunning = false;
+    unlockInGui();
+}
+
+
+void QSGCanvasRenderThread::paint()
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: paint called..\n");
+#endif
+
+    lockInGui();
+    exhaustSyncEvent();
+
+    isPaintCompleted = false;
+    while (isRunning() && !isPaintCompleted)
+        wait();
+    unlockInGui();
+
+    // paint is only called for the inital show. After that we will do all
+    // drawing ourselves, so block future updates..
+    renderer->setUpdatesEnabled(false);
+}
+
+
+
+void QSGCanvasRenderThread::resize(const QSize &size)
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: Resize Event: %dx%d\n", size.width(), size.height());
+#endif
+
+    if (!isRunning()) {
+        windowSize = size;
+        return;
+    }
+
+    lockInGui();
+    exhaustSyncEvent();
+
+    windowSize = size;
+
+    while (isRunning() && renderedSize != windowSize) {
+        if (isRenderBlocked)
+            wake();
+        wait();
+    }
+    unlockInGui();
+}
+
+
+
+void QSGCanvasRenderThread::startRenderThread()
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: Starting Render Thread\n");
+#endif
+    hasExited = false;
+    shouldExit = false;
+    isGuiBlocked = 0;
+    isGuiBlockPending = false;
+
+    renderer->doneCurrent();
+    start();
+}
+
+
+
+void QSGCanvasRenderThread::stopRenderThread()
+{
+#ifdef THREAD_DEBUG
+    printf("GUI: stopping render thread\n");
+#endif
+
+    lockInGui();
+    exhaustSyncEvent();
+    shouldExit = true;
+
+    if (isRenderBlocked) {
+#ifdef THREAD_DEBUG
+        printf("GUI: waking up render thread\n");
+#endif
+        wake();
+    }
+
+    while (!hasExited) {
+#ifdef THREAD_DEBUG
+        printf("GUI: waiting for render thread to have exited..\n");
+#endif
+        wait();
+    }
+
+    unlockInGui();
+}
+
+
+
+QImage QSGCanvasRenderThread::grab()
+{
+    if (!isRunning())
+        return QImage();
+
+#ifdef THREAD_DEBUG
+    printf("GUI: doing a pixelwise grab..\n");
+#endif
+
+    lockInGui();
+    exhaustSyncEvent();
+
+    doGrab = true;
+    isPaintCompleted = false;
+    while (isRunning() && !isPaintCompleted) {
+        // If we're in an animation we don't need to wake as we know another frame
+        // will be pending anyway.
+        if (!d->animationRunning)
+            wake();
+        wait();
+    }
+
+    QImage grabbed = grabContent;
+    grabContent = QImage();
+
+    unlockInGui();
+
+    return grabbed;
+}
+
+
 #include "moc_qsgcanvas.cpp"
 
 QT_END_NAMESPACE
diff --git a/src/declarative/items/qsgcanvas.h b/src/declarative/items/qsgcanvas.h
index 6707d24b30..d0d0c79d5e 100644
--- a/src/declarative/items/qsgcanvas.h
+++ b/src/declarative/items/qsgcanvas.h
@@ -75,6 +75,8 @@ public:
 
     QSGEngine *sceneGraphEngine() const;
 
+    QImage grabFrameBuffer();
+
 Q_SIGNALS:
     void sceneGraphInitialized();
 
@@ -109,8 +111,6 @@ private Q_SLOTS:
 
 private:
     Q_DISABLE_COPY(QSGCanvas)
-    Q_PRIVATE_SLOT(d_func(), void _q_animationStarted())
-    Q_PRIVATE_SLOT(d_func(), void _q_animationStopped())
 };
 
 QT_END_NAMESPACE
diff --git a/src/declarative/items/qsgcanvas_p.h b/src/declarative/items/qsgcanvas_p.h
index 6b8034f922..9ea11f5892 100644
--- a/src/declarative/items/qsgcanvas_p.h
+++ b/src/declarative/items/qsgcanvas_p.h
@@ -78,6 +78,8 @@ public:
 class QSGCanvasPrivate;
 
 class QTouchEvent;
+class QSGCanvasRenderThread;
+
 class QSGCanvasPrivate : public QGLWidgetPrivate
 {
 public:
@@ -115,8 +117,6 @@ public:
     void sendHoverEvent(QEvent::Type, QSGItem *, QGraphicsSceneHoverEvent *);
     void clearHover();
 
-    void stopRenderingThread();
-
     QDeclarativeGuard<QSGItem> hoverItem;
     enum FocusOption {
         DontChangeFocusProperty = 0x01,
@@ -135,11 +135,7 @@ public:
     void initializeSceneGraph();
     void polishItems();
     void syncSceneGraph();
-    void renderSceneGraph();
-    void runThread();
-
-    void _q_animationStarted();
-    void _q_animationStopped();
+    void renderSceneGraph(const QSize &size);
 
     QSGItem::UpdatePaintNodeData updatePaintNodeData;
 
@@ -158,22 +154,10 @@ public:
 
     uint contextInThread : 1;
     uint threadedRendering : 1;
-    uint exitThread : 1;
     uint animationRunning: 1;
-    uint idle : 1;              // Set to true when render thread sees no change and enters a wait()
-    uint needsRepaint : 1;      // Set by callback from render if scene needs repainting.
     uint renderThreadAwakened : 1;
-    uint inSync: 1;
 
-    struct MyThread : public QThread {
-        MyThread(QSGCanvasPrivate *r) : renderer(r) {}
-        virtual void run() { renderer->runThread(); }
-        static void doWait() { QThread::msleep(16); }
-        QSGCanvasPrivate *renderer;
-    };
-    MyThread *thread;
-    QMutex mutex;
-    QWaitCondition wait;
+    QSGCanvasRenderThread *thread;
     QSize widgetSize;
     QSize viewportSize;
 
@@ -182,6 +166,73 @@ public:
     QHash<int, QSGItem *> itemForTouchPointId;
 };
 
+
+
+class QSGCanvasRenderThread : public QThread
+{
+    Q_OBJECT
+public:
+    QSGCanvasRenderThread()
+        : mutex(QMutex::NonRecursive)
+        , isGuiBlocked(0)
+        , isPaintCompleted(false)
+        , isGuiBlockPending(false)
+        , isRenderBlocked(false)
+        , isExternalUpdatePending(false)
+        , syncAlreadyHappened(false)
+        , doGrab(false)
+        , shouldExit(false)
+        , hasExited(false)
+    {}
+
+    inline void lock() { mutex.lock(); }
+    inline void unlock() { mutex.unlock(); }
+    inline void wait() { condition.wait(&mutex); }
+    inline void wake() { condition.wakeOne(); }
+
+    void lockInGui();
+    void unlockInGui();
+
+    void paint();
+    void resize(const QSize &size);
+    void startRenderThread();
+    void stopRenderThread();
+    void exhaustSyncEvent();
+    void sync(bool guiAlreadyLocked);
+
+    QImage grab();
+
+public slots:
+    void animationStarted();
+    void animationStopped();
+
+public:
+    QMutex mutex;
+    QWaitCondition condition;
+
+    QSize windowSize;
+    QSize renderedSize;
+
+    QSGCanvas *renderer;
+    QSGCanvasPrivate *d;
+
+    int isGuiBlocked;
+    uint isPaintCompleted : 1;
+    uint isGuiBlockPending : 1;
+    uint isRenderBlocked : 1;
+    uint isExternalUpdatePending : 1;
+    uint syncAlreadyHappened : 1;
+
+    uint doGrab : 1;
+    uint shouldExit : 1;
+    uint hasExited : 1;
+
+    QImage grabContent;
+
+    void run();
+};
+
+
 Q_DECLARE_OPERATORS_FOR_FLAGS(QSGCanvasPrivate::FocusOptions)
 
 QT_END_NAMESPACE
-- 
GitLab