From aef7dad91640bb495b655012dbc7214a79195dff Mon Sep 17 00:00:00 2001 From: Gunnar Sletta Date: Fri, 15 Feb 2013 15:35:04 +0100 Subject: [PATCH] Improved animations in the new render loop MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit To advance animations in line with vsync, we used a dedicated event from the rendering thread which we fired immediately after sync. This is a bit elaborate as we know in Gui when sync is complete and we can just animate there and then. This means we can remove all animation logic from the rendering thread, making it simpler. I also updated the syncAndRender pass so that it does not render anything if the scene graph reported no changes during the sync pass. This will prevent non-visual animations and property updates from triggering render passes which will save quite a few cycles. Change-Id: I62bb5484f0673f99abe726fca5a9b424f6b0a317 Reviewed-by: Eskil Abrahamsen Blomfeldt Reviewed-by: Samuel Rødal --- src/quick/scenegraph/qsgthreadedrenderloop.cpp | 182 +++++++++----------- src/quick/scenegraph/qsgthreadedrenderloop_p.h | 5 +- tests/auto/quick/qquickwindow/data/focus.qml | 4 + .../quick/qquickwindow/data/ownershipRootItem.qml | 2 +- tests/auto/quick/qquickwindow/tst_qquickwindow.cpp | 74 ++++++++ 5 files changed, 168 insertions(+), 99 deletions(-) diff --git a/src/quick/scenegraph/qsgthreadedrenderloop.cpp b/src/quick/scenegraph/qsgthreadedrenderloop.cpp index b11da22..eebcad7 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop.cpp +++ b/src/quick/scenegraph/qsgthreadedrenderloop.cpp @@ -103,10 +103,12 @@ QT_BEGIN_NAMESPACE #endif #if defined (QSG_RENDER_LOOP_DEBUG_FULL) -# define RLDEBUG1(x) qDebug("%s : %4d - %s", __FILE__, __LINE__, x); -# define RLDEBUG(x) qDebug("%s : %4d - %s", __FILE__, __LINE__, x); +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) -# define RLDEBUG1(x) qDebug("%s : %4d - %s", __FILE__, __LINE__, x); +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) @@ -182,15 +184,6 @@ const QEvent::Type WM_UpdateLater = QEvent::Type(QEvent::User + 8); // called. const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 9); -// Passed by the RT to the RL to trigger animations to be advanced. -const QEvent::Type WM_AdvanceAnimations = QEvent::Type(QEvent::User + 10); - -// Passed by the RT to the RL when animations start -const QEvent::Type WM_AnimationsStarted = QEvent::Type(QEvent::User + 11); - -// Passed by the RT to the RL when animations stop -const QEvent::Type WM_AnimationsStopped = QEvent::Type(QEvent::User + 12); - template T *windowFor(const QList list, QQuickWindow *window) { for (int i=0; imoveToThread(this); + vsyncDelta = QGuiApplication::primaryScreen()->refreshRate(); } @@ -330,16 +322,9 @@ public: void postEvent(QEvent *e); public slots: - void animationStarted() { - RLDEBUG(" Render: animationStarted()"); - animationRunning = true; - if (sleeping) - stopEventProcessing = true; - } - - void animationStopped() { - RLDEBUG(" Render: animationStopped()"); - animationRunning = false; + void sceneGraphChanged() { + RLDEBUG(" Render: sceneGraphChanged()"); + syncResultedInChanges = true; } public: @@ -356,13 +341,12 @@ public: uint pendingUpdate : 2; uint sleeping : 1; - uint animationRunning : 1; + uint syncResultedInChanges : 1; volatile bool guiIsLocked; volatile bool shouldExit; - volatile bool allowMainThreadProcessing; - volatile int animationRequestsPending; + float vsyncDelta; QMutex mutex; QWaitCondition waitCondition; @@ -472,12 +456,10 @@ bool QSGRenderThread::event(QEvent *e) return true; } - case WM_AnimationsStarted: - animationStarted(); - break; - - case WM_AnimationsStopped: - animationStopped(); + 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: @@ -590,7 +572,13 @@ void QSGRenderThread::sync() } 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"); @@ -608,21 +596,28 @@ void QSGRenderThread::syncAndRender() if (qquick_window_timing) sinceLastTime = threadTimer.restart(); #endif + QElapsedTimer waitTimer; + waitTimer.start(); + RLDEBUG(" Render: syncAndRender()"); - // This animate request will get there after the sync - if (animationRunning && animationRequestsPending < 2) { - RLDEBUG(" Render: - posting animate to gui.."); - ++animationRequestsPending; - QCoreApplication::postEvent(wm, new QEvent(WM_AdvanceAnimations)); + 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(); @@ -668,25 +663,25 @@ void QSGRenderThread::postEvent(QEvent *e) void QSGRenderThread::processEvents() { - RLDEBUG1(" Render: processEvents()"); + RLDEBUG(" Render: processEvents()"); while (eventQueue.hasMoreEvents()) { QEvent *e = eventQueue.takeEvent(false); event(e); delete e; } - RLDEBUG1(" Render: - done with processEvents()"); + RLDEBUG(" Render: - done with processEvents()"); } void QSGRenderThread::processEventsAndWaitForMore() { - RLDEBUG1(" Render: processEventsAndWaitForMore()"); + RLDEBUG(" Render: processEventsAndWaitForMore()"); stopEventProcessing = false; while (!stopEventProcessing) { QEvent *e = eventQueue.takeEvent(true); event(e); delete e; } - RLDEBUG1(" Render: - done with processEventsAndWaitForMore()"); + RLDEBUG(" Render: - done with processEventsAndWaitForMore()"); } void QSGRenderThread::run() @@ -706,7 +701,7 @@ void QSGRenderThread::run() QCoreApplication::processEvents(); if (!shouldExit - && ((!animationRunning && pendingUpdate == 0) || m_windows.size() == 0)) { + && (pendingUpdate == 0 || m_windows.size() == 0)) { RLDEBUG(" Render: enter event loop (going to sleep)"); sleeping = true; processEventsAndWaitForMore(); @@ -723,7 +718,12 @@ void QSGRenderThread::run() 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); @@ -738,6 +738,14 @@ QSGThreadedRenderLoop::QSGThreadedRenderLoop() 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; @@ -763,7 +771,7 @@ void QSGThreadedRenderLoop::animationStarted() RLDEBUG("GUI: animationStarted()"); if (!anyoneShowing() && m_animation_timer == 0) m_animation_timer = startTimer(qsgrl_animation_interval()); - m_thread->postEvent(new QEvent(WM_AnimationsStarted)); + maybePostPolishRequest(); } void QSGThreadedRenderLoop::animationStopped() @@ -773,7 +781,6 @@ void QSGThreadedRenderLoop::animationStopped() killTimer(m_animation_timer); m_animation_timer = 0; } - m_thread->postEvent(new QEvent(WM_AnimationsStopped)); } @@ -790,7 +797,6 @@ void QSGThreadedRenderLoop::show(QQuickWindow *window) Window win; win.window = window; - win.pendingUpdate = false; m_windows << win; } @@ -869,7 +875,6 @@ void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window) // Start render thread if it is not running if (!m_thread->isRunning()) { m_thread->shouldExit = false; - m_thread->animationRunning = m_animation_driver->isRunning(); RLDEBUG1("GUI: - starting render thread..."); m_thread->start(); @@ -919,27 +924,19 @@ void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window) RLDEBUG("GUI: maybeUpdate..."); Window *w = windowFor(m_windows, window); - if (!w || w->pendingUpdate || !m_thread->isRunning()) { + 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, posting update later"); - QCoreApplication::postEvent(this, new WMWindowEvent(window, WM_UpdateLater)); + RLDEBUG("GUI: - on render thread, will update later.."); + m_sync_triggered_update = true; return; } - - w->pendingUpdate = true; - - if (m_update_timer > 0) { - return; - } - - RLDEBUG("GUI: - posting update"); - m_update_timer = startTimer(m_animation_driver->isRunning() ? m_exhaust_delay : 0, Qt::PreciseTimer); + maybePostPolishRequest(); } /*! @@ -956,6 +953,7 @@ void QSGThreadedRenderLoop::update(QQuickWindow *window) } RLDEBUG("Gui: update called"); + m_thread->postEvent(new QEvent(WM_RequestRepaint)); maybeUpdate(window); } @@ -985,6 +983,8 @@ void QSGThreadedRenderLoop::polishAndSync() if (!anyoneShowing()) return; + RLDEBUG("GUI: polishAndSync()"); + #ifndef QSG_NO_WINDOW_TIMING QElapsedTimer timer; int polishTime = 0; @@ -992,7 +992,7 @@ void QSGThreadedRenderLoop::polishAndSync() if (qquick_window_timing) timer.start(); #endif - RLDEBUG("GUI: polishAndSync()"); + // Polish as the last thing we do before we allow the sync to take place for (int i=0; imutex.lock(); m_thread->guiIsLocked = true; - QEvent *event = new QEvent(WM_RequestSync); + m_thread->postEvent(new QEvent(WM_RequestSync)); - m_thread->postEvent(event); RLDEBUG("GUI: - wait for sync..."); #ifndef QSG_NO_WINDOW_TIMING if (qquick_window_timing) @@ -1026,8 +1022,26 @@ void QSGThreadedRenderLoop::polishAndSync() RLDEBUG("GUI: - unlocked after sync..."); #ifndef QSG_NO_WINDOW_TIMING + int 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", polishTime, waitTime - polishTime, int(timer.elapsed() - waitTime)); + qDebug(" - polish=%d, wait=%d, sync=%d -- animations=%d", polishTime, waitTime - polishTime, syncTime - waitTime, int(timer.elapsed() - syncTime)); #endif } @@ -1037,40 +1051,14 @@ bool QSGThreadedRenderLoop::event(QEvent *e) case QEvent::Timer: if (static_cast(e)->timerId() == m_animation_timer) { - RLDEBUG("Gui: QEvent::Timer -> non-visual animation"); + RLDEBUG("GUI: QEvent::Timer -> non-visual animation"); m_animation_driver->advance(); } else if (static_cast(e)->timerId() == m_update_timer) { - RLDEBUG("Gui: QEvent::Timer -> polishAndSync()"); - killTimer(m_update_timer); - m_update_timer = 0; + RLDEBUG("GUI: QEvent::Timer -> Polish & Sync"); polishAndSync(); } return true; - case WM_UpdateLater: { - QQuickWindow *window = static_cast(e)->window; - // The window might have gone away... - if (windowFor(m_windows, window)) - maybeUpdate(window); - return true; } - - case WM_AdvanceAnimations: - --m_thread->animationRequestsPending; - RLDEBUG("GUI: WM_AdvanceAnimations"); - if (m_animation_driver->isRunning()) { -#ifdef QQUICK_CANVAS_TIMING - QElapsedTimer timer; - timer.start(); -#endif - m_animation_driver->advance(); - RLDEBUG("GUI: - animations advanced.."); -#ifdef QQUICK_CANVAS_TIMING - if (qquick_canvas_timing) - qDebug(" - animation: %d", (int) timer.elapsed()); -#endif - } - return true; - default: break; } diff --git a/src/quick/scenegraph/qsgthreadedrenderloop_p.h b/src/quick/scenegraph/qsgthreadedrenderloop_p.h index 4c297f5..63b2b44 100644 --- a/src/quick/scenegraph/qsgthreadedrenderloop_p.h +++ b/src/quick/scenegraph/qsgthreadedrenderloop_p.h @@ -96,13 +96,14 @@ private: bool anyoneShowing(); void initialize(); + void maybePostPolishRequest(); + void waitForReleaseComplete(); void polishAndSync(); struct Window { QQuickWindow *window; - uint pendingUpdate : 1; }; QSGRenderThread *m_thread; @@ -112,6 +113,8 @@ private: int m_animation_timer; int m_update_timer; int m_exhaust_delay; + + bool m_sync_triggered_update; }; diff --git a/tests/auto/quick/qquickwindow/data/focus.qml b/tests/auto/quick/qquickwindow/data/focus.qml index 901f2fc..899b999 100644 --- a/tests/auto/quick/qquickwindow/data/focus.qml +++ b/tests/auto/quick/qquickwindow/data/focus.qml @@ -2,6 +2,10 @@ import QtQuick 2.0 import QtQuick.Window 2.0 as Window Window.Window { + + width: 400 + height: 300 + Item { objectName: "item1" } diff --git a/tests/auto/quick/qquickwindow/data/ownershipRootItem.qml b/tests/auto/quick/qquickwindow/data/ownershipRootItem.qml index 955304e..03400ba 100644 --- a/tests/auto/quick/qquickwindow/data/ownershipRootItem.qml +++ b/tests/auto/quick/qquickwindow/data/ownershipRootItem.qml @@ -10,6 +10,6 @@ Window.Window { RootItemAccessor { id:accessor objectName:"accessor" - Component.onCompleted:accessor.rootItem(); + Component.onCompleted:accessor.contentItem(); } } diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp index 3e907a5..858653a 100644 --- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp +++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp @@ -284,8 +284,11 @@ private slots: void constantUpdates(); + void constantUpdatesOnWindow_data(); + void constantUpdatesOnWindow(); void mouseFiltering(); void headless(); + void noUpdateWhenNothingChanges(); void touchEvent_basic(); void touchEvent_propagation(); @@ -336,6 +339,55 @@ void tst_qquickwindow::constantUpdates() QTRY_VERIFY(item.iterations > 60); } +void tst_qquickwindow::constantUpdatesOnWindow_data() +{ + QTest::addColumn("blockedGui"); + QTest::addColumn("signal"); + + QQuickWindow window; + window.setGeometry(100, 100, 300, 200); + window.show(); + QTest::qWaitForWindowExposed(&window); + bool threaded = window.openglContext()->thread() != QGuiApplication::instance()->thread(); + + if (threaded) { + QTest::newRow("blocked, beforeSync") << true << QByteArray(SIGNAL(beforeSynchronizing())); + QTest::newRow("blocked, beforeRender") << true << QByteArray(SIGNAL(beforeRendering())); + QTest::newRow("blocked, afterRender") << true << QByteArray(SIGNAL(afterRendering())); + QTest::newRow("blocked, swapped") << true << QByteArray(SIGNAL(frameSwapped())); + } + QTest::newRow("unblocked, beforeSync") << false << QByteArray(SIGNAL(beforeSynchronizing())); + QTest::newRow("unblocked, beforeRender") << false << QByteArray(SIGNAL(beforeRendering())); + QTest::newRow("unblocked, afterRender") << false << QByteArray(SIGNAL(afterRendering())); + QTest::newRow("unblocked, swapped") << false << QByteArray(SIGNAL(frameSwapped())); +} + +void tst_qquickwindow::constantUpdatesOnWindow() +{ + QFETCH(bool, blockedGui); + QFETCH(QByteArray, signal); + + QQuickWindow window; + window.setGeometry(100, 100, 300, 200); + + connect(&window, signal.constData(), &window, SLOT(update()), Qt::DirectConnection); + window.show(); + QTRY_VERIFY(window.isExposed()); + + QSignalSpy catcher(&window, SIGNAL(frameSwapped())); + if (blockedGui) + QTest::qSleep(1000); + else { + window.update(); + QTest::qWait(1000); + } + window.hide(); + + // We should expect 60, but under loaded conditions we could be skipping + // frames, so don't expect too much. + QVERIFY(catcher.size() > 10); +} + void tst_qquickwindow::touchEvent_basic() { TestTouchItem::clearMousePressCounter(); @@ -991,6 +1043,28 @@ void tst_qquickwindow::headless() QCOMPARE(originalContent, newContent); } +void tst_qquickwindow::noUpdateWhenNothingChanges() +{ + QQuickWindow window; + window.setGeometry(100, 100, 300, 200); + + QQuickRectangle rect(window.contentItem()); + + window.show(); + QTRY_VERIFY(window.isExposed()); + + if (window.openglContext()->thread() == QGuiApplication::instance()->thread()) { + QSKIP("Only threaded renderloop implements this feature"); + return; + } + + QSignalSpy spy(&window, SIGNAL(frameSwapped())); + rect.update(); + QTest::qWait(500); + + QCOMPARE(spy.size(), 0); +} + void tst_qquickwindow::focusObject() { QQmlEngine engine; -- 1.7.2.5