diff options
Diffstat (limited to 'src/quick/items/qquickwindowmanager.cpp')
-rw-r--r-- | src/quick/items/qquickwindowmanager.cpp | 1242 |
1 files changed, 1242 insertions, 0 deletions
diff --git a/src/quick/items/qquickwindowmanager.cpp b/src/quick/items/qquickwindowmanager.cpp new file mode 100644 index 0000000000..c6baf13758 --- /dev/null +++ b/src/quick/items/qquickwindowmanager.cpp @@ -0,0 +1,1242 @@ +/**************************************************************************** +** +** Copyright (C) 2010 Nokia Corporation and/or its subsidiary(-ies). +** All rights reserved. +** Contact: Nokia Corporation (qt-info@nokia.com) +** +** This file is part of the QtDeclarative module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** GNU Lesser General Public License Usage +** This file may be used under the terms of the GNU Lesser General Public +** License version 2.1 as published by the Free Software Foundation and +** appearing in the file LICENSE.LGPL included in the packaging of this +** file. Please review the following information to ensure the GNU Lesser +** General Public License version 2.1 requirements will be met: +** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html. +** +** In addition, as a special exception, Nokia gives you certain additional +** rights. These rights are described in the Nokia Qt LGPL Exception +** version 1.1, included in the file LGPL_EXCEPTION.txt in this package. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU General +** Public License version 3.0 as published by the Free Software Foundation +** and appearing in the file LICENSE.GPL included in the packaging of this +** file. Please review the following information to ensure the GNU General +** Public License version 3.0 requirements will be met: +** http://www.gnu.org/copyleft/gpl.html. +** +** Other Usage +** Alternatively, this file may be used in accordance with the terms and +** conditions contained in a signed written agreement between you and Nokia. +** +** +** +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qquickwindowmanager_p.h" + +#include <QtCore/QCoreApplication> +#include <QtCore/QTime> +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/private/qabstractanimation_p.h> + +#include <QtGui/QOpenGLContext> +#include <QtGui/private/qguiapplication_p.h> + +#include <QtDeclarative/private/qdeclarativeglobal_p.h> + +#include <QtQuick/QQuickCanvas> +#include <QtQuick/private/qquickcanvas_p.h> +#include <QtQuick/private/qsgcontext_p.h> + +QT_BEGIN_NAMESPACE + + +#define QQUICK_CANVAS_TIMING +#ifdef QQUICK_CANVAS_TIMING +static bool qquick_canvas_timing = !qgetenv("QML_CANVAS_TIMING").isEmpty(); +static QTime threadTimer; +static int syncTime; +static int renderTime; +static int swapTime; +#endif + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + + + +/*! + expectations for this manager to work: + - one opengl context to render multiple windows + - OpenGL pipeline will not block for vsync in swap + - OpenGL pipeline will block based on a full buffer queue. + - Multiple screens can share the OpenGL context + - Animations are advanced for all windows once per swap + */ + +/* + 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::isGuiLocked: 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 isPostingSyncEvent. + + 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::isPostingSyncEvent: 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 when + a new render pass is started 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_BOOL_CONFIG_OPTION(qmlFixedAnimationStep, QML_FIXED_ANIMATION_STEP); +DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP); + +//#define THREAD_DEBUG + +class QQuickRenderThreadSingleContextWindowManager : public QThread, public QQuickWindowManager +{ + Q_OBJECT +public: + QQuickRenderThreadSingleContextWindowManager() + : sg(QSGContext::createDefaultContext()) + , gl(0) + , animationTimer(-1) + , isGuiLocked(0) + , animationRunning(false) + , isPaintCompleted(false) + , isPostingSyncEvent(false) + , isRenderBlocked(false) + , syncAlreadyHappened(false) + , inSync(false) + , shouldExit(false) + , hasExited(false) + , renderThreadAwakened(false) + , canvasToGrab(0) + { + sg->moveToThread(this); + + animationDriver = sg->createAnimationDriver(this); + animationDriver->install(); + connect(animationDriver, SIGNAL(started()), this, SLOT(animationStarted())); + connect(animationDriver, SIGNAL(stopped()), this, SLOT(animationStopped())); + } + + QSGContext *sceneGraphContext() const { return sg; } + + void show(QQuickCanvas *canvas); + void hide(QQuickCanvas *canvas); + + void canvasDestroyed(QQuickCanvas *canvas); + + void paint(QQuickCanvas *canvas); + QImage grab(QQuickCanvas *canvas); + void resize(QQuickCanvas *canvas, const QSize &size); + void maybeUpdate(QQuickCanvas *canvas); + + void startRendering(); + void stopRendering(); + + void exhaustSyncEvent(); + void sync(bool guiAlreadyLocked); + + void initialize(); + + bool *allowMainThreadProcessing() { return &allowMainThreadProcessingFlag; } + + bool event(QEvent *); + + 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 run(); + +public slots: + void animationStarted(); + void animationStopped(); + void canvasVisibilityChanged(); + +private: + void handleAddedWindows(); + void handleAddedWindow(QQuickCanvas *canvas); + void handleRemovedWindows(); + + QSGContext *sg; + QOpenGLContext *gl; + QAnimationDriver *animationDriver; + int animationTimer; + + QMutex mutex; + QWaitCondition condition; + + bool allowMainThreadProcessingFlag; + + int isGuiLocked; + uint animationRunning: 1; + uint isPaintCompleted : 1; + uint isPostingSyncEvent : 1; + uint isRenderBlocked : 1; + uint isExternalUpdatePending : 1; + uint syncAlreadyHappened : 1; + uint inSync : 1; + uint shouldExit : 1; + uint hasExited : 1; + uint renderThreadAwakened : 1; + uint isGuiAboutToBeBlockedForSync : 1; + + QQuickCanvas *canvasToGrab; + QImage grabContent; + + struct CanvasData { + QSize renderedSize; + QSize windowSize; + QSize viewportSize; + + uint sizeWasChanged : 1; + uint isExternalUpdatePending : 1; + }; + + QHash<QQuickCanvas *, CanvasData *> m_rendered_windows; + + struct CanvasTracker { + QQuickCanvas *canvas; + uint isVisible : 1; + uint toBeRemoved : 1; + }; + + QList<CanvasTracker> m_tracked_windows; + + QList<QQuickCanvas *> m_removed_windows; + QList<QQuickCanvas *> m_added_windows; +}; + + +class QQuickTrivialWindowManager : public QObject, public QQuickWindowManager +{ +public: + QQuickTrivialWindowManager(); + + void show(QQuickCanvas *canvas); + void hide(QQuickCanvas *canvas); + + void canvasDestroyed(QQuickCanvas *canvas); + + void renderCanvas(QQuickCanvas *canvas); + void paint(QQuickCanvas *canvas); + QImage grab(QQuickCanvas *canvas); + void resize(QQuickCanvas *canvas, const QSize &size); + + void maybeUpdate(QQuickCanvas *canvas); + + bool *allowMainThreadProcessing(); + + QSGContext *sceneGraphContext() const; + + bool event(QEvent *); + + struct CanvasData { + bool updatePending : 1; + bool grabOnly : 1; + }; + + QHash<QQuickCanvas *, CanvasData> m_windows; + + QOpenGLContext *gl; + QSGContext *sg; + + QImage grabContent; + + bool eventPending; +}; + + +QQuickWindowManager *QQuickWindowManager::instance() +{ + static QQuickWindowManager *theInstance; + + if (!theInstance) { + bool fancy = QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL); + if (qmlNoThreadedRenderer()) + fancy = false; + if (qmlFixedAnimationStep()) + QUnifiedTimer::instance(true)->setConsistentTiming(true); + theInstance = fancy + ? (QQuickWindowManager*) new QQuickRenderThreadSingleContextWindowManager + : (QQuickWindowManager*) new QQuickTrivialWindowManager; + } + return theInstance; +} + + + + + +void QQuickRenderThreadSingleContextWindowManager::initialize() +{ + Q_ASSERT(m_rendered_windows.size()); + QQuickCanvas *win = m_rendered_windows.constBegin().key(); + + gl = new QOpenGLContext(); + // Pick up the surface format from one of them + gl->setFormat(win->requestedFormat()); + gl->create(); + gl->makeCurrent(win); + + Q_ASSERT(!sg->isReady()); + sg->initialize(gl); +} + + +/*! + This function is called when the canvas is created to register the canvas with + the window manager. + + Called on GUI Thread. + */ + +void QQuickRenderThreadSingleContextWindowManager::show(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf("GUI: Canvas added to windowing system, %p, %dx%d\n", canvas, canvas->width(), canvas->height()); +#endif + + CanvasTracker tracker; + tracker.canvas = canvas; + tracker.isVisible = false; + tracker.toBeRemoved = false; + m_tracked_windows << tracker; + + connect(canvas, SIGNAL(widthChanged(int)), this, SLOT(canvasVisibilityChanged()), Qt::DirectConnection); + connect(canvas, SIGNAL(heightChanged(int)), this, SLOT(canvasVisibilityChanged()), Qt::DirectConnection); + + canvasVisibilityChanged(); +} + + +void QQuickRenderThreadSingleContextWindowManager::handleAddedWindow(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf(" RenderThread: adding canvas: %p\n", canvas); +#endif + + CanvasData *data = new CanvasData; + data->isExternalUpdatePending = true; + data->sizeWasChanged = false; + data->windowSize = canvas->size(); + m_rendered_windows[canvas] = data; +} + + +/*! + Called on Render Thread + */ +void QQuickRenderThreadSingleContextWindowManager::handleAddedWindows() +{ +#ifdef THREAD_DEBUG + printf(" RenderThread: about to add %d\n", m_added_windows.size()); +#endif + + while (m_added_windows.size()) { + QQuickCanvas *canvas = m_added_windows.takeLast(); + handleAddedWindow(canvas); + } +} + + +/*! + Called on the GUI Thread, from the canvas' destructor + */ + +void QQuickRenderThreadSingleContextWindowManager::canvasDestroyed(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf("GUI: Canvas destroyed: %p\n", canvas); +#endif + + hide(canvas); +} + + +/*! + Called on GUI Thread + */ + +void QQuickRenderThreadSingleContextWindowManager::hide(QQuickCanvas *canvas) +{ +#ifdef THREAD_DEBUG + printf("GUI: Canvas hidden: %p\n", canvas); +#endif + + int position = -1; + for (int i=0; i<m_tracked_windows.size(); ++i) { + if (m_tracked_windows.at(i).canvas == canvas) { + m_tracked_windows[i].toBeRemoved = true; + position = i; + break; + } + } + + if (position >= 0) { + disconnect(canvas, SIGNAL(widthChanged(int)), this, SLOT(canvasVisibilityChanged())); + disconnect(canvas, SIGNAL(heightChanged(int)), this, SLOT(canvasVisibilityChanged())); + canvasVisibilityChanged(); + m_tracked_windows.removeAt(position); + } + +#ifdef THREAD_DEBUG + printf("GUI: Canvas removal completed... %p\n", canvas); +#endif +} + +/*! + Called on Render Thread + */ +void QQuickRenderThreadSingleContextWindowManager::handleRemovedWindows() +{ +#ifdef THREAD_DEBUG + printf(" RenderThread: about to remove %d\n", m_removed_windows.size()); +#endif + + bool removedAnything = false; + while (m_removed_windows.size()) { + QQuickCanvas *canvas = m_removed_windows.takeLast(); +#ifdef THREAD_DEBUG + printf(" RenderThread: removing %p\n", canvas); +#endif + + QQuickCanvasPrivate::get(canvas)->cleanupNodesOnShutdown(); + delete m_rendered_windows.take(canvas); + removedAnything = true; + } + + // If a window is removed because it has been hidden it will take with it + // the gl context (at least on Mac) if bound, so disconnect the gl context + // from anything + if (removedAnything) + gl->doneCurrent(); +} + + + +/*! + Called on GUI Thread + */ + +void QQuickRenderThreadSingleContextWindowManager::canvasVisibilityChanged() +{ + bool anyoneShowing = false; + QList<QQuickCanvas *> toAdd, toRemove; + + // Not optimal, but also not frequently used... + for (int i=0; i<m_tracked_windows.size(); ++i) { + CanvasTracker &t = const_cast<CanvasTracker &>(m_tracked_windows.at(i)); + QQuickCanvas *win = t.canvas; + + Q_ASSERT(win->visible() || t.toBeRemoved); + bool canvasVisible = win->width() > 0 && win->height() > 0; + anyoneShowing |= (canvasVisible && !t.toBeRemoved); + + if ((!canvasVisible && t.isVisible) || t.toBeRemoved) { + toRemove << win; + } else if (canvasVisible && !t.isVisible) { + toAdd << win; + } + t.isVisible = canvasVisible; + } + + if (isRunning()) { + if (!anyoneShowing) { + stopRendering(); + } else { + lockInGui(); + exhaustSyncEvent(); + m_added_windows << toAdd; + m_removed_windows << toRemove; + while (isRunning() && (m_added_windows.size() || m_removed_windows.size())) { + if (isRenderBlocked) + wake(); + wait(); + } + unlockInGui(); + } + + } else if (anyoneShowing) { + Q_ASSERT(toRemove.isEmpty()); // since loop is not running, nothing is showing now + for (int i=0; i<toAdd.size(); ++i) + handleAddedWindow(toAdd.at(i)); + startRendering(); + } + +} + + +void QQuickRenderThreadSingleContextWindowManager::run() +{ +#ifdef THREAD_DEBUG + printf("QML Rendering Thread Started\n"); +#endif + + if (!gl) + initialize(); + + while (!shouldExit) { + lock(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: *** NEW FRAME ***\n"); +#endif + + handleAddedWindows(); + + if (!isGuiLocked) { + isPostingSyncEvent = true; + +#ifdef THREAD_DEBUG + printf(" RenderThread: aquired sync lock...\n"); +#endif + allowMainThreadProcessingFlag = false; + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + +#ifdef THREAD_DEBUG + printf(" RenderThread: going to sleep...\n"); +#endif + wake(); // In case the event got through all the way to wait() before this thread got to wait. + wait(); + + + isPostingSyncEvent = false; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: Doing locked sync\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + threadTimer.start(); +#endif + inSync = true; + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + QQuickCanvas *canvas = it.key(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: Syncing canvas: %p\n", canvas); +#endif + + CanvasData *canvasData = it.value(); + QQuickCanvasPrivate *canvasPrivate = QQuickCanvasPrivate::get(canvas); + + Q_ASSERT(canvasData->windowSize.width() > 0 && canvasData->windowSize.height() > 0); + + gl->makeCurrent(canvas); + + if (canvasData->viewportSize != canvasData->windowSize) { +#ifdef THREAD_DEBUG + printf(" RenderThread: --- window has changed size...\n"); +#endif + canvasData->viewportSize = canvasData->windowSize; + canvasData->sizeWasChanged = true; + glViewport(0, 0, canvasData->viewportSize.width(), canvasData->viewportSize.height()); + } + + canvasData->isExternalUpdatePending = false; + canvasPrivate->syncSceneGraph(); + } + inSync = false; + + // Wake GUI after sync to let it continue animating and event processing. + allowMainThreadProcessingFlag = true; + wake(); + unlock(); +#ifdef THREAD_DEBUG + printf(" RenderThread: sync done\n"); +#endif +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + syncTime = threadTimer.elapsed(); +#endif + + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + QQuickCanvas *canvas = it.key(); + CanvasData *canvasData = it.value(); + QQuickCanvasPrivate *canvasPrivate = QQuickCanvasPrivate::get(canvas); + +#ifdef THREAD_DEBUG + printf(" RenderThread: Rendering canvas %p\n", canvas); +#endif + + Q_ASSERT(canvasData->windowSize.width() > 0 && canvasData->windowSize.height() > 0); + +#ifdef THREAD_DEBUG + printf(" RenderThread: --- rendering at size %dx%d\n", + canvasData->viewportSize.width(), canvasData->viewportSize.height() + ); +#endif + + // We only need to re-makeCurrent when we have multiple surfaces. + if (m_rendered_windows.size() > 1) + gl->makeCurrent(canvas); + + canvasPrivate->renderSceneGraph(canvasData->viewportSize); +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + renderTime = threadTimer.elapsed() - syncTime; +#endif + + // The content of the target buffer is undefined after swap() so grab needs + // to happen before swap(); + if (canvas == canvasToGrab) { +#ifdef THREAD_DEBUG + printf(" RenderThread: --- grabbing...\n"); +#endif + grabContent = qt_gl_read_framebuffer(canvasData->windowSize, false, false); + canvasToGrab = 0; + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: --- wait for swap...\n"); +#endif + + gl->swapBuffers(canvas); +#ifdef THREAD_DEBUG + printf(" RenderThread: --- swap complete...\n"); +#endif + + } + +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) { + swapTime = threadTimer.elapsed() - renderTime; + qDebug() << "- Breakdown of frame time; sync:" << syncTime + << "ms render:" << renderTime << "ms swap:" << swapTime + << "ms total:" << swapTime + renderTime << "ms"; + } +#endif + + lock(); + + handleRemovedWindows(); + + isPaintCompleted = true; + + bool isExternalUpdatePending = false; + + // Update sizes... + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + CanvasData *canvasData = it.value(); + if (canvasData->sizeWasChanged) { + canvasData->renderedSize = canvasData->viewportSize; + canvasData->sizeWasChanged = false; + } + isExternalUpdatePending |= canvasData->isExternalUpdatePending; + } + + + // 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 (!animationRunning && !isExternalUpdatePending && !shouldExit && !canvasToGrab) { +#ifdef THREAD_DEBUG + printf(" RenderThread: nothing to do, going to sleep...\n"); +#endif + isRenderBlocked = true; + wait(); + isRenderBlocked = false; + } + + unlock(); + + QCoreApplication::processEvents(); + + // Process any "deleteLater" objects... + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + } + +#ifdef THREAD_DEBUG + printf(" RenderThread: deleting all outstanding nodes\n"); +#endif + + m_removed_windows << m_rendered_windows.keys(); + handleRemovedWindows(); + + sg->invalidate(); + + gl->doneCurrent(); + delete gl; + gl = 0; + +#ifdef THREAD_DEBUG + printf(" RenderThread: render loop exited... Good Night!\n"); +#endif + + lock(); + hasExited = true; + +#ifdef THREAD_DEBUG + printf(" RenderThread: waking GUI for final sleep..\n"); +#endif + wake(); + unlock(); + +#ifdef THREAD_DEBUG + printf(" RenderThread: All done...\n"); +#endif +} + +bool QQuickRenderThreadSingleContextWindowManager::event(QEvent *e) +{ + Q_ASSERT(QThread::currentThread() == qApp->thread()); + + if (e->type() == QEvent::User) { + + // If all canvases have been hidden, ignore the event + if (!isRunning()) + return true; + + if (!syncAlreadyHappened) + sync(false); + + syncAlreadyHappened = false; + + if (animationRunning) { +#ifdef THREAD_DEBUG + printf("GUI: Advancing animations...\n"); +#endif + + animationDriver->advance(); + +#ifdef THREAD_DEBUG + printf("GUI: Animations advanced...\n"); +#endif + } + + return true; + + } else if (e->type() == QEvent::Timer) { +#ifdef THREAD_DEBUG + printf("GUI: Animations advanced via timer...\n"); +#endif + animationDriver->advance(); + } + + return QThread::event(e); +} + + + +void QQuickRenderThreadSingleContextWindowManager::exhaustSyncEvent() +{ + if (isPostingSyncEvent) { + sync(true); + syncAlreadyHappened = true; + } +} + + + +void QQuickRenderThreadSingleContextWindowManager::sync(bool guiAlreadyLocked) +{ +#ifdef THREAD_DEBUG + printf("GUI: sync - %s\n", guiAlreadyLocked ? "outside event" : "inside event"); +#endif + if (!guiAlreadyLocked) + lockInGui(); + + renderThreadAwakened = false; + + for (QHash<QQuickCanvas *, CanvasData *>::const_iterator it = m_rendered_windows.constBegin(); + it != m_rendered_windows.constEnd(); ++it) { + QQuickCanvasPrivate::get(it.key())->polishItems(); + } + + wake(); + wait(); + + if (!guiAlreadyLocked) + unlockInGui(); +} + + + + +/*! + Acquires the mutex for the GUI thread. The function uses the isGuiLocked + variable to keep track of how many recursion levels the gui is locked with. + We only actually acquire the mutex for the first level to avoid deadlocking + ourselves. + */ + +void QQuickRenderThreadSingleContextWindowManager::lockInGui() +{ + if (++isGuiLocked == 1) + lock(); + +#ifdef THREAD_DEBUG + printf("GUI: aquired lock... level=%d\n", isGuiLocked); +#endif +} + + + +void QQuickRenderThreadSingleContextWindowManager::unlockInGui() +{ +#ifdef THREAD_DEBUG + printf("GUI: releasing lock... level=%d\n", isGuiLocked); +#endif + + if (--isGuiLocked == 0) + unlock(); +} + + + + +void QQuickRenderThreadSingleContextWindowManager::animationStarted() +{ +#ifdef THREAD_DEBUG + printf("GUI: animationStarted()\n"); +#endif + + if (!isRunning()) { + animationTimer = startTimer(1000/60); + return; + } + + lockInGui(); + + animationRunning = true; + + if (isRenderBlocked) + wake(); + + unlockInGui(); +} + + + +void QQuickRenderThreadSingleContextWindowManager::animationStopped() +{ +#ifdef THREAD_DEBUG + printf("GUI: animationStopped()...\n"); +#endif + + if (!isRunning()) { + killTimer(animationTimer); + animationTimer = -1; + return; + } + + lockInGui(); + animationRunning = false; + unlockInGui(); +} + + +void QQuickRenderThreadSingleContextWindowManager::paint(QQuickCanvas *canvas) +{ + Q_UNUSED(canvas); +#ifdef THREAD_DEBUG + printf("GUI: paint called: %p\n", canvas); +#endif + + return; + + + + lockInGui(); + exhaustSyncEvent(); + + isPaintCompleted = false; + while (isRunning() && !isPaintCompleted) { + if (isRenderBlocked) + wake(); + wait(); + } + unlockInGui(); + +#ifdef THREAD_DEBUG + printf("GUI: paint done: %p\n", canvas); +#endif +} + + + +void QQuickRenderThreadSingleContextWindowManager::resize(QQuickCanvas *canvas, const QSize &size) +{ +#ifdef THREAD_DEBUG + printf("GUI: Resize Event: %p = %dx%d\n", canvas, size.width(), size.height()); +#endif + + // If the rendering thread is not running we do not need to do anything. + // Also if the canvas is being resized to an invalid size, it will be removed + // by the canvasVisibilityChanged slot as result of width/heightcChanged() + if (!isRunning() || size.width() <= 0 || size.height() <= 0) + return; + + lockInGui(); + exhaustSyncEvent(); + + CanvasData *canvasData = m_rendered_windows.value(canvas); + if (canvasData) { + canvasData->windowSize = size; + while (isRunning() && canvasData->renderedSize != size && size.width() > 0 && size.height() > 0) { + if (isRenderBlocked) + wake(); + wait(); + } + } + unlockInGui(); + +#ifdef THREAD_DEBUG + printf("GUI: Resize done: %p\n", canvas); +#endif +} + + + +void QQuickRenderThreadSingleContextWindowManager::startRendering() +{ +#ifdef THREAD_DEBUG + printf("GUI: Starting Render Thread\n"); +#endif + hasExited = false; + shouldExit = false; + isGuiLocked = 0; + isPostingSyncEvent = false; + syncAlreadyHappened = false; + renderThreadAwakened = false; + inSync = false; + + start(); // Start the render thread... + + // Animations will now be driven from the rendering thread. + if (animationTimer >= 0) { + killTimer(animationTimer); + animationTimer = -1; + } + + +} + + + +void QQuickRenderThreadSingleContextWindowManager::stopRendering() +{ +#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(); + +#ifdef THREAD_DEBUG + printf("GUI: waiting for render thread to terminate..\n"); +#endif + // Actually wait for the thread to terminate. Otherwise we can delete it + // too early and crash. + QThread::wait(); + +#ifdef THREAD_DEBUG + printf("GUI: thread has terminated and we're all good..\n"); +#endif + + // Activate timer to keep animations running + if (animationDriver->isRunning()) + animationTimer = startTimer(1000/60); +} + + + +QImage QQuickRenderThreadSingleContextWindowManager::grab(QQuickCanvas *canvas) +{ + if (!isRunning()) + return QImage(); + + if (QThread::currentThread() != qApp->thread()) { + qWarning("QQuickCanvas::grabFrameBuffer: can only be called from the GUI thread"); + return QImage(); + } + +#ifdef THREAD_DEBUG + printf("GUI: doing a pixelwise grab..\n"); +#endif + + lockInGui(); + exhaustSyncEvent(); + + canvasToGrab = canvas; + isPaintCompleted = false; + while (isRunning() && !isPaintCompleted) { + if (isRenderBlocked) + wake(); + wait(); + } + + QImage grabbed = grabContent; + grabContent = QImage(); + + unlockInGui(); + + return grabbed; +} + + + +void QQuickRenderThreadSingleContextWindowManager::maybeUpdate(QQuickCanvas *canvas) +{ + Q_ASSERT_X(QThread::currentThread() == QCoreApplication::instance()->thread() || inSync, + "QQuickCanvas::update", + "Function can only be called from GUI thread or during QQuickItem::updatePaintNode()"); + + if (inSync) { + CanvasData *canvasData = m_rendered_windows.value(canvas); + if (canvasData) + canvasData->isExternalUpdatePending = true; + + } else if (!renderThreadAwakened) { +#ifdef THREAD_DEBUG + printf("GUI: doing update...\n"); +#endif + renderThreadAwakened = true; + bool locked = false; + + // If we are getting here from the renderer's sync event, the renderer is about + // to go to sleep anyway. + if (!isGuiAboutToBeBlockedForSync) + lockInGui(); + CanvasData *canvasData = m_rendered_windows.value(canvas); + if (canvasData) + canvasData->isExternalUpdatePending = true; + if (isRenderBlocked) + wake(); + if (!isGuiAboutToBeBlockedForSync) + unlockInGui(); + } +} + +QQuickTrivialWindowManager::QQuickTrivialWindowManager() + : gl(0) + , eventPending(false) +{ + sg = QSGContext::createDefaultContext(); +} + + +void QQuickTrivialWindowManager::show(QQuickCanvas *canvas) +{ + CanvasData data; + data.updatePending = false; + m_windows[canvas] = data; + + maybeUpdate(canvas); +} + +void QQuickTrivialWindowManager::hide(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + m_windows.remove(canvas); + QQuickCanvasPrivate *cd = QQuickCanvasPrivate::get(canvas); + cd->cleanupNodesOnShutdown(); + + if (m_windows.size() == 0) { + sg->invalidate(); + delete gl; + gl = 0; + } +} + +void QQuickTrivialWindowManager::canvasDestroyed(QQuickCanvas *canvas) +{ + hide(canvas); +} + +void QQuickTrivialWindowManager::renderCanvas(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + CanvasData &data = const_cast<CanvasData &>(m_windows[canvas]); + + if (!gl) { + gl = new QOpenGLContext(); + gl->setFormat(canvas->requestedFormat()); + gl->create(); + gl->makeCurrent(canvas); + sg->initialize(gl); + } else { + gl->makeCurrent(canvas); + } + + bool alsoSwap = data.updatePending; + data.updatePending = false; + + QQuickCanvasPrivate *cd = QQuickCanvasPrivate::get(canvas); + cd->polishItems(); + cd->syncSceneGraph(); + cd->renderSceneGraph(canvas->size()); + + if (data.grabOnly) { + grabContent = qt_gl_read_framebuffer(canvas->size(), false, false); + data.grabOnly = false; + } + + if (alsoSwap) + gl->swapBuffers(canvas); + + // Might have been set during syncSceneGraph() + if (data.updatePending) + maybeUpdate(canvas); +} + +void QQuickTrivialWindowManager::paint(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + m_windows[canvas].updatePending = true; + renderCanvas(canvas); +} + +QImage QQuickTrivialWindowManager::grab(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return QImage(); + + m_windows[canvas].grabOnly = true; + + renderCanvas(canvas); + + QImage grabbed = grabContent; + grabContent = QImage(); + return grabbed; +} + + + +void QQuickTrivialWindowManager::resize(QQuickCanvas *, const QSize &) +{ +} + + + +void QQuickTrivialWindowManager::maybeUpdate(QQuickCanvas *canvas) +{ + if (!m_windows.contains(canvas)) + return; + + m_windows[canvas].updatePending = true; + + if (!eventPending) { + QCoreApplication::postEvent(this, new QEvent(QEvent::User)); + eventPending = true; + } +} + + + +bool *QQuickTrivialWindowManager::allowMainThreadProcessing() +{ + return 0; +} + + + +QSGContext *QQuickTrivialWindowManager::sceneGraphContext() const +{ + return sg; +} + + +bool QQuickTrivialWindowManager::event(QEvent *e) +{ + if (e->type() == QEvent::User) { + eventPending = false; + for (QHash<QQuickCanvas *, CanvasData>::const_iterator it = m_windows.constBegin(); + it != m_windows.constEnd(); ++it) { + const CanvasData &data = it.value(); + if (data.updatePending) + renderCanvas(it.key()); + } + return true; + } + return QObject::event(e); +} + +#include "qquickwindowmanager.moc" + +QT_END_NAMESPACE |