1 /****************************************************************************
2 **
3 ** Copyright (C) 2016 The Qt Company Ltd.
4 ** Contact: https://www.qt.io/licensing/
5 **
6 ** This file is part of the QtQuick module of the Qt Toolkit.
7 **
8 ** $QT_BEGIN_LICENSE:LGPL$
9 ** Commercial License Usage
10 ** Licensees holding valid commercial Qt licenses may use this file in
11 ** accordance with the commercial license agreement provided with the
12 ** Software or, alternatively, in accordance with the terms contained in
13 ** a written agreement between you and The Qt Company. For licensing terms
14 ** and conditions see https://www.qt.io/terms-conditions. For further
15 ** information use the contact form at https://www.qt.io/contact-us.
16 **
17 ** GNU Lesser General Public License Usage
18 ** Alternatively, this file may be used under the terms of the GNU Lesser
19 ** General Public License version 3 as published by the Free Software
20 ** Foundation and appearing in the file LICENSE.LGPL3 included in the
21 ** packaging of this file. Please review the following information to
22 ** ensure the GNU Lesser General Public License version 3 requirements
23 ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
24 **
25 ** GNU General Public License Usage
26 ** Alternatively, this file may be used under the terms of the GNU
27 ** General Public License version 2.0 or (at your option) the GNU General
28 ** Public license version 3 or any later version approved by the KDE Free
29 ** Qt Foundation. The licenses are as published by the Free Software
30 ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
31 ** included in the packaging of this file. Please review the following
32 ** information to ensure the GNU General Public License requirements will
33 ** be met: https://www.gnu.org/licenses/gpl-2.0.html and
34 ** https://www.gnu.org/licenses/gpl-3.0.html.
35 **
36 ** $QT_END_LICENSE$
37 **
38 ****************************************************************************/
39 
40 #include "qsgsoftwarethreadedrenderloop_p.h"
41 #include "qsgsoftwarecontext_p.h"
42 #include "qsgsoftwarerenderer_p.h"
43 
44 #include <private/qsgrenderer_p.h>
45 #include <private/qquickwindow_p.h>
46 #include <private/qquickprofiler_p.h>
47 #include <private/qquickanimatorcontroller_p.h>
48 #include <private/qquickprofiler_p.h>
49 #include <private/qqmldebugserviceinterfaces_p.h>
50 #include <private/qqmldebugconnector_p.h>
51 
52 #include <qpa/qplatformbackingstore.h>
53 
54 #include <QtCore/QQueue>
55 #include <QtCore/QElapsedTimer>
56 #include <QtCore/QThread>
57 #include <QtCore/QMutex>
58 #include <QtCore/QWaitCondition>
59 #include <QtGui/QGuiApplication>
60 #include <QtGui/QBackingStore>
61 #include <QtQuick/QQuickWindow>
62 
63 #include <qtquick_tracepoints_p.h>
64 
65 QT_BEGIN_NAMESPACE
66 
67 // Passed from the RL to the RT when a window is removed obscured and should be
68 // removed from the render loop.
69 const QEvent::Type WM_Obscure           = QEvent::Type(QEvent::User + 1);
70 
71 // Passed from the RL to RT when GUI has been locked, waiting for sync.
72 const QEvent::Type WM_RequestSync       = QEvent::Type(QEvent::User + 2);
73 
74 // Passed by the RL to the RT to maybe release resource if no windows are
75 // rendering.
76 const QEvent::Type WM_TryRelease        = QEvent::Type(QEvent::User + 4);
77 
78 // Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
79 const QEvent::Type WM_Grab              = QEvent::Type(QEvent::User + 5);
80 
81 // Passed by the window when there is a render job to run.
82 const QEvent::Type WM_PostJob           = QEvent::Type(QEvent::User + 6);
83 
84 class QSGSoftwareWindowEvent : public QEvent
85 {
86 public:
QSGSoftwareWindowEvent(QQuickWindow * c,QEvent::Type type)87     QSGSoftwareWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
88     QQuickWindow *window;
89 };
90 
91 class QSGSoftwareTryReleaseEvent : public QSGSoftwareWindowEvent
92 {
93 public:
QSGSoftwareTryReleaseEvent(QQuickWindow * win,bool destroy)94     QSGSoftwareTryReleaseEvent(QQuickWindow *win, bool destroy)
95         : QSGSoftwareWindowEvent(win, WM_TryRelease), destroying(destroy) { }
96     bool destroying;
97 };
98 
99 class QSGSoftwareSyncEvent : public QSGSoftwareWindowEvent
100 {
101 public:
QSGSoftwareSyncEvent(QQuickWindow * c,bool inExpose,bool force)102     QSGSoftwareSyncEvent(QQuickWindow *c, bool inExpose, bool force)
103         : QSGSoftwareWindowEvent(c, WM_RequestSync)
104         , size(c->size())
105         , dpr(c->effectiveDevicePixelRatio())
106         , syncInExpose(inExpose)
107         , forceRenderPass(force) { }
108     QSize size;
109     float dpr;
110     bool syncInExpose;
111     bool forceRenderPass;
112 };
113 
114 class QSGSoftwareGrabEvent : public QSGSoftwareWindowEvent
115 {
116 public:
QSGSoftwareGrabEvent(QQuickWindow * c,QImage * result)117     QSGSoftwareGrabEvent(QQuickWindow *c, QImage *result)
118         : QSGSoftwareWindowEvent(c, WM_Grab), image(result) { }
119     QImage *image;
120 };
121 
122 class QSGSoftwareJobEvent : public QSGSoftwareWindowEvent
123 {
124 public:
QSGSoftwareJobEvent(QQuickWindow * c,QRunnable * postedJob)125     QSGSoftwareJobEvent(QQuickWindow *c, QRunnable *postedJob)
126         : QSGSoftwareWindowEvent(c, WM_PostJob), job(postedJob) { }
~QSGSoftwareJobEvent()127     ~QSGSoftwareJobEvent() { delete job; }
128     QRunnable *job;
129 };
130 
131 class QSGSoftwareEventQueue : public QQueue<QEvent *>
132 {
133 public:
addEvent(QEvent * e)134     void addEvent(QEvent *e) {
135         mutex.lock();
136         enqueue(e);
137         if (waiting)
138             condition.wakeOne();
139         mutex.unlock();
140     }
141 
takeEvent(bool wait)142     QEvent *takeEvent(bool wait) {
143         mutex.lock();
144         if (isEmpty() && wait) {
145             waiting = true;
146             condition.wait(&mutex);
147             waiting = false;
148         }
149         QEvent *e = dequeue();
150         mutex.unlock();
151         return e;
152     }
153 
hasMoreEvents()154     bool hasMoreEvents() {
155         mutex.lock();
156         bool has = !isEmpty();
157         mutex.unlock();
158         return has;
159     }
160 
161 private:
162     QMutex mutex;
163     QWaitCondition condition;
164     bool waiting = false;
165 };
166 
qsgrl_animation_interval()167 static inline int qsgrl_animation_interval()
168 {
169     const qreal refreshRate = QGuiApplication::primaryScreen() ? QGuiApplication::primaryScreen()->refreshRate() : 0;
170     return refreshRate < 1 ? 16 : int(1000 / refreshRate);
171 }
172 
173 class QSGSoftwareRenderThread : public QThread
174 {
175     Q_OBJECT
176 public:
QSGSoftwareRenderThread(QSGSoftwareThreadedRenderLoop * rl,QSGRenderContext * renderContext)177     QSGSoftwareRenderThread(QSGSoftwareThreadedRenderLoop *rl, QSGRenderContext *renderContext)
178         : renderLoop(rl)
179     {
180         rc = static_cast<QSGSoftwareRenderContext *>(renderContext);
181         vsyncDelta = qsgrl_animation_interval();
182     }
183 
~QSGSoftwareRenderThread()184     ~QSGSoftwareRenderThread()
185     {
186         delete rc;
187     }
188 
189     bool event(QEvent *e) override;
190     void run() override;
191 
192     void syncAndRender();
193     void sync(bool inExpose);
194 
requestRepaint()195     void requestRepaint()
196     {
197         if (sleeping)
198             stopEventProcessing = true;
199         if (exposedWindow)
200             pendingUpdate |= RepaintRequest;
201     }
202 
203     void processEventsAndWaitForMore();
204     void processEvents();
205     void postEvent(QEvent *e);
206 
207     enum UpdateRequest {
208         SyncRequest         = 0x01,
209         RepaintRequest      = 0x02,
210         ExposeRequest       = 0x04 | RepaintRequest | SyncRequest
211     };
212 
213     QSGSoftwareThreadedRenderLoop *renderLoop;
214     QSGSoftwareRenderContext *rc;
215     QAnimationDriver *rtAnim = nullptr;
216     volatile bool active = false;
217     uint pendingUpdate = 0;
218     bool sleeping = false;
219     bool syncResultedInChanges = false;
220     float vsyncDelta;
221     QMutex mutex;
222     QWaitCondition waitCondition;
223     QQuickWindow *exposedWindow = nullptr;
224     QBackingStore *backingStore = nullptr;
225     bool stopEventProcessing = false;
226     QSGSoftwareEventQueue eventQueue;
227     QElapsedTimer renderThrottleTimer;
228     qint64 syncTime;
229     qint64 renderTime;
230     qint64 sinceLastTime;
231 
232 public slots:
onSceneGraphChanged()233     void onSceneGraphChanged() {
234         syncResultedInChanges = true;
235     }
236 };
237 
event(QEvent * e)238 bool QSGSoftwareRenderThread::event(QEvent *e)
239 {
240     switch ((int)e->type()) {
241 
242     case WM_Obscure:
243         Q_ASSERT(!exposedWindow || exposedWindow == static_cast<QSGSoftwareWindowEvent *>(e)->window);
244         qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "RT - WM_Obscure" << exposedWindow;
245         mutex.lock();
246         if (exposedWindow) {
247             QQuickWindowPrivate::get(exposedWindow)->fireAboutToStop();
248             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Obscure - window removed");
249             exposedWindow = nullptr;
250             delete backingStore;
251             backingStore = nullptr;
252         }
253         waitCondition.wakeOne();
254         mutex.unlock();
255         return true;
256 
257     case WM_RequestSync: {
258         QSGSoftwareSyncEvent *wme = static_cast<QSGSoftwareSyncEvent *>(e);
259         if (sleeping)
260             stopEventProcessing = true;
261         exposedWindow = wme->window;
262         if (backingStore == nullptr)
263             backingStore = new QBackingStore(exposedWindow);
264         if (backingStore->size() != exposedWindow->size())
265             backingStore->resize(exposedWindow->size());
266         qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "RT - WM_RequestSync" << exposedWindow;
267         pendingUpdate |= SyncRequest;
268         if (wme->syncInExpose) {
269             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_RequestSync - triggered from expose");
270             pendingUpdate |= ExposeRequest;
271         }
272         if (wme->forceRenderPass) {
273             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_RequestSync - repaint regardless");
274             pendingUpdate |= RepaintRequest;
275         }
276         return true;
277     }
278 
279     case WM_TryRelease: {
280         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease");
281         mutex.lock();
282         renderLoop->lockedForSync = true;
283         QSGSoftwareTryReleaseEvent *wme = static_cast<QSGSoftwareTryReleaseEvent *>(e);
284         // Only when no windows are exposed anymore or we are shutting down.
285         if (!exposedWindow || wme->destroying) {
286             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease - invalidating rc");
287             if (wme->window) {
288                 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
289                 if (wme->destroying) {
290                     // Bye bye nodes...
291                     wd->cleanupNodesOnShutdown();
292                 }
293                 rc->invalidate();
294                 QCoreApplication::processEvents();
295                 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
296                 if (wme->destroying)
297                     wd->animationController.reset();
298             }
299             if (wme->destroying)
300                 active = false;
301             if (sleeping)
302                 stopEventProcessing = true;
303         } else {
304             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_TryRelease - not releasing because window is still active");
305         }
306         waitCondition.wakeOne();
307         renderLoop->lockedForSync = false;
308         mutex.unlock();
309         return true;
310     }
311 
312     case WM_Grab: {
313         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Grab");
314         QSGSoftwareGrabEvent *wme = static_cast<QSGSoftwareGrabEvent *>(e);
315         Q_ASSERT(wme->window);
316         Q_ASSERT(wme->window == exposedWindow || !exposedWindow);
317         mutex.lock();
318         if (wme->window) {
319             // Grabbing is generally done by rendering a frame and reading the
320             // color buffer contents back, without presenting, and then
321             // creating a QImage from the returned data. It is terribly
322             // inefficient since it involves a full blocking wait for the GPU.
323             // However, our hands are tied by the existing, synchronous APIs of
324             // QQuickWindow and such.
325             QQuickWindowPrivate *wd = QQuickWindowPrivate::get(wme->window);
326             auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);
327             if (softwareRenderer)
328                 softwareRenderer->setBackingStore(backingStore);
329             rc->initialize(nullptr);
330             wd->syncSceneGraph();
331             rc->endSync();
332             wd->renderSceneGraph(wme->window->size());
333             *wme->image = backingStore->handle()->toImage();
334         }
335         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_Grab - waking gui to handle result");
336         waitCondition.wakeOne();
337         mutex.unlock();
338         return true;
339     }
340 
341     case WM_PostJob: {
342         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_PostJob");
343         QSGSoftwareJobEvent *wme = static_cast<QSGSoftwareJobEvent *>(e);
344         Q_ASSERT(wme->window == exposedWindow);
345         if (exposedWindow) {
346             wme->job->run();
347             delete wme->job;
348             wme->job = nullptr;
349             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - WM_PostJob - job done");
350         }
351         return true;
352     }
353 
354     default:
355         break;
356     }
357 
358     return QThread::event(e);
359 }
360 
postEvent(QEvent * e)361 void QSGSoftwareRenderThread::postEvent(QEvent *e)
362 {
363     eventQueue.addEvent(e);
364 }
365 
processEvents()366 void QSGSoftwareRenderThread::processEvents()
367 {
368     while (eventQueue.hasMoreEvents()) {
369         QEvent *e = eventQueue.takeEvent(false);
370         event(e);
371         delete e;
372     }
373 }
374 
processEventsAndWaitForMore()375 void QSGSoftwareRenderThread::processEventsAndWaitForMore()
376 {
377     stopEventProcessing = false;
378     while (!stopEventProcessing) {
379         QEvent *e = eventQueue.takeEvent(true);
380         event(e);
381         delete e;
382     }
383 }
384 
run()385 void QSGSoftwareRenderThread::run()
386 {
387     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run()");
388 
389     rtAnim = rc->sceneGraphContext()->createAnimationDriver(nullptr);
390     rtAnim->install();
391 
392     if (QQmlDebugConnector::service<QQmlProfilerService>())
393         QQuickProfiler::registerAnimationCallback();
394 
395     renderThrottleTimer.start();
396 
397     while (active) {
398         if (exposedWindow)
399             syncAndRender();
400 
401         processEvents();
402         QCoreApplication::processEvents();
403 
404         if (pendingUpdate == 0 || !exposedWindow) {
405             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - done drawing, sleep");
406             sleeping = true;
407             processEventsAndWaitForMore();
408             sleeping = false;
409         }
410     }
411 
412     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run() exiting");
413 
414     delete rtAnim;
415     rtAnim = nullptr;
416 
417     rc->moveToThread(renderLoop->thread());
418     moveToThread(renderLoop->thread());
419 }
420 
sync(bool inExpose)421 void QSGSoftwareRenderThread::sync(bool inExpose)
422 {
423     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - sync");
424 
425     mutex.lock();
426     Q_ASSERT_X(renderLoop->lockedForSync, "QSGD3D12RenderThread::sync()", "sync triggered with gui not locked");
427 
428     if (exposedWindow) {
429         QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
430         bool hadRenderer = wd->renderer != nullptr;
431         // If the scene graph was touched since the last sync() make sure it sends the
432         // changed signal.
433         if (wd->renderer)
434             wd->renderer->clearChangedFlag();
435 
436         rc->initialize(nullptr);
437         wd->syncSceneGraph();
438         rc->endSync();
439 
440         if (!hadRenderer && wd->renderer) {
441             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - created renderer");
442             syncResultedInChanges = true;
443             connect(wd->renderer, &QSGRenderer::sceneGraphChanged, this,
444                     &QSGSoftwareRenderThread::onSceneGraphChanged, Qt::DirectConnection);
445         }
446 
447         // Process deferred deletes now, directly after the sync as deleteLater
448         // on the GUI must now also have resulted in SG changes and the delete
449         // is a safe operation.
450         QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
451     }
452 
453     if (!inExpose) {
454         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - sync complete, waking gui");
455         waitCondition.wakeOne();
456         mutex.unlock();
457     }
458 }
459 
syncAndRender()460 void QSGSoftwareRenderThread::syncAndRender()
461 {
462     Q_TRACE_SCOPE(QSG_syncAndRender);
463     Q_TRACE(QSG_sync_entry);
464     Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
465 
466     QElapsedTimer waitTimer;
467     waitTimer.start();
468 
469     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - syncAndRender()");
470 
471     syncResultedInChanges = false;
472     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(exposedWindow);
473 
474     const bool repaintRequested = (pendingUpdate & RepaintRequest) || wd->customRenderStage;
475     const bool syncRequested = pendingUpdate & SyncRequest;
476     const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
477     pendingUpdate = 0;
478 
479     if (syncRequested)
480         sync(exposeRequested);
481 
482     Q_TRACE(QSG_sync_exit);
483     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
484                               QQuickProfiler::SceneGraphRenderLoopSync);
485 
486     if (!syncResultedInChanges && !repaintRequested) {
487         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - no changes, render aborted");
488         int waitTime = vsyncDelta - (int) waitTimer.elapsed();
489         if (waitTime > 0)
490             msleep(waitTime);
491         return;
492     }
493 
494     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - rendering started");
495     Q_TRACE(QSG_render_entry);
496 
497     if (rtAnim->isRunning()) {
498         wd->animationController->lock();
499         rtAnim->advance();
500         wd->animationController->unlock();
501     }
502 
503     bool canRender = wd->renderer != nullptr;
504 
505     if (canRender) {
506         auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);
507         if (softwareRenderer)
508             softwareRenderer->setBackingStore(backingStore);
509         wd->renderSceneGraph(exposedWindow->size());
510 
511         Q_TRACE(QSG_render_exit);
512         Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
513                                   QQuickProfiler::SceneGraphRenderLoopRender);
514         Q_TRACE(QSG_swap_entry);
515 
516         if (softwareRenderer && (!wd->customRenderStage || !wd->customRenderStage->swap()))
517             backingStore->flush(softwareRenderer->flushRegion());
518 
519         // Since there is no V-Sync with QBackingStore, throttle rendering the refresh
520         // rate of the current screen the window is on.
521         int blockTime = vsyncDelta - (int) renderThrottleTimer.elapsed();
522         if (blockTime > 0) {
523             qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - blocking for %d ms", blockTime);
524             msleep(blockTime);
525         }
526         renderThrottleTimer.restart();
527 
528         wd->fireFrameSwapped();
529     } else {
530         Q_TRACE(QSG_render_exit);
531         Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
532                                 QQuickProfiler::SceneGraphRenderLoopSync, 1);
533         Q_TRACE(QSG_swap_entry);
534         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - window not ready, skipping render");
535     }
536 
537     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - rendering done");
538 
539     if (exposeRequested) {
540         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - wake gui after initial expose");
541         waitCondition.wakeOne();
542         mutex.unlock();
543     }
544 
545     Q_TRACE(QSG_swap_exit);
546     Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
547                            QQuickProfiler::SceneGraphRenderLoopSwap);
548 }
549 
windowFor(const QVector<T> & list,QQuickWindow * window)550 template<class T> T *windowFor(const QVector<T> &list, QQuickWindow *window)
551 {
552     for (const T &t : list) {
553         if (t.window == window)
554             return const_cast<T *>(&t);
555     }
556     return nullptr;
557 }
558 
559 
QSGSoftwareThreadedRenderLoop()560 QSGSoftwareThreadedRenderLoop::QSGSoftwareThreadedRenderLoop()
561 {
562     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop constructor");
563     m_sg = new QSGSoftwareContext;
564     m_anim = m_sg->createAnimationDriver(this);
565     connect(m_anim, &QAnimationDriver::started, this, &QSGSoftwareThreadedRenderLoop::onAnimationStarted);
566     connect(m_anim, &QAnimationDriver::stopped, this, &QSGSoftwareThreadedRenderLoop::onAnimationStopped);
567     m_anim->install();
568 }
569 
~QSGSoftwareThreadedRenderLoop()570 QSGSoftwareThreadedRenderLoop::~QSGSoftwareThreadedRenderLoop()
571 {
572     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop destructor");
573     delete m_sg;
574 }
575 
show(QQuickWindow * window)576 void QSGSoftwareThreadedRenderLoop::show(QQuickWindow *window)
577 {
578     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "show" << window;
579 }
580 
hide(QQuickWindow * window)581 void QSGSoftwareThreadedRenderLoop::hide(QQuickWindow *window)
582 {
583     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "hide" << window;
584 
585     if (window->isExposed())
586         handleObscurity(windowFor(m_windows, window));
587 
588     releaseResources(window);
589 }
590 
resize(QQuickWindow * window)591 void QSGSoftwareThreadedRenderLoop::resize(QQuickWindow *window)
592 {
593     if (!window->isExposed() || window->size().isEmpty())
594         return;
595 
596     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "resize" << window << window->size();
597 }
598 
windowDestroyed(QQuickWindow * window)599 void QSGSoftwareThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
600 {
601     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "window destroyed" << window;
602 
603     WindowData *w = windowFor(m_windows, window);
604     if (!w)
605         return;
606 
607     handleObscurity(w);
608     handleResourceRelease(w, true);
609 
610     QSGSoftwareRenderThread *thread = w->thread;
611     while (thread->isRunning())
612         QThread::yieldCurrentThread();
613 
614     Q_ASSERT(thread->thread() == QThread::currentThread());
615     delete thread;
616 
617     for (int i = 0; i < m_windows.size(); ++i) {
618         if (m_windows.at(i).window == window) {
619             m_windows.removeAt(i);
620             break;
621         }
622     }
623 
624     // Now that we altered the window list, we may need to stop the animation
625     // timer even if we didn't via handleObscurity. This covers the case where
626     // we destroy a visible & exposed QQuickWindow.
627     startOrStopAnimationTimer();
628 }
629 
exposureChanged(QQuickWindow * window)630 void QSGSoftwareThreadedRenderLoop::exposureChanged(QQuickWindow *window)
631 {
632     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "exposure changed" << window;
633 
634     if (window->isExposed()) {
635         handleExposure(window);
636     } else {
637         WindowData *w = windowFor(m_windows, window);
638         if (w)
639             handleObscurity(w);
640     }
641 }
642 
grab(QQuickWindow * window)643 QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
644 {
645     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "grab" << window;
646 
647     WindowData *w = windowFor(m_windows, window);
648     // Have to support invisible (but created()'ed) windows as well.
649     // Unlike with GL, leaving that case for QQuickWindow to handle is not feasible.
650     const bool tempExpose = !w;
651     if (tempExpose) {
652         handleExposure(window);
653         w = windowFor(m_windows, window);
654         Q_ASSERT(w);
655     }
656 
657     if (!w->thread->isRunning())
658         return QImage();
659 
660     if (!window->handle())
661         window->create();
662 
663     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
664     wd->polishItems();
665 
666     QImage result;
667     w->thread->mutex.lock();
668     lockedForSync = true;
669     w->thread->postEvent(new QSGSoftwareGrabEvent(window, &result));
670     w->thread->waitCondition.wait(&w->thread->mutex);
671     lockedForSync = false;
672     w->thread->mutex.unlock();
673 
674     result.setDevicePixelRatio(window->effectiveDevicePixelRatio());
675 
676     if (tempExpose)
677         handleObscurity(w);
678 
679     return result;
680 }
681 
update(QQuickWindow * window)682 void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
683 {
684     WindowData *w = windowFor(m_windows, window);
685     if (!w)
686         return;
687 
688     if (w->thread == QThread::currentThread()) {
689         w->thread->requestRepaint();
690         return;
691     }
692 
693     // We set forceRenderPass because we want to make sure the QQuickWindow
694     // actually does a full render pass after the next sync.
695     w->forceRenderPass = true;
696     scheduleUpdate(w);
697 }
698 
maybeUpdate(QQuickWindow * window)699 void QSGSoftwareThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
700 {
701     WindowData *w = windowFor(m_windows, window);
702     if (w)
703         scheduleUpdate(w);
704 }
705 
handleUpdateRequest(QQuickWindow * window)706 void QSGSoftwareThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
707 {
708     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleUpdateRequest" << window;
709 
710     WindowData *w = windowFor(m_windows, window);
711     if (w)
712         polishAndSync(w, false);
713 }
714 
animationDriver() const715 QAnimationDriver *QSGSoftwareThreadedRenderLoop::animationDriver() const
716 {
717     return m_anim;
718 }
719 
sceneGraphContext() const720 QSGContext *QSGSoftwareThreadedRenderLoop::sceneGraphContext() const
721 {
722     return m_sg;
723 }
724 
createRenderContext(QSGContext *) const725 QSGRenderContext *QSGSoftwareThreadedRenderLoop::createRenderContext(QSGContext *) const
726 {
727     return m_sg->createRenderContext();
728 }
729 
releaseResources(QQuickWindow * window)730 void QSGSoftwareThreadedRenderLoop::releaseResources(QQuickWindow *window)
731 {
732     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "releaseResources" << window;
733 
734     WindowData *w = windowFor(m_windows, window);
735     if (w)
736         handleResourceRelease(w, false);
737 }
738 
postJob(QQuickWindow * window,QRunnable * job)739 void QSGSoftwareThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
740 {
741     WindowData *w = windowFor(m_windows, window);
742     if (w && w->thread && w->thread->exposedWindow)
743         w->thread->postEvent(new QSGSoftwareJobEvent(window, job));
744     else
745         delete job;
746 }
747 
windowSurfaceType() const748 QSurface::SurfaceType QSGSoftwareThreadedRenderLoop::windowSurfaceType() const
749 {
750     return QSurface::RasterSurface;
751 }
752 
interleaveIncubation() const753 bool QSGSoftwareThreadedRenderLoop::interleaveIncubation() const
754 {
755     bool somethingVisible = false;
756     for (const WindowData &w : m_windows) {
757         if (w.window->isVisible() && w.window->isExposed()) {
758             somethingVisible = true;
759             break;
760         }
761     }
762     return somethingVisible && m_anim->isRunning();
763 }
764 
flags() const765 int QSGSoftwareThreadedRenderLoop::flags() const
766 {
767     return SupportsGrabWithoutExpose;
768 }
769 
event(QEvent * e)770 bool QSGSoftwareThreadedRenderLoop::event(QEvent *e)
771 {
772     if (e->type() == QEvent::Timer) {
773         QTimerEvent *te = static_cast<QTimerEvent *>(e);
774         if (te->timerId() == animationTimer) {
775             m_anim->advance();
776             emit timeToIncubate();
777             return true;
778         }
779     }
780 
781     return QObject::event(e);
782 }
783 
onAnimationStarted()784 void QSGSoftwareThreadedRenderLoop::onAnimationStarted()
785 {
786     startOrStopAnimationTimer();
787 
788     for (const WindowData &w : qAsConst(m_windows))
789         w.window->requestUpdate();
790 }
791 
onAnimationStopped()792 void QSGSoftwareThreadedRenderLoop::onAnimationStopped()
793 {
794     startOrStopAnimationTimer();
795 }
796 
startOrStopAnimationTimer()797 void QSGSoftwareThreadedRenderLoop::startOrStopAnimationTimer()
798 {
799     int exposedWindowCount = 0;
800     const WindowData *exposed = nullptr;
801 
802     for (int i = 0; i < m_windows.size(); ++i) {
803         const WindowData &w(m_windows[i]);
804         if (w.window->isVisible() && w.window->isExposed()) {
805             ++exposedWindowCount;
806             exposed = &w;
807         }
808     }
809 
810     if (animationTimer && (exposedWindowCount == 1 || !m_anim->isRunning())) {
811         killTimer(animationTimer);
812         animationTimer = 0;
813         // If animations are running, make sure we keep on animating
814         if (m_anim->isRunning())
815             exposed->window->requestUpdate();
816     } else if (!animationTimer && exposedWindowCount != 1 && m_anim->isRunning()) {
817         animationTimer = startTimer(qsgrl_animation_interval());
818     }
819 }
820 
handleExposure(QQuickWindow * window)821 void QSGSoftwareThreadedRenderLoop::handleExposure(QQuickWindow *window)
822 {
823     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleExposure" << window;
824 
825     WindowData *w = windowFor(m_windows, window);
826     if (!w) {
827         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "adding window to list");
828         WindowData win;
829         win.window = window;
830         QSGRenderContext *rc = QQuickWindowPrivate::get(window)->context; // will transfer ownership
831         win.thread = new QSGSoftwareRenderThread(this, rc);
832         win.updateDuringSync = false;
833         win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
834         m_windows.append(win);
835         w = &m_windows.last();
836     }
837 
838     // set this early as we'll be rendering shortly anyway and this avoids
839     // special casing exposure in polishAndSync.
840     w->thread->exposedWindow = window;
841 
842     if (w->window->size().isEmpty()
843         || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
844 #ifndef QT_NO_DEBUG
845         qWarning().noquote().nospace() << "QSGSotwareThreadedRenderLoop: expose event received for window "
846             << w->window << " with invalid geometry: " << w->window->geometry()
847             << " on " << w->window->screen();
848 #endif
849     }
850 
851     if (!w->window->handle())
852         w->window->create();
853 
854     // Start render thread if it is not running
855     if (!w->thread->isRunning()) {
856         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "starting render thread");
857         // Push a few things to the render thread.
858         QQuickAnimatorController *controller
859                 = QQuickWindowPrivate::get(w->window)->animationController.get();
860         if (controller->thread() != w->thread)
861             controller->moveToThread(w->thread);
862         if (w->thread->thread() == QThread::currentThread()) {
863             w->thread->rc->moveToThread(w->thread);
864             w->thread->moveToThread(w->thread);
865         }
866 
867         w->thread->active = true;
868         w->thread->start();
869 
870         if (!w->thread->isRunning())
871             qFatal("Render thread failed to start, aborting application.");
872     }
873 
874     polishAndSync(w, true);
875 
876     startOrStopAnimationTimer();
877 }
878 
handleObscurity(QSGSoftwareThreadedRenderLoop::WindowData * w)879 void QSGSoftwareThreadedRenderLoop::handleObscurity(QSGSoftwareThreadedRenderLoop::WindowData *w)
880 {
881     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleObscurity" << w->window;
882 
883     if (w->thread->isRunning()) {
884         w->thread->mutex.lock();
885         w->thread->postEvent(new QSGSoftwareWindowEvent(w->window, WM_Obscure));
886         w->thread->waitCondition.wait(&w->thread->mutex);
887         w->thread->mutex.unlock();
888     }
889 
890     startOrStopAnimationTimer();
891 }
892 
scheduleUpdate(QSGSoftwareThreadedRenderLoop::WindowData * w)893 void QSGSoftwareThreadedRenderLoop::scheduleUpdate(QSGSoftwareThreadedRenderLoop::WindowData *w)
894 {
895     if (!QCoreApplication::instance())
896         return;
897 
898     if (!w || !w->thread->isRunning())
899         return;
900 
901     QThread *current = QThread::currentThread();
902     if (current != QCoreApplication::instance()->thread() && (current != w->thread || !lockedForSync)) {
903         qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
904         return;
905     }
906 
907     if (current == w->thread) {
908         w->updateDuringSync = true;
909         return;
910     }
911 
912     w->window->requestUpdate();
913 }
914 
handleResourceRelease(QSGSoftwareThreadedRenderLoop::WindowData * w,bool destroying)915 void QSGSoftwareThreadedRenderLoop::handleResourceRelease(QSGSoftwareThreadedRenderLoop::WindowData *w, bool destroying)
916 {
917     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleResourceRelease" << (destroying ? "destroying" : "hide/releaseResources") << w->window;
918 
919     w->thread->mutex.lock();
920     if (w->thread->isRunning() && w->thread->active) {
921         QQuickWindow *window = w->window;
922 
923         // Note that window->handle() is typically null by this time because
924         // the platform window is already destroyed. This should not be a
925         // problem for the D3D cleanup.
926 
927         w->thread->postEvent(new QSGSoftwareTryReleaseEvent(window, destroying));
928         w->thread->waitCondition.wait(&w->thread->mutex);
929 
930         // Avoid a shutdown race condition.
931         // If SG is invalidated and 'active' becomes false, the thread's run()
932         // method will exit. handleExposure() relies on QThread::isRunning() (because it
933         // potentially needs to start the thread again) and our mutex cannot be used to
934         // track the thread stopping, so we wait a few nanoseconds extra so the thread
935         // can exit properly.
936         if (!w->thread->active)
937             w->thread->wait();
938     }
939     w->thread->mutex.unlock();
940 }
941 
polishAndSync(QSGSoftwareThreadedRenderLoop::WindowData * w,bool inExpose)942 void QSGSoftwareThreadedRenderLoop::polishAndSync(QSGSoftwareThreadedRenderLoop::WindowData *w, bool inExpose)
943 {
944     qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
945 
946     QQuickWindow *window = w->window;
947     if (!w->thread || !w->thread->exposedWindow) {
948         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - not exposed, abort");
949         return;
950     }
951 
952     // Flush pending touch events.
953     QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
954     // The delivery of the event might have caused the window to stop rendering
955     w = windowFor(m_windows, window);
956     if (!w || !w->thread || !w->thread->exposedWindow) {
957         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - removed after touch event flushing, abort");
958         return;
959     }
960 
961     Q_TRACE_SCOPE(QSG_polishAndSync);
962 
963     Q_TRACE(QSG_polishItems_entry);
964     Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
965 
966     QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
967     wd->polishItems();
968 
969     Q_TRACE(QSG_polishItems_exit);
970     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
971                               QQuickProfiler::SceneGraphPolishAndSyncPolish);
972     Q_TRACE(QSG_sync_entry);
973 
974     w->updateDuringSync = false;
975 
976     emit window->afterAnimating();
977 
978     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - lock for sync");
979     w->thread->mutex.lock();
980     lockedForSync = true;
981     w->thread->postEvent(new QSGSoftwareSyncEvent(window, inExpose, w->forceRenderPass));
982     w->forceRenderPass = false;
983 
984     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - wait for sync");
985 
986     Q_TRACE(QSG_sync_exit);
987     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
988                               QQuickProfiler::SceneGraphPolishAndSyncWait);
989     Q_TRACE(QSG_wait_entry);
990     w->thread->waitCondition.wait(&w->thread->mutex);
991     lockedForSync = false;
992     w->thread->mutex.unlock();
993     qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - unlock after sync");
994 
995     Q_TRACE(QSG_wait_exit);
996     Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
997                               QQuickProfiler::SceneGraphPolishAndSyncSync);
998     Q_TRACE(QSG_animations_entry);
999 
1000     if (!animationTimer && m_anim->isRunning()) {
1001         qCDebug(QSG_RASTER_LOG_RENDERLOOP, "polishAndSync - advancing animations");
1002         m_anim->advance();
1003         // We need to trigger another sync to keep animations running...
1004         w->window->requestUpdate();
1005         emit timeToIncubate();
1006     } else if (w->updateDuringSync) {
1007         w->window->requestUpdate();
1008     }
1009 
1010     Q_TRACE(QSG_animations_exit);
1011     Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1012                            QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1013 }
1014 
1015 #include "qsgsoftwarethreadedrenderloop.moc"
1016 #include "moc_qsgsoftwarethreadedrenderloop_p.cpp"
1017 
1018 QT_END_NAMESPACE
1019