diff options
Diffstat (limited to 'src/quick/scenegraph/qsgthreadedrenderloop.cpp')
-rw-r--r-- | src/quick/scenegraph/qsgthreadedrenderloop.cpp | 1113 |
1 files changed, 1113 insertions, 0 deletions
diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp new file mode 100644 index 0000000000..ef50d5a89d --- /dev/null +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -0,0 +1,1113 @@ +/**************************************************************************** +** +** Copyright (C) 2013 Digia Plc and/or its subsidiary(-ies). +** Contact: http://www.qt-project.org/legal +** +** This file is part of the QtQml module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and Digia. For licensing terms and +** conditions see http://qt.digia.com/licensing. For further information +** use the contact form at http://qt.digia.com/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, 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, Digia gives you certain additional +** rights. These rights are described in the Digia 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. +** +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#include <QtCore/QMutex> +#include <QtCore/QWaitCondition> +#include <QtCore/QAnimationDriver> +#include <QtCore/QQueue> + +#include <QtGui/QGuiApplication> +#include <QtGui/QScreen> + +#include <QtQuick/QQuickWindow> +#include <private/qquickwindow_p.h> + +#include <QtQuick/private/qsgrenderer_p.h> + +#include "qsgthreadedrenderloop_p.h" + +/* + Overall design: + + There are two classes here. QSGThreadedRenderLoop and + QSGRenderThread. All communication between the two is based on + event passing and we have a number of custom events. + + In this implementation, the render thread is never blocked and the + GUI thread will initiate a polishAndSync which will block and wait + for the render thread to pick it up and release the block only + after the render thread is done syncing. The reason for this + is: + + 1. Clear blocking paradigm. We only have one real "block" point + (polishAndSync()) and all blocking is initiated by GUI and picked + up by Render at specific times based on events. This makes the + execution deterministic. + + 2. Render does not have to interact with GUI. This is done so that + the render thread can run its own animation system which stays + alive even when the GUI thread is blocked doing i/o, object + instantiation, QPainter-painting or any other non-trivial task. + + --- + + The render loop is active while any window is exposed. All visible + windows are tracked, but only exposed windows are actually added to + the render thread and rendered. That means that if all windows are + obscured, we might end up cleaning up the SG and GL context (if all + windows have disabled persistency). Especially for multiprocess, + low-end systems, this should be quite important. + + */ + +QT_BEGIN_NAMESPACE + + +// #define QSG_RENDER_LOOP_DEBUG +// #define QSG_RENDER_LOOP_DEBUG_FULL +#ifdef QSG_RENDER_LOOP_DEBUG +#define QSG_RENDER_LOOP_DEBUG_BASIC +#endif + +#ifdef QSG_RENDER_LOOP_DEBUG_FULL +#define QSG_RENDER_LOOP_DEBUG_BASIC +#endif + +#if defined (QSG_RENDER_LOOP_DEBUG_FULL) +QElapsedTimer qsgrl_timer; +# define RLDEBUG1(x) qDebug("(%6d) %s : %4d - %s", (int) qsgrl_timer.elapsed(), __FILE__, __LINE__, x); +# define RLDEBUG(x) qDebug("(%6d) %s : %4d - %s", (int) qsgrl_timer.elapsed(), __FILE__, __LINE__, x); +#elif defined (QSG_RENDER_LOOP_DEBUG_BASIC) +QElapsedTimer qsgrl_timer; +# define RLDEBUG1(x) qDebug("(%6d) %s : %4d - %s", (int) qsgrl_timer.elapsed(), __FILE__, __LINE__, x); +# define RLDEBUG(x) +#else +# define RLDEBUG1(x) +# define RLDEBUG(x) +#endif + + +static int get_env_int(const char *name, int defaultValue) +{ + QByteArray content = qgetenv(name); + + bool ok = false; + int value = content.toInt(&ok); + return ok ? value : defaultValue; +} + + +static inline int qsgrl_animation_interval() { + qreal refreshRate = QGuiApplication::primaryScreen()->refreshRate(); + // To work around that some platforms wrongfully return 0 or something + // bogus for refreshrate + if (refreshRate < 1) + return 16; + return int(1000 / refreshRate); +} + + +#ifndef QSG_NO_WINDOW_TIMING +static bool qquick_window_timing = !qgetenv("QML_WINDOW_TIMING").isEmpty(); +static QTime threadTimer; +static int syncTime; +static int renderTime; +static int sinceLastTime; +#endif + +extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); + +// RL: Render Loop +// RT: Render Thread + +// Passed from the RL to the RT when a window is rendeirng on screen +// and should be added to the render loop. +const QEvent::Type WM_Expose = QEvent::Type(QEvent::User + 1); + +// Passed from the RL to the RT when a window is removed obscured and +// should be removed from the render loop. +const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 2); + +// Passed from the RL to itself to initiate a polishAndSync() call. +const QEvent::Type WM_LockAndSync = QEvent::Type(QEvent::User + 3); + +// Passed from the RL to RT when GUI has been locked, waiting for sync +// (updatePaintNode()) +const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 4); + +// Passed by the RT to itself to trigger another render pass. This is +// typically a result of QQuickWindow::update(). +const QEvent::Type WM_RequestRepaint = QEvent::Type(QEvent::User + 5); + +// Passed by the RL to the RT when a window has changed size. +const QEvent::Type WM_Resize = QEvent::Type(QEvent::User + 6); + +// Passed by the RL to the RT to free up maybe release SG and GL contexts +// if no windows are rendering. +const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 7); + +// Passed by the RL to the RL when maybeUpdate is called on the RT to +// just replay the maybeUpdate later. This typically happens when +// updatePaintNode() results in a call to QQuickItem::update(). +const QEvent::Type WM_UpdateLater = QEvent::Type(QEvent::User + 8); + +// Passed by the RL to the RT when a QQuickWindow::grabWindow() is +// called. +const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 9); + +template <typename T> T *windowFor(const QList<T> list, QQuickWindow *window) +{ + for (int i=0; i<list.size(); ++i) { + const T &t = list.at(i); + if (t.window == window) + return const_cast<T *>(&t); + } + return 0; +} + + +class WMWindowEvent : public QEvent +{ +public: + WMWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { } + QQuickWindow *window; +}; + +class WMTryReleaseEvent : public WMWindowEvent +{ +public: + WMTryReleaseEvent(QQuickWindow *win, bool destroy) + : WMWindowEvent(win, WM_TryRelease) + , inDestructor(destroy) + {} + + bool inDestructor; +}; + +class WMResizeEvent : public WMWindowEvent +{ +public: + WMResizeEvent(QQuickWindow *c, const QSize &s) : WMWindowEvent(c, WM_Resize), size(s) { } + QSize size; +}; + + +class WMExposeEvent : public WMWindowEvent +{ +public: + WMExposeEvent(QQuickWindow *c) : WMWindowEvent(c, WM_Expose), size(c->size()) { } + QSize size; +}; + + +class WMGrabEvent : public WMWindowEvent +{ +public: + WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {} + QImage *image; +}; + + +class QSGRenderThreadEventQueue : public QQueue<QEvent *> +{ +public: + QSGRenderThreadEventQueue() + : waiting(false) + { + } + + void addEvent(QEvent *e) { + mutex.lock(); + enqueue(e); + if (waiting) + condition.wakeOne(); + mutex.unlock(); + } + + QEvent *takeEvent(bool wait) { + mutex.lock(); + if (size() == 0 && wait) { + waiting = true; + condition.wait(&mutex); + waiting = false; + } + QEvent *e = dequeue(); + mutex.unlock(); + return e; + } + + bool hasMoreEvents() { + mutex.lock(); + bool has = !isEmpty(); + mutex.unlock(); + return has; + } + +private: + QMutex mutex; + QWaitCondition condition; + bool waiting; +}; + + +class QSGRenderThread : public QThread +{ + Q_OBJECT +public: + + QSGRenderThread(QSGThreadedRenderLoop *w) + : wm(w) + , gl(0) + , sg(QSGContext::createDefaultContext()) + , pendingUpdate(0) + , sleeping(false) + , syncResultedInChanges(false) + , guiIsLocked(false) + , shouldExit(false) + , stopEventProcessing(false) + { + sg->moveToThread(this); + vsyncDelta = qsgrl_animation_interval(); + } + + + void invalidateOpenGL(QQuickWindow *window, bool inDestructor); + void initializeOpenGL(); + + bool event(QEvent *); + void run(); + + void syncAndRender(); + void sync(); + + void requestRepaint() + { + if (sleeping) + stopEventProcessing = true; + if (m_windows.size() > 0) + pendingUpdate |= RepaintRequest; + } + + void processEventsAndWaitForMore(); + void processEvents(); + void postEvent(QEvent *e); + +public slots: + void sceneGraphChanged() { + RLDEBUG(" Render: sceneGraphChanged()"); + syncResultedInChanges = true; + } + +public: + enum UpdateRequest { + SyncRequest = 0x01, + RepaintRequest = 0x02 + }; + + QSGThreadedRenderLoop *wm; + QOpenGLContext *gl; + QSGContext *sg; + + QEventLoop eventLoop; + + uint pendingUpdate : 2; + uint sleeping : 1; + uint syncResultedInChanges : 1; + + volatile bool guiIsLocked; + volatile bool shouldExit; + + float vsyncDelta; + + QMutex mutex; + QWaitCondition waitCondition; + + QElapsedTimer m_timer; + + struct Window { + QQuickWindow *window; + QSize size; + }; + QList<Window> m_windows; + + // Local event queue stuff... + bool stopEventProcessing; + QSGRenderThreadEventQueue eventQueue; +}; + +bool QSGRenderThread::event(QEvent *e) +{ + switch ((int) e->type()) { + + case WM_Expose: { + RLDEBUG1(" Render: WM_Expose"); + WMExposeEvent *se = static_cast<WMExposeEvent *>(e); + + pendingUpdate |= RepaintRequest; + + if (windowFor(m_windows, se->window)) { + RLDEBUG1(" Render: - window already added..."); + return true; + } + + Window window; + window.window = se->window; + window.size = se->size; + m_windows << window; + return true; } + + case WM_Obscure: { + RLDEBUG1(" Render: WM_Obscure"); + WMWindowEvent *ce = static_cast<WMWindowEvent *>(e); + for (int i=0; i<m_windows.size(); ++i) { + if (m_windows.at(i).window == ce->window) { + RLDEBUG1(" Render: - removed one..."); + m_windows.removeAt(i); + break; + } + } + + if (sleeping && m_windows.size()) + stopEventProcessing = true; + + return true; } + + case WM_RequestSync: + RLDEBUG(" Render: WM_RequestSync"); + if (sleeping) + stopEventProcessing = true; + if (m_windows.size() > 0) + pendingUpdate |= SyncRequest; + return true; + + case WM_Resize: { + RLDEBUG(" Render: WM_Resize"); + WMResizeEvent *re = static_cast<WMResizeEvent *>(e); + Window *w = windowFor(m_windows, re->window); + w->size = re->size; + // No need to wake up here as we will get a sync shortly.. (see QSGThreadedRenderLoop::resize()); + return true; } + + case WM_TryRelease: + RLDEBUG1(" Render: WM_TryRelease"); + mutex.lock(); + if (m_windows.size() == 0) { + WMTryReleaseEvent *wme = static_cast<WMTryReleaseEvent *>(e); + RLDEBUG1(" Render: - setting exit flag and invalidating GL"); + invalidateOpenGL(wme->window, wme->inDestructor); + shouldExit = !gl; + if (sleeping) + stopEventProcessing = true; + } else { + RLDEBUG1(" Render: - not releasing anything because we have active windows..."); + } + waitCondition.wakeOne(); + mutex.unlock(); + return true; + + case WM_Grab: { + RLDEBUG1(" Render: WM_Grab"); + WMGrabEvent *ce = static_cast<WMGrabEvent *>(e); + Window *w = windowFor(m_windows, ce->window); + mutex.lock(); + if (w) { + gl->makeCurrent(ce->window); + + RLDEBUG1(" Render: - syncing scene graph"); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(w->window); + d->syncSceneGraph(); + + RLDEBUG1(" Render: - rendering scene graph"); + QQuickWindowPrivate::get(ce->window)->renderSceneGraph(w->size); + + RLDEBUG1(" Render: - grabbing result..."); + *ce->image = qt_gl_read_framebuffer(w->size, false, false); + } + RLDEBUG1(" Render: - waking gui to handle grab result"); + waitCondition.wakeOne(); + mutex.unlock(); + return true; + } + + case WM_RequestRepaint: + // When GUI posts this event, it is followed by a polishAndSync, so we mustn't + // exit the event loop yet. + pendingUpdate |= RepaintRequest; + break; + + default: + break; + } + return QThread::event(e); +} + +void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor) +{ + RLDEBUG1(" Render: invalidateOpenGL()"); + + if (!gl) + return; + + if (!window) { + qWarning("QSGThreadedRenderLoop:QSGRenderThread: no window to make current..."); + return; + } + + + bool persistentGL = false; + bool persistentSG = false; + + // GUI is locked so accessing the wm and window here is safe + for (int i=0; i<wm->m_windows.size(); ++i) { + const QSGThreadedRenderLoop::Window &w = wm->m_windows.at(i); + if (!inDestructor || w.window != window) { + persistentSG |= w.window->isPersistentSceneGraph(); + persistentGL |= w.window->isPersistentOpenGLContext(); + } + } + + gl->makeCurrent(window); + + // The canvas nodes must be cleanded up regardless if we are in the destructor.. + if (!persistentSG || inDestructor) { + QQuickWindowPrivate *dd = QQuickWindowPrivate::get(window); + dd->cleanupNodesOnShutdown(); + } + + // We're not doing any cleanup in this case... + if (persistentSG) { + RLDEBUG1(" Render: - persistent SG, avoiding cleanup"); + return; + } + + sg->invalidate(); + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); + gl->doneCurrent(); + RLDEBUG1(" Render: - invalidated scenegraph.."); + + if (!persistentGL) { + delete gl; + gl = 0; + RLDEBUG1(" Render: - invalidated OpenGL"); + } else { + RLDEBUG1(" Render: - persistent GL, avoiding cleanup"); + } +} + +/*! + Enters the mutex lock to make sure GUI is blocking and performs + sync, then wakes GUI. + */ +void QSGRenderThread::sync() +{ + RLDEBUG(" Render: sync()"); + mutex.lock(); + + Q_ASSERT_X(guiIsLocked, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked..."); + pendingUpdate = 0; + + for (int i=0; i<m_windows.size(); ++i) { + Window &w = const_cast<Window &>(m_windows.at(i)); + if (w.size.width() == 0 || w.size.height() == 0) { + RLDEBUG(" Render: - window has bad size, waiting..."); + continue; + } + gl->makeCurrent(w.window); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(w.window); + bool hadRenderer = d->renderer != 0; + d->syncSceneGraph(); + if (!hadRenderer && d->renderer) { + RLDEBUG(" Render: - renderer was created, hooking up changed signal"); + syncResultedInChanges = true; + connect(d->renderer, SIGNAL(sceneGraphChanged()), this, SLOT(sceneGraphChanged()), Qt::DirectConnection); + } + } + + RLDEBUG(" Render: - unlocking after sync"); + + waitCondition.wakeOne(); + mutex.unlock(); + + QCoreApplication::sendPostedEvents(0, QEvent::DeferredDelete); +} + + +void QSGRenderThread::syncAndRender() +{ +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + sinceLastTime = threadTimer.restart(); +#endif + QElapsedTimer waitTimer; + waitTimer.start(); + + RLDEBUG(" Render: syncAndRender()"); + + syncResultedInChanges = false; + + bool repaintRequested = pendingUpdate & RepaintRequest; + + if (pendingUpdate & SyncRequest) { + RLDEBUG(" Render: - update pending, doing sync"); + sync(); + } + + if (!syncResultedInChanges && !(repaintRequested)) { + RLDEBUG(" Render: - no changes, rendering aborted"); + int waitTime = vsyncDelta - (int) waitTimer.elapsed(); + if (waitTime > 0) + msleep(waitTime); + return; + } + +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + syncTime = threadTimer.elapsed(); +#endif + RLDEBUG(" Render: - rendering starting"); + + for (int i=0; i<m_windows.size(); ++i) { + Window &w = const_cast<Window &>(m_windows.at(i)); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(w.window); + if (!d->renderer || w.size.width() == 0 || w.size.height() == 0) { + RLDEBUG(" Render: - Window not yet ready, skipping render..."); + continue; + } + gl->makeCurrent(w.window); + d->renderSceneGraph(w.size); +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing && i == 0) + renderTime = threadTimer.elapsed(); +#endif + gl->swapBuffers(w.window); + d->fireFrameSwapped(); + } + RLDEBUG(" Render: - rendering done"); + +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + qDebug("window Time: sinceLast=%d, sync=%d, first render=%d, after final swap=%d", + sinceLastTime, + syncTime, + renderTime - syncTime, + threadTimer.elapsed() - renderTime); +#endif +} + + + +void QSGRenderThread::postEvent(QEvent *e) +{ + eventQueue.addEvent(e); +} + + + +void QSGRenderThread::processEvents() +{ + RLDEBUG(" Render: processEvents()"); + while (eventQueue.hasMoreEvents()) { + QEvent *e = eventQueue.takeEvent(false); + event(e); + delete e; + } + RLDEBUG(" Render: - done with processEvents()"); +} + +void QSGRenderThread::processEventsAndWaitForMore() +{ + RLDEBUG(" Render: processEventsAndWaitForMore()"); + stopEventProcessing = false; + while (!stopEventProcessing) { + QEvent *e = eventQueue.takeEvent(true); + event(e); + delete e; + } + RLDEBUG(" Render: - done with processEventsAndWaitForMore()"); +} + +void QSGRenderThread::run() +{ + RLDEBUG1(" Render: run()"); + while (!shouldExit) { + + if (m_windows.size() > 0) { + if (!sg->isReady()) { + gl->makeCurrent(m_windows.at(0).window); + sg->initialize(gl); + } + syncAndRender(); + } + + processEvents(); + QCoreApplication::processEvents(); + + if (!shouldExit + && (pendingUpdate == 0 || m_windows.size() == 0)) { + RLDEBUG(" Render: enter event loop (going to sleep)"); + sleeping = true; + processEventsAndWaitForMore(); + sleeping = false; + } + + } + + Q_ASSERT_X(!gl, "QSGRenderThread::run()", "The OpenGL context should be cleaned up before exiting the render thread..."); + + RLDEBUG1(" Render: run() completed..."); +} + +QSGThreadedRenderLoop::QSGThreadedRenderLoop() + : m_animation_timer(0) + , m_update_timer(0) + , m_sync_triggered_update(false) +{ +#if defined(QSG_RENDER_LOOP_DEBUG_BASIC) || defined (QSG_RENDER_LOOP_DEBUG_FULL) + qsgrl_timer.start(); +#endif + + m_thread = new QSGRenderThread(this); + m_thread->moveToThread(m_thread); + + m_animation_driver = m_thread->sg->createAnimationDriver(this); + + m_exhaust_delay = get_env_int("QML_EXHAUST_DELAY", 5); + + connect(m_animation_driver, SIGNAL(started()), this, SLOT(animationStarted())); + connect(m_animation_driver, SIGNAL(stopped()), this, SLOT(animationStopped())); + + m_animation_driver->install(); + RLDEBUG1("GUI: QSGThreadedRenderLoop() created"); +} + +void QSGThreadedRenderLoop::maybePostPolishRequest() +{ + if (m_update_timer == 0) { + RLDEBUG("GUI: - posting update"); + m_update_timer = startTimer(m_exhaust_delay, Qt::PreciseTimer); + } +} + +QAnimationDriver *QSGThreadedRenderLoop::animationDriver() const +{ + return m_animation_driver; +} + +QSGContext *QSGThreadedRenderLoop::sceneGraphContext() const +{ + return m_thread->sg; +} + +bool QSGThreadedRenderLoop::anyoneShowing() +{ + for (int i=0; i<m_windows.size(); ++i) { + QQuickWindow *c = m_windows.at(i).window; + if (c->isVisible() && c->isExposed()) + return true; + } + return false; +} + +void QSGThreadedRenderLoop::animationStarted() +{ + RLDEBUG("GUI: animationStarted()"); + if (!anyoneShowing() && m_animation_timer == 0) + m_animation_timer = startTimer(qsgrl_animation_interval()); + maybePostPolishRequest(); +} + +void QSGThreadedRenderLoop::animationStopped() +{ + RLDEBUG("GUI: animationStopped()"); + if (!anyoneShowing()) { + killTimer(m_animation_timer); + m_animation_timer = 0; + } +} + + + +/* + Adds this window to the list of tracked windowes in this window + manager. show() does not trigger rendering to start, that happens + in expose. + */ + +void QSGThreadedRenderLoop::show(QQuickWindow *window) +{ + RLDEBUG1("GUI: show()"); + + Window win; + win.window = window; + m_windows << win; +} + + + +/* + Removes this window from the list of tracked windowes in this + window manager. hide() will trigger obscure, which in turn will + stop rendering. + */ + +void QSGThreadedRenderLoop::hide(QQuickWindow *window) +{ + RLDEBUG1("GUI: hide()"); + + if (window->isExposed()) + handleObscurity(window); + + releaseResources(window); + + for (int i=0; i<m_windows.size(); ++i) { + if (m_windows.at(i).window == window) { + m_windows.removeAt(i); + break; + } + } +} + + +/*! + If the window is first hide it, then perform a complete cleanup + with releaseResources which will take down the GL context and + exit the rendering thread. + */ +void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window) +{ + RLDEBUG1("GUI: windowDestroyed()"); + + if (window->isVisible()) + hide(window); + releaseResources(window, true); + + RLDEBUG1("GUI: - done with windowDestroyed()"); +} + + +void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window) +{ + RLDEBUG1("GUI: exposureChanged()"); + if (windowFor(m_windows, window) == 0) + return; + + if (window->isExposed()) { + handleExposure(window); + } else { + handleObscurity(window); + } +} + + +/*! + Will post an event to the render thread that this window should + start to render. + */ +void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) +{ + RLDEBUG1("GUI: handleExposure"); + + // Because we are going to bind a GL context to it, make sure it + // is created. + if (!window->handle()) + window->create(); + + m_thread->postEvent(new WMExposeEvent(window)); + + // Start render thread if it is not running + if (!m_thread->isRunning()) { + m_thread->shouldExit = false; + + RLDEBUG1("GUI: - starting render thread..."); + + if (!m_thread->gl) { + QOpenGLContext *ctx = new QOpenGLContext(); + ctx->setFormat(window->requestedFormat()); + ctx->create(); + ctx->moveToThread(m_thread); + m_thread->gl = ctx; + } + + m_thread->start(); + + } else { + RLDEBUG1("GUI: - render thread already running"); + } + + polishAndSync(); + + // Kill non-visual animation timer if it is running + if (m_animation_timer) { + killTimer(m_animation_timer); + m_animation_timer = 0; + } + +} + +/*! + This function posts an event to the render thread to remove the window + from the list of windowses to render. + + It also starts up the non-vsync animation tick if no more windows + are showing. + */ +void QSGThreadedRenderLoop::handleObscurity(QQuickWindow *window) +{ + RLDEBUG1("GUI: handleObscurity"); + if (m_thread->isRunning()) + m_thread->postEvent(new WMWindowEvent(window, WM_Obscure)); + + if (!anyoneShowing() && m_animation_driver->isRunning() && m_animation_timer == 0) { + m_animation_timer = startTimer(qsgrl_animation_interval()); + } +} + + +/*! + Called whenever the QML scene has changed. Will post an event to + ourselves that a sync is needed. + */ +void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window) +{ + Q_ASSERT_X(QThread::currentThread() == QCoreApplication::instance()->thread() || m_thread->guiIsLocked, + "QQuickItem::update()", + "Function can only be called from GUI thread or during QQuickItem::updatePaintNode()"); + + RLDEBUG("GUI: maybeUpdate..."); + Window *w = windowFor(m_windows, window); + if (!w || !m_thread->isRunning()) { + return; + } + + // Call this function from the Gui thread later as startTimer cannot be + // called from the render thread. + if (QThread::currentThread() == m_thread) { + RLDEBUG("GUI: - on render thread, will update later.."); + m_sync_triggered_update = true; + return; + } + + maybePostPolishRequest(); +} + +/*! + Called when the QQuickWindow should be explicitly repainted. This function + can also be called on the render thread when the GUI thread is blocked to + keep render thread animations alive. + */ +void QSGThreadedRenderLoop::update(QQuickWindow *window) +{ + if (QThread::currentThread() == m_thread) { + RLDEBUG("Gui: update called on render thread"); + m_thread->requestRepaint(); + return; + } + + RLDEBUG("Gui: update called"); + m_thread->postEvent(new QEvent(WM_RequestRepaint)); + maybeUpdate(window); +} + + + +/*! + * Release resources will post an event to the render thread to + * free up the SG and GL resources and exists the render thread. + */ +void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window, bool inDestructor) +{ + RLDEBUG1("GUI: releaseResources requested..."); + + m_thread->mutex.lock(); + if (m_thread->isRunning() && !m_thread->shouldExit) { + RLDEBUG1("GUI: - posting release request to render thread"); + m_thread->postEvent(new WMTryReleaseEvent(window, inDestructor)); + m_thread->waitCondition.wait(&m_thread->mutex); + } + m_thread->mutex.unlock(); +} + + + +void QSGThreadedRenderLoop::polishAndSync() +{ + if (!anyoneShowing()) + return; + + RLDEBUG("GUI: polishAndSync()"); + +#ifndef QSG_NO_WINDOW_TIMING + QElapsedTimer timer; + int polishTime = 0; + int waitTime = 0; + int syncTime; + if (qquick_window_timing) + timer.start(); +#endif + + // Polish as the last thing we do before we allow the sync to take place + for (int i=0; i<m_windows.size(); ++i) { + const Window &w = m_windows.at(i); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(w.window); + d->polishItems(); + } +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + polishTime = timer.elapsed(); +#endif + + m_sync_triggered_update = false; + + RLDEBUG("GUI: - lock for sync..."); + m_thread->mutex.lock(); + m_thread->guiIsLocked = true; + m_thread->postEvent(new QEvent(WM_RequestSync)); + + RLDEBUG("GUI: - wait for sync..."); +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + waitTime = timer.elapsed(); +#endif + m_thread->waitCondition.wait(&m_thread->mutex); + m_thread->guiIsLocked = false; + m_thread->mutex.unlock(); + RLDEBUG("GUI: - unlocked after sync..."); + +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + syncTime = timer.elapsed(); +#endif + + killTimer(m_update_timer); + m_update_timer = 0; + + if (m_animation_driver->isRunning()) { + RLDEBUG("GUI: - animations advancing"); + m_animation_driver->advance(); + RLDEBUG("GUI: - animations done"); + + // We need to trigger another sync to keep animations running... + maybePostPolishRequest(); + } else if (m_sync_triggered_update) { + maybePostPolishRequest(); + } + +#ifndef QSG_NO_WINDOW_TIMING + if (qquick_window_timing) + qDebug(" - polish=%d, wait=%d, sync=%d -- animations=%d", polishTime, waitTime - polishTime, syncTime - waitTime, int(timer.elapsed() - syncTime)); +#endif +} + +bool QSGThreadedRenderLoop::event(QEvent *e) +{ + switch ((int) e->type()) { + + case QEvent::Timer: + if (static_cast<QTimerEvent *>(e)->timerId() == m_animation_timer) { + RLDEBUG("GUI: QEvent::Timer -> non-visual animation"); + m_animation_driver->advance(); + } else if (static_cast<QTimerEvent *>(e)->timerId() == m_update_timer) { + RLDEBUG("GUI: QEvent::Timer -> Polish & Sync"); + polishAndSync(); + } + return true; + + default: + break; + } + + return QObject::event(e); +} + + + +/* + Locks down GUI and performs a grab the scene graph, then returns the result. + + Since the QML scene could have changed since the last time it was rendered, + we need to polish and sync the scene graph. This might seem superfluous, but + - QML changes could have triggered deleteLater() which could have removed + textures or other objects from the scene graph, causing render to crash. + - Autotests rely on grab(), setProperty(), grab(), compare behavior. + */ + +QImage QSGThreadedRenderLoop::grab(QQuickWindow *window) +{ + RLDEBUG("GUI: grab"); + if (!m_thread->isRunning()) + return QImage(); + + if (!window->handle()) + window->create(); + + RLDEBUG1("GUI: - polishing items..."); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(window); + d->polishItems(); + + QImage result; + m_thread->mutex.lock(); + RLDEBUG1("GUI: - locking, posting grab event"); + m_thread->postEvent(new WMGrabEvent(window, &result)); + m_thread->waitCondition.wait(&m_thread->mutex); + RLDEBUG1("GUI: - locking, grab done, unlocking"); + m_thread->mutex.unlock(); + + RLDEBUG1("Gui: - grab complete"); + + return result; +} + +/* + Notify the render thread that the window is now a new size. Then + locks GUI until render has adapted. + */ + +void QSGThreadedRenderLoop::resize(QQuickWindow *w, const QSize &size) +{ + RLDEBUG1("GUI: resize"); + + if (!m_thread->isRunning() || !m_windows.size() || !w->isExposed() || windowFor(m_windows, w) == 0) { + return; + } + + if (size.width() == 0 || size.height() == 0) + return; + + RLDEBUG("GUI: - posting resize event..."); + m_thread->postEvent(new WMResizeEvent(w, size)); + + polishAndSync(); +} + +#include "qsgthreadedrenderloop.moc" + +QT_END_NAMESPACE |