From 15ce5d915b6bda4bf1d3c85cbdc79b2e11690bca Mon Sep 17 00:00:00 2001
From: Gunnar Sletta <gunnar.sletta@jollamobile.com>
Date: Fri, 4 Jul 2014 23:50:50 +0200
Subject: [PATCH] Introducing QQuickWindow::scheduleRenderJob()

[ChangeLog][QtQuick][QQuickWindow] Added
QQuickWindow::scheduleRenderJob(), a convenience alternative to the
equivalent signals for one-shot tasks.

Change-Id: I5e4f0d67d5223f7fd77bca394e2a85810fadd335
Reviewed-by: Laszlo Agocs <laszlo.agocs@digia.com>
---
 src/quick/items/qquickwindow.cpp              | 107 +++++++++++++++++-
 src/quick/items/qquickwindow.h                |  12 ++
 src/quick/items/qquickwindow_p.h              |   9 ++
 .../quick/qquickwindow/tst_qquickwindow.cpp   |  55 +++++++++
 4 files changed, 182 insertions(+), 1 deletion(-)

diff --git a/src/quick/items/qquickwindow.cpp b/src/quick/items/qquickwindow.cpp
index 63e57615ba..0c71254169 100644
--- a/src/quick/items/qquickwindow.cpp
+++ b/src/quick/items/qquickwindow.cpp
@@ -66,6 +66,7 @@
 #include <QtCore/qvarlengtharray.h>
 #include <QtCore/qabstractanimation.h>
 #include <QtCore/QLibraryInfo>
+#include <QtCore/QRunnable>
 #include <QtQml/qqmlincubator.h>
 
 #include <QtQuick/private/qquickpixmapcache_p.h>
@@ -331,6 +332,7 @@ void QQuickWindowPrivate::syncSceneGraph()
     animationController->beforeNodeSync();
 
     emit q->beforeSynchronizing();
+    runAndClearJobs(&beforeSynchronizingJobs);
     if (!renderer) {
         forceUpdate(contentItem);
 
@@ -354,6 +356,7 @@ void QQuickWindowPrivate::syncSceneGraph()
     renderer->setCustomRenderMode(customRenderMode);
 
     emit q->afterSynchronizing();
+    runAndClearJobs(&afterSynchronizingJobs);
     context->endSync();
 }
 
@@ -367,6 +370,7 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size)
 
     animationController->advance();
     emit q->beforeRendering();
+    runAndClearJobs(&beforeRenderingJobs);
     int fboId = 0;
     const qreal devicePixelRatio = q->devicePixelRatio();
     renderer->setDeviceRect(QRect(QPoint(0, 0), size * devicePixelRatio));
@@ -381,6 +385,7 @@ void QQuickWindowPrivate::renderSceneGraph(const QSize &size)
 
     context->renderNextFrame(renderer, fboId);
     emit q->afterRendering();
+    runAndClearJobs(&afterRenderingJobs);
 }
 
 QQuickWindowPrivate::QQuickWindowPrivate()
@@ -470,6 +475,8 @@ void QQuickWindowPrivate::init(QQuickWindow *c, QQuickRenderControl *control)
 
     QObject::connect(q, SIGNAL(focusObjectChanged(QObject*)), q, SIGNAL(activeFocusItemChanged()));
     QObject::connect(q, SIGNAL(screenChanged(QScreen*)), q, SLOT(forcePolish()));
+
+    QObject::connect(q, SIGNAL(frameSwapped()), q, SLOT(runJobsAfterSwap()), Qt::DirectConnection);
 }
 
 /*!
@@ -1101,6 +1108,19 @@ QQuickWindow::~QQuickWindow()
     delete d->dragGrabber; d->dragGrabber = 0;
 #endif
     delete d->contentItem; d->contentItem = 0;
+
+    d->renderJobMutex.lock();
+    qDeleteAll(d->beforeSynchronizingJobs);
+    d->beforeSynchronizingJobs.clear();
+    qDeleteAll(d->afterSynchronizingJobs);
+    d->afterSynchronizingJobs.clear();
+    qDeleteAll(d->beforeRenderingJobs);
+    d->beforeRenderingJobs.clear();
+    qDeleteAll(d->afterRenderingJobs);
+    d->afterRenderingJobs.clear();
+    qDeleteAll(d->afterSwapJobs);
+    d->afterSwapJobs.clear();
+    d->renderJobMutex.unlock();
 }
 
 /*!
@@ -2815,8 +2835,13 @@ void QQuickWindow::cleanupSceneGraph()
 
     delete d->renderer->rootNode();
     delete d->renderer;
-
     d->renderer = 0;
+
+    d->runAndClearJobs(&d->beforeSynchronizingJobs);
+    d->runAndClearJobs(&d->afterSynchronizingJobs);
+    d->runAndClearJobs(&d->beforeRenderingJobs);
+    d->runAndClearJobs(&d->afterRenderingJobs);
+    d->runAndClearJobs(&d->afterSwapJobs);
 }
 
 void QQuickWindow::setTransientParent_helper(QQuickWindow *window)
@@ -3834,6 +3859,86 @@ bool QQuickWindow::glslIsCoreProfile() const
     flashing or bouncing the taskbar entry.
 */
 
+/*!
+    \enum QQuickWindow::RenderJobSchedule
+    \since 5.4
+
+    \value ScheduleBeforeSynchronizing Before synchronization.
+    \value ScheduleAfterSynchronizing After synchronization.
+    \value ScheduleBeforeRendering Before rendering.
+    \value ScheduleAfterRendering After rendering.
+    \value ScheduleAfterSwap After the frame is swapped.
+
+    \sa {Scene Graph and Rendering}
+ */
+
+/*!
+    \since 5.4
+
+    Schedule \a job to run when the rendering of this window reaches
+    the given \a stage.
+
+    This is a convenience to the equivalent signals in QQuickWindow for
+    "one shot" tasks.
+
+    The window takes ownership over \a job and will delete it when the
+    job is completed.
+
+    If rendering is shut down before \a job has a chance to run, the
+    job will be run and then deleted as part of the scene graph cleanup.
+    If the window is never shown and no rendering happens before the QQuickWindow
+    is destroyed, all pending jobs will be destroyed without their run()
+    method being called.
+
+    If the rendering is happening on a different thread, then the job
+    will happen on the rendering thread.
+
+    \note This function does not trigger rendering; the job
+    will be stored run until rendering is triggered elsewhere.
+    To force the job to run earlier, call QQuickWindow::update();
+
+    \sa beforeRendering(), afterRendering(), beforeSynchronizing(),
+    afterSynchronizing(), frameSwapped(), sceneGraphInvalidated()
+ */
+
+void QQuickWindow::scheduleRenderJob(QRunnable *job, RenderStage stage)
+{
+    Q_D(QQuickWindow);
+
+    d->renderJobMutex.lock();
+    if (stage == BeforeSynchronizingStage)
+        d->beforeSynchronizingJobs << job;
+    else if (stage == AfterSynchronizingStage)
+        d->afterSynchronizingJobs << job;
+    else if (stage == BeforeRenderingStage)
+        d->beforeRenderingJobs << job;
+    else if (stage == AfterRenderingStage)
+        d->afterRenderingJobs << job;
+    else if (stage == AfterSwapStage)
+        d->afterSwapJobs << job;
+    d->renderJobMutex.unlock();
+}
+
+void QQuickWindowPrivate::runAndClearJobs(QList<QRunnable *> *jobs)
+{
+    renderJobMutex.lock();
+    QList<QRunnable *> jobList = *jobs;
+    jobs->clear();
+    renderJobMutex.unlock();
+
+    foreach (QRunnable *r, jobList) {
+        r->run();
+        delete r;
+    }
+}
+
+void QQuickWindow::runJobsAfterSwap()
+{
+    Q_D(QQuickWindow);
+    d->runAndClearJobs(&d->afterSwapJobs);
+}
+
+
 #include "moc_qquickwindow.cpp"
 
 QT_END_NAMESPACE
diff --git a/src/quick/items/qquickwindow.h b/src/quick/items/qquickwindow.h
index 4ed663ee6e..6353f6a30c 100644
--- a/src/quick/items/qquickwindow.h
+++ b/src/quick/items/qquickwindow.h
@@ -50,6 +50,7 @@
 
 QT_BEGIN_NAMESPACE
 
+class QRunnable;
 class QQuickItem;
 class QSGTexture;
 class QInputMethodEvent;
@@ -79,6 +80,14 @@ public:
         TextureCanUseAtlas      = 0x0008
     };
 
+    enum RenderStage {
+        BeforeSynchronizingStage,
+        AfterSynchronizingStage,
+        BeforeRenderingStage,
+        AfterRenderingStage,
+        AfterSwapStage
+    };
+
     Q_DECLARE_FLAGS(CreateTextureOptions, CreateTextureOption)
 
     enum SceneGraphError {
@@ -145,6 +154,8 @@ public:
     QString glslVersion() const;
     bool glslIsCoreProfile() const;
 
+    void scheduleRenderJob(QRunnable *job, RenderStage schedule);
+
 Q_SIGNALS:
     void frameSwapped();
     Q_REVISION(2) void openglContextCreated(QOpenGLContext *context);
@@ -196,6 +207,7 @@ private Q_SLOTS:
     void cleanupSceneGraph();
     void forcePolish();
     void setTransientParent_helper(QQuickWindow *window);
+    void runJobsAfterSwap();
 
 private:
     friend class QQuickItem;
diff --git a/src/quick/items/qquickwindow_p.h b/src/quick/items/qquickwindow_p.h
index 8faaf6489b..66202aec5c 100644
--- a/src/quick/items/qquickwindow_p.h
+++ b/src/quick/items/qquickwindow_p.h
@@ -256,6 +256,15 @@ public:
     static bool defaultFormatInitialized;
     static QSurfaceFormat defaultFormat;
 
+    QMutex renderJobMutex;
+    QList<QRunnable *> beforeSynchronizingJobs;
+    QList<QRunnable *> afterSynchronizingJobs;
+    QList<QRunnable *> beforeRenderingJobs;
+    QList<QRunnable *> afterRenderingJobs;
+    QList<QRunnable *> afterSwapJobs;
+
+    void runAndClearJobs(QList<QRunnable *> *jobs);
+
 private:
     static void cleanupNodesOnShutdown(QQuickItem *);
 };
diff --git a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
index 17773fcfc4..6c1d46b191 100644
--- a/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
+++ b/tests/auto/quick/qquickwindow/tst_qquickwindow.cpp
@@ -55,6 +55,7 @@
 #include <qpa/qwindowsysteminterface.h>
 #include <private/qquickwindow_p.h>
 #include <private/qguiapplication_p.h>
+#include <QRunnable>
 
 struct TouchEventData {
     QEvent::Type type;
@@ -367,6 +368,8 @@ private slots:
     void defaultSurfaceFormat();
     void glslVersion();
 
+    void testRenderJob();
+
 private:
     QTouchDevice *touchDevice;
     QTouchDevice *touchDeviceWithVelocity;
@@ -1971,6 +1974,58 @@ void tst_qquickwindow::glslVersion()
     }
 }
 
+class RenderJob : public QRunnable
+{
+public:
+    RenderJob(QQuickWindow::RenderStage s, QList<QQuickWindow::RenderStage> *l) : stage(s), list(l) { }
+    ~RenderJob() { ++deleted; }
+    QQuickWindow::RenderStage stage;
+    QList<QQuickWindow::RenderStage> *list;
+    void run() {
+        list->append(stage);
+    }
+    static int deleted;
+};
+
+int RenderJob::deleted = 0;
+
+void tst_qquickwindow::testRenderJob()
+{
+    QList<QQuickWindow::RenderStage> completedJobs;
+
+    QQuickWindow window;
+
+    QQuickWindow::RenderStage stages[] = {
+        QQuickWindow::BeforeSynchronizingStage,
+        QQuickWindow::AfterSynchronizingStage,
+        QQuickWindow::BeforeRenderingStage,
+        QQuickWindow::AfterRenderingStage,
+        QQuickWindow::AfterSwapStage
+    };
+    // Schedule the jobs
+    for (int i=0; i<5; ++i)
+        window.scheduleRenderJob(new RenderJob(stages[i], &completedJobs), stages[i]);
+    window.show();
+
+    QTRY_COMPARE(completedJobs.size(), 5);
+
+    for (int i=0; i<5; ++i) {
+        QCOMPARE(completedJobs.at(i), stages[i]);
+    }
+
+    // Verify that jobs are deleted when window has not been rendered at all...
+    completedJobs.clear();
+    RenderJob::deleted = 0;
+    {
+        QQuickWindow window2;
+        for (int i=0; i<5; ++i) {
+            window2.scheduleRenderJob(new RenderJob(stages[i], &completedJobs), stages[i]);
+        }
+    }
+    QCOMPARE(completedJobs.size(), 0);
+    QCOMPARE(RenderJob::deleted, 5);
+}
+
 QTEST_MAIN(tst_qquickwindow)
 
 #include "tst_qquickwindow.moc"
-- 
GitLab