diff options
-rw-r--r-- | src/quick/scenegraph/qsgthreadedrenderloop.cpp | 182 | ||||
-rw-r--r-- | src/quick/scenegraph/qsgthreadedrenderloop_p.h | 5 | ||||
-rw-r--r-- | tests/auto/quick/qquickwindow/data/focus.qml | 4 | ||||
-rw-r--r-- | tests/auto/quick/qquickwindow/data/ownershipRootItem.qml | 2 | ||||
-rw-r--r-- | 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 b11da22268..eebcad72d7 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 <typename T> T *windowFor(const QList<T> list, QQuickWindow *window) { for (int i=0; i<list.size(); ++i) { @@ -297,14 +290,13 @@ public: , sg(QSGContext::createDefaultContext()) , pendingUpdate(0) , sleeping(false) - , animationRunning(false) + , syncResultedInChanges(false) , guiIsLocked(false) , shouldExit(false) - , allowMainThreadProcessing(true) - , animationRequestsPending(0) , stopEventProcessing(false) { sg->moveToThread(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; i<m_windows.size(); ++i) { const Window &w = m_windows.at(i); @@ -1004,17 +1004,13 @@ void QSGThreadedRenderLoop::polishAndSync() polishTime = timer.elapsed(); #endif - RLDEBUG("GUI: - clearing update flags..."); - for (int i=0; i<m_windows.size(); ++i) { - m_windows[i].pendingUpdate = false; - } + m_sync_triggered_update = false; RLDEBUG("GUI: - lock for sync..."); m_thread->mutex.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<QTimerEvent *>(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<QTimerEvent *>(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<WMWindowEvent *>(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 4c297f500a..63b2b442e6 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 901f2fcf2e..899b999cdc 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 955304e317..03400ba673 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 3e907a5204..858653a613 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<bool>("blockedGui"); + QTest::addColumn<QByteArray>("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; |