diff options
author | Gunnar Sletta <gunnar.sletta@nokia.com> | 2012-08-10 13:53:40 +0200 |
---|---|---|
committer | Gunnar Sletta <gunnar.sletta@nokia.com> | 2012-08-10 13:59:27 +0200 |
commit | 0e5a9dfd37cb4500196e50be87487eb85e348ef4 (patch) | |
tree | 4825395aa8ef93b2ea3801c26faa5aeef3b07e43 /customcontext | |
parent | ca0510a56828617041f2f41683018ae5d7920b22 (diff) |
Animation system to run on the rendering thread
Initial code dump
Change-Id: I628e9ea14d9984e1d82502f77444313357275d94
Reviewed-by: Gunnar Sletta <gunnar.sletta@nokia.com>
Diffstat (limited to 'customcontext')
-rw-r--r-- | customcontext/animationdriver.cpp | 4 | ||||
-rw-r--r-- | customcontext/customcontext.pro | 1 | ||||
-rw-r--r-- | customcontext/windowmanager.cpp | 235 | ||||
-rw-r--r-- | customcontext/windowmanager.h | 5 |
4 files changed, 179 insertions, 66 deletions
diff --git a/customcontext/animationdriver.cpp b/customcontext/animationdriver.cpp index f1abe9b..0b882db 100644 --- a/customcontext/animationdriver.cpp +++ b/customcontext/animationdriver.cpp @@ -88,7 +88,7 @@ void AnimationDriver::maybeUpdateDelta() qint64 AnimationDriver::elapsed() const { - if (WindowManager::fakeRendering || !isRunning() || m_stable_vsync < -1) + if (!isRunning() || m_stable_vsync < -1) return startTime() + m_timer.elapsed(); else return startTime() + m_current_animation_time; @@ -108,7 +108,7 @@ void AnimationDriver::advance() { maybeUpdateDelta(); - if (WindowManager::fakeRendering || m_stable_vsync < 0) { + if (m_stable_vsync < 0) { m_current_animation_time = m_timer.elapsed(); } else { diff --git a/customcontext/customcontext.pro b/customcontext/customcontext.pro index 432f215..cf0c255 100644 --- a/customcontext/customcontext.pro +++ b/customcontext/customcontext.pro @@ -36,3 +36,4 @@ arm_build { DEFINES += DESKTOP_BUILD } +verbose:DEFINES+=CUSTOMCONTEXT_DEBUG diff --git a/customcontext/windowmanager.cpp b/customcontext/windowmanager.cpp index 5f86b03..fbe97c5 100644 --- a/customcontext/windowmanager.cpp +++ b/customcontext/windowmanager.cpp @@ -71,6 +71,16 @@ # define WMDEBUG(x) #endif + +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; +} + #define QQUICK_WINDOW_TIMING #ifdef QQUICK_WINDOW_TIMING static bool qquick_window_timing = !qgetenv("QML_WINDOW_TIMING").isEmpty(); @@ -80,13 +90,11 @@ static int renderTime; static int sinceLastTime; #endif -bool WindowManager::fakeRendering = false; - extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha); const QEvent::Type WM_Show = QEvent::Type(QEvent::User + 1); const QEvent::Type WM_Hide = QEvent::Type(QEvent::User + 2); -const QEvent::Type WM_SyncAndAdvance = QEvent::Type(QEvent::User + 3); +const QEvent::Type WM_LockAndSync = QEvent::Type(QEvent::User + 3); const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 4); const QEvent::Type WM_NotifyDoneRender = QEvent::Type(QEvent::User + 5); const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 6); @@ -94,7 +102,7 @@ const QEvent::Type WM_ReleaseHandled = QEvent::Type(QEvent::User + 7); const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 8); const QEvent::Type WM_GrabResult = QEvent::Type(QEvent::User + 9); const QEvent::Type WM_EnterWait = QEvent::Type(QEvent::User + 10); -const QEvent::Type WM_Polish = QEvent::Type(QEvent::User + 11); +const QEvent::Type WM_AdvanceAnimations = QEvent::Type(QEvent::User + 11); template <typename T> T *windowFor(const QList<T> list, QQuickWindow *window) { @@ -142,10 +150,20 @@ class RenderThread : public QThread { Q_OBJECT public: + + enum SyncState { + SyncNotSpecified, + SyncPendingInGui, + SyncAcceptedInGui, + SyncAbortedByRender + }; + + RenderThread(WindowManager *w) : wm(w) , gl(0) , sg(QSGContext::createDefaultContext()) + , waitForGuiTime(0) , pendingUpdate(false) , sleeping(false) , animationRunning(false) @@ -153,10 +171,18 @@ public: , shouldExit(false) , allowMainThreadProcessing(true) , inSync(true) + , syncState(SyncNotSpecified) { sg->moveToThread(this); + + waitForGuiTime = get_env_int("QML_RENDERLOOP_WAIT_FOR_GUI_TIME", 5); + +#ifdef CUSTOMCONTEXT_DEBUG + qDebug("CustomContext: setting GUI wait to %d ms", waitForGuiTime); +#endif } + void invalidateOpenGL(QQuickWindow *window); void initializeOpenGL(); @@ -164,6 +190,7 @@ public: void run(); void syncAndRender(); + void sync(); void lockGuiFromRender(QEvent::Type eventType) { @@ -197,6 +224,9 @@ public slots: sceneChanged = true; } + + + public: WindowManager *wm; QOpenGLContext *gl; @@ -204,19 +234,29 @@ public: QEventLoop eventLoop; + int waitForGuiTime; + uint pendingUpdate : 1; uint sleeping : 1; uint animationRunning : 1; uint sceneChanged : 1; + // New canvas has been added, we must perform a full sync in order for + // the renderer to be created and the initial scene graph built. + uint canvasAdded : 1; + volatile bool shouldExit; + volatile bool allowMainThreadProcessing; volatile bool inSync : 1; + volatile SyncState syncState; QMutex mutex; QWaitCondition waitCondition; + QElapsedTimer m_timer; + struct Window { QQuickWindow *window; QSize size; @@ -241,8 +281,10 @@ bool RenderThread::event(QEvent *e) Window window; window.window = se->window; window.size = se->size; + window.sceneChanged = true; m_windows << window; pendingUpdate = true; + canvasAdded = true; if (sleeping) exit(); return true; } @@ -364,6 +406,57 @@ void RenderThread::initializeOpenGL() sg->initialize(gl); } +/*! + * Makes an attemt to lock the GUI thread so we can perform the synchronizaiton + * of the QML scene's state into the scene graph. + * + * We return the number of views that did not trigger any changes. + */ +void RenderThread::sync() +{ + WMDEBUG(" Render: about to lock for sync"); + mutex.lock(); + allowMainThreadProcessing = false; + syncState = SyncPendingInGui; + + WMDEBUG(" Render: posting WM_SyncAndAdvance to GUI"); + QCoreApplication::postEvent(wm, new QEvent(WM_LockAndSync)); + waitCondition.wait(&mutex, canvasAdded ? 99999 : waitForGuiTime); + + // Gui thread did not process the event in time, so abort it... + if (syncState == SyncPendingInGui) { + WMDEBUG(" Render: aborting..."); + syncState = SyncAbortedByRender; + } + pendingUpdate = false; + + if (syncState == SyncAcceptedInGui) { + WMDEBUG(" Render: performing sync"); + inSync = true; + for (int i=0; i<m_windows.size(); ++i) { + Window &w = const_cast<Window &>(m_windows.at(i)); + gl->makeCurrent(w.window); + QQuickWindowPrivate *d = QQuickWindowPrivate::get(w.window); + + bool hasRenderer = d->renderer != 0; + sceneChanged = !hasRenderer || wm->checkAndResetForceUpdate(w.window); + d->syncSceneGraph(); // May trigger sceneWasChanged... + w.sceneChanged |= sceneChanged; + + // If there was no renderer before, this was the first render pass and + // we register for the sceneGraphChanged signal.. + if (!hasRenderer) + connect(d->renderer, SIGNAL(sceneGraphChanged()), this, SLOT(sceneWasChanged())); + + canvasAdded = false; + } + inSync = false; + } + + WMDEBUG(" Render: unlocking after sync"); + unlockGuiFromRender(); +} + void RenderThread::syncAndRender() { @@ -371,41 +464,22 @@ void RenderThread::syncAndRender() if (qquick_window_timing) sinceLastTime = threadTimer.restart(); #endif + WMDEBUG(" Render: Sync & Render") - WMDEBUG(" Render: about to lock"); - lockGuiFromRender(WM_SyncAndAdvance); - pendingUpdate = false; - inSync = true; - WMDEBUG(" Render: performing sync"); - int skippedRenders = 0; - for (int i=0; i<m_windows.size(); ++i) { - Window &w = const_cast<Window &>(m_windows.at(i)); - gl->makeCurrent(w.window); - QQuickWindowPrivate *d = QQuickWindowPrivate::get(w.window); - - bool hasRenderer = d->renderer != 0; - sceneChanged = !hasRenderer || wm->checkAndResetForceUpdate(w.window); - d->syncSceneGraph(); // May trigger sceneWasChanged... - w.sceneChanged = sceneChanged; + bool updateWasPending = pendingUpdate; - if (!sceneChanged) - ++skippedRenders; + if (pendingUpdate) + sync(); - // If there was no renderer before, this was the first render pass and - // we register for the sceneGraphChanged signal.. - if (!hasRenderer) - connect(d->renderer, SIGNAL(sceneGraphChanged()), this, SLOT(sceneWasChanged())); + // Posting animation request to GUI, but only if it is currently alive and + // kicking, otherwise we would pile up events and the animation system + // would spend a lot of time advancing in addition to the predictive times + // being way off. + if (animationRunning && syncState == SyncAcceptedInGui) { + WMDEBUG(" Render: posting animate to gui.."); + QCoreApplication::postEvent(wm, new QEvent(WM_AdvanceAnimations)); } - // When we are skipping renders, animations we are not tracking vsync, so the - // increment in the animation driver will be wrong, causing a drift over time. - // Make this knowledge public so the animation driver can pick it up. - WindowManager::fakeRendering = skippedRenders == m_windows.size(); - - inSync = false; - unlockGuiFromRender(); - WMDEBUG(" Render: sync done, on to rendering.."); - #ifdef QQUICK_WINDOW_TIMING if (qquick_window_timing) syncTime = threadTimer.elapsed(); @@ -414,30 +488,24 @@ void RenderThread::syncAndRender() 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 (w.sceneChanged) { - gl->makeCurrent(w.window); - d->renderSceneGraph(w.size); + gl->makeCurrent(w.window); + d->renderSceneGraph(w.size); #ifdef QQUICK_WINDOW_TIMING - if (qquick_window_timing && i == 0) - renderTime = threadTimer.elapsed(); + if (qquick_window_timing && i == 0) + renderTime = threadTimer.elapsed(); #endif - gl->swapBuffers(w.window); - d->fireFrameSwapped(); - } + gl->swapBuffers(w.window); + d->fireFrameSwapped(); } WMDEBUG(" Render: rendering done"); - if (WindowManager::fakeRendering) - msleep(16); - #ifdef QQUICK_WINDOW_TIMING if (qquick_window_timing) - qDebug("window Time: sinceLast=%d, sync=%d, render=%d, swap=%d%s", + qDebug("window Time: sinceLast=%d, sync=%d, render=%d, swap=%d", sinceLastTime, syncTime, renderTime - syncTime, - threadTimer.elapsed() - renderTime, - WindowManager::fakeRendering ? ", fake frame" : ""); + threadTimer.elapsed() - renderTime); #endif } @@ -691,12 +759,13 @@ void WindowManager::handleObscurity(QQuickWindow *window) */ void WindowManager::maybeUpdate(QQuickWindow *window) { - WMDEBUG("GUI: maybeUpdate called"); - if (m_thread->inSync) { + if (QThread::currentThread() == m_thread) { + WMDEBUG("GUI: maybeUpdate on render thread..."); m_thread->pendingUpdate = true; return; } + WMDEBUG("GUI: maybeUpdate..."); Window *w = windowFor(m_windows, window); if (!w || w->pendingUpdate || !m_thread->isRunning()) return; @@ -716,6 +785,16 @@ void WindowManager::maybeUpdate(QQuickWindow *window) */ void WindowManager::update(QQuickWindow *window) { + if (QThread::currentThread() == m_thread) { + WMDEBUG("Gui: update called on render thread"); + RenderThread::Window *w = windowFor(m_thread->m_windows, window); + if (w) { + w->sceneChanged = true; + m_thread->pendingUpdate = true; + } + return; + } + WMDEBUG("Gui: update called"); maybeUpdate(window); Window *w = windowFor(m_windows, window); @@ -821,7 +900,7 @@ bool WindowManager::event(QEvent *e) wakeAndWait(m_thread); break; - case WM_SyncAndAdvance: { + case WM_LockAndSync: { #ifdef QQUICK_WINDOW_TIMING QElapsedTimer timer; int polishTime; @@ -839,24 +918,62 @@ bool WindowManager::event(QEvent *e) if (qquick_window_timing) polishTime = timer.elapsed(); #endif - wakeAndWait(m_thread); + + bool aborted = false; + m_thread->mutex.lock(); + + if (m_thread->syncState == RenderThread::SyncPendingInGui) { + m_thread->syncState = RenderThread::SyncAcceptedInGui; + + // The lock down the GUI thread and wake render for doing the sync + m_thread->waitCondition.wakeOne(); + WMDEBUG("GUI: woke up render, now waiting..."); + m_thread->waitCondition.wait(&m_thread->mutex); + + WMDEBUG("GUI: done with the waiting..."); + + } else { + WMDEBUG("GUI: sync aborted...") + aborted = true; + } + m_thread->mutex.unlock(); + WMDEBUG("GUI: clearing update flags..."); - // The lock down the GUI thread and wake render for doing the sync renderPassScheduled = false; for (int i=0; i<m_windows.size(); ++i) { m_windows[i].pendingUpdate = false; } + + if (aborted) { + // Force another sync-round since this one is aborted.. + if (m_windows.size()) + maybeUpdate(m_windows.at(0).window); + } + +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + qDebug(" - polish=%d aborted=%s", polishTime, aborted ? "yes" : "no"); +#endif + WMDEBUG("GUI: sync done..."); + return true; + } + + case WM_AdvanceAnimations: + WMDEBUG("GUI: got animate request.."); if (m_animation_driver->isRunning()) { +#ifdef QQUICK_CANVAS_TIMING + QElapsedTimer timer; + timer.start(); +#endif m_animation_driver->advance(); WMDEBUG("GUI: animations advanced.."); - } -#ifdef QQUICK_WINDOW_TIMING - if (qquick_window_timing) - qDebug(" - polish=%d, animation=%d", polishTime, (int) (timer.elapsed() - polishTime)); +#ifdef QQUICK_CANVAS_TIMING + if (qquick_canvas_timing) + qDebug(" - animation: %d", (int) timer.elapsed()); #endif - WMDEBUG("GUI: sync and animate done..."); + } return true; - } + case WM_NotifyDoneRender: WMDEBUG("GUI: render done notification..."); diff --git a/customcontext/windowmanager.h b/customcontext/windowmanager.h index 9066a7d..29e0e1f 100644 --- a/customcontext/windowmanager.h +++ b/customcontext/windowmanager.h @@ -80,11 +80,6 @@ public: void wakeup(); - // Set to true when we are spinning the scene graph render loop to - // drive animations but not really rendering or swapping, only - // sync'ing and animating. - static bool fakeRendering; - public slots: void animationStarted(); void animationStopped(); |