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