1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Copyright (C) 2016 Jolla Ltd, author: <gunnar.sletta@jollamobile.com>
5** Contact: https://www.qt.io/licensing/
6**
7** This file is part of the QtQuick module of the Qt Toolkit.
8**
9** $QT_BEGIN_LICENSE:LGPL$
10** Commercial License Usage
11** Licensees holding valid commercial Qt licenses may use this file in
12** accordance with the commercial license agreement provided with the
13** Software or, alternatively, in accordance with the terms contained in
14** a written agreement between you and The Qt Company. For licensing terms
15** and conditions see https://www.qt.io/terms-conditions. For further
16** information use the contact form at https://www.qt.io/contact-us.
17**
18** GNU Lesser General Public License Usage
19** Alternatively, this file may be used under the terms of the GNU Lesser
20** General Public License version 3 as published by the Free Software
21** Foundation and appearing in the file LICENSE.LGPL3 included in the
22** packaging of this file. Please review the following information to
23** ensure the GNU Lesser General Public License version 3 requirements
24** will be met: https://www.gnu.org/licenses/lgpl-3.0.html.
25**
26** GNU General Public License Usage
27** Alternatively, this file may be used under the terms of the GNU
28** General Public License version 2.0 or (at your option) the GNU General
29** Public license version 3 or any later version approved by the KDE Free
30** Qt Foundation. The licenses are as published by the Free Software
31** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3
32** included in the packaging of this file. Please review the following
33** information to ensure the GNU General Public License requirements will
34** be met: https://www.gnu.org/licenses/gpl-2.0.html and
35** https://www.gnu.org/licenses/gpl-3.0.html.
36**
37** $QT_END_LICENSE$
38**
39****************************************************************************/
40
41
42#include <QtCore/QMutex>
43#include <QtCore/QWaitCondition>
44#include <QtCore/QAnimationDriver>
45#include <QtCore/QQueue>
46#include <QtCore/QTime>
47
48#include <QtGui/QGuiApplication>
49#include <QtGui/QScreen>
50#include <QtGui/QOffscreenSurface>
51
52#include <qpa/qwindowsysteminterface.h>
53
54#include <QtQuick/QQuickWindow>
55#include <private/qquickwindow_p.h>
56
57#include <QtQuick/private/qsgrenderer_p.h>
58
59#include "qsgthreadedrenderloop_p.h"
60#include "qsgrhisupport_p.h"
61#include <private/qquickanimatorcontroller_p.h>
62
63#include <private/qquickprofiler_p.h>
64#include <private/qqmldebugserviceinterfaces_p.h>
65#include <private/qqmldebugconnector_p.h>
66
67#if QT_CONFIG(quick_shadereffect)
68#include <private/qquickopenglshadereffectnode_p.h>
69#endif
70#include <private/qsgrhishadereffectnode_p.h>
71#include <private/qsgdefaultrendercontext_p.h>
72
73/*
74 Overall design:
75
76 There are two classes here. QSGThreadedRenderLoop and
77 QSGRenderThread. All communication between the two is based on
78 event passing and we have a number of custom events.
79
80 In this implementation, the render thread is never blocked and the
81 GUI thread will initiate a polishAndSync which will block and wait
82 for the render thread to pick it up and release the block only
83 after the render thread is done syncing. The reason for this
84 is:
85
86 1. Clear blocking paradigm. We only have one real "block" point
87 (polishAndSync()) and all blocking is initiated by GUI and picked
88 up by Render at specific times based on events. This makes the
89 execution deterministic.
90
91 2. Render does not have to interact with GUI. This is done so that
92 the render thread can run its own animation system which stays
93 alive even when the GUI thread is blocked doing i/o, object
94 instantiation, QPainter-painting or any other non-trivial task.
95
96 ---
97
98 There is one thread per window and one opengl context per thread.
99
100 ---
101
102 The render thread has affinity to the GUI thread until a window
103 is shown. From that moment and until the window is destroyed, it
104 will have affinity to the render thread. (moved back at the end
105 of run for cleanup).
106
107 ---
108
109 The render loop is active while any window is exposed. All visible
110 windows are tracked, but only exposed windows are actually added to
111 the render thread and rendered. That means that if all windows are
112 obscured, we might end up cleaning up the SG and GL context (if all
113 windows have disabled persistency). Especially for multiprocess,
114 low-end systems, this should be quite important.
115
116 */
117
118QT_BEGIN_NAMESPACE
119
120#define QSG_RT_PAD " (RT) %s"
121
122static inline int qsgrl_animation_interval() {
123 qreal refreshRate = QGuiApplication::primaryScreen()->refreshRate();
124 // To work around that some platforms wrongfully return 0 or something
125 // bogus for refreshrate
126 if (refreshRate < 1)
127 return 16;
128 return int(1000 / refreshRate);
129}
130
131
132static QElapsedTimer threadTimer;
133static qint64 syncTime;
134static qint64 renderTime;
135static qint64 sinceLastTime;
136
137extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
138
139// RL: Render Loop
140// RT: Render Thread
141
142// Passed from the RL to the RT when a window is removed obscured and
143// should be removed from the render loop.
144const QEvent::Type WM_Obscure = QEvent::Type(QEvent::User + 1);
145
146// Passed from the RL to RT when GUI has been locked, waiting for sync
147// (updatePaintNode())
148const QEvent::Type WM_RequestSync = QEvent::Type(QEvent::User + 2);
149
150// Passed by the RT to itself to trigger another render pass. This is
151// typically a result of QQuickWindow::update().
152const QEvent::Type WM_RequestRepaint = QEvent::Type(QEvent::User + 3);
153
154// Passed by the RL to the RT to free up maybe release SG and GL contexts
155// if no windows are rendering.
156const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
157
158// Passed by the RL to the RT when a QQuickWindow::grabWindow() is
159// called.
160const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
161
162// Passed by the window when there is a render job to run
163const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
164
165// When using the QRhi this is sent upon PlatformSurfaceAboutToBeDestroyed from
166// the event filter installed on the QQuickWindow.
167const QEvent::Type WM_ReleaseSwapchain = QEvent::Type(QEvent::User + 7);
168
169template <typename T> T *windowFor(const QList<T> &list, QQuickWindow *window)
170{
171 for (int i=0; i<list.size(); ++i) {
172 const T &t = list.at(i);
173 if (t.window == window)
174 return const_cast<T *>(&t);
175 }
176 return nullptr;
177}
178
179
180class WMWindowEvent : public QEvent
181{
182public:
183 WMWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
184 QQuickWindow *window;
185};
186
187class WMTryReleaseEvent : public WMWindowEvent
188{
189public:
190 WMTryReleaseEvent(QQuickWindow *win, bool destroy, bool needsFallbackSurface)
191 : WMWindowEvent(win, WM_TryRelease)
192 , inDestructor(destroy)
193 , needsFallback(needsFallbackSurface)
194 {}
195
196 bool inDestructor;
197 bool needsFallback;
198};
199
200class WMSyncEvent : public WMWindowEvent
201{
202public:
203 WMSyncEvent(QQuickWindow *c, bool inExpose, bool force)
204 : WMWindowEvent(c, WM_RequestSync)
205 , size(c->size())
206 , dpr(c->effectiveDevicePixelRatio())
207 , syncInExpose(inExpose)
208 , forceRenderPass(force)
209 {}
210 QSize size;
211 float dpr;
212 bool syncInExpose;
213 bool forceRenderPass;
214};
215
216
217class WMGrabEvent : public WMWindowEvent
218{
219public:
220 WMGrabEvent(QQuickWindow *c, QImage *result) : WMWindowEvent(c, WM_Grab), image(result) {}
221 QImage *image;
222};
223
224class WMJobEvent : public WMWindowEvent
225{
226public:
227 WMJobEvent(QQuickWindow *c, QRunnable *postedJob)
228 : WMWindowEvent(c, WM_PostJob), job(postedJob) {}
229 ~WMJobEvent() { delete job; }
230 QRunnable *job;
231};
232
233class WMReleaseSwapchainEvent : public WMWindowEvent
234{
235public:
236 WMReleaseSwapchainEvent(QQuickWindow *c) : WMWindowEvent(c, WM_ReleaseSwapchain) { }
237};
238
239class QSGRenderThreadEventQueue : public QQueue<QEvent *>
240{
241public:
242 QSGRenderThreadEventQueue()
243 : waiting(false)
244 {
245 }
246
247 void addEvent(QEvent *e) {
248 mutex.lock();
249 enqueue(e);
250 if (waiting)
251 condition.wakeOne();
252 mutex.unlock();
253 }
254
255 QEvent *takeEvent(bool wait) {
256 mutex.lock();
257 if (size() == 0 && wait) {
258 waiting = true;
259 condition.wait(&mutex);
260 waiting = false;
261 }
262 QEvent *e = dequeue();
263 mutex.unlock();
264 return e;
265 }
266
267 bool hasMoreEvents() {
268 mutex.lock();
269 bool has = !isEmpty();
270 mutex.unlock();
271 return has;
272 }
273
274private:
275 QMutex mutex;
276 QWaitCondition condition;
277 bool waiting;
278};
279
280
281class QSGRenderThread : public QThread
282{
283 Q_OBJECT
284public:
285 QSGRenderThread(QSGThreadedRenderLoop *w, QSGRenderContext *renderContext)
286 : wm(w)
287 , gl(nullptr)
288 , enableRhi(false)
289 , rhi(nullptr)
290 , offscreenSurface(nullptr)
291 , animatorDriver(nullptr)
292 , pendingUpdate(0)
293 , sleeping(false)
294 , syncResultedInChanges(false)
295 , active(false)
296 , window(nullptr)
297 , stopEventProcessing(false)
298 {
299 sgrc = static_cast<QSGDefaultRenderContext *>(renderContext);
300#if defined(Q_OS_QNX) && defined(Q_PROCESSOR_X86)
301 // The SDP 6.6.0 x86 MESA driver requires a larger stack than the default.
302 setStackSize(1024 * 1024);
303#endif
304 vsyncDelta = qsgrl_animation_interval();
305 }
306
307 ~QSGRenderThread()
308 {
309 delete sgrc;
310 delete offscreenSurface;
311 }
312
313 void invalidateOpenGL(QQuickWindow *window, bool inDestructor, QOffscreenSurface *backupSurface);
314 void initializeOpenGL();
315
316 bool event(QEvent *) override;
317 void run() override;
318
319 void syncAndRender(QImage *grabImage = nullptr);
320 void sync(bool inExpose, bool inGrab);
321
322 void requestRepaint()
323 {
324 if (sleeping)
325 stopEventProcessing = true;
326 if (window)
327 pendingUpdate |= RepaintRequest;
328 }
329
330 void processEventsAndWaitForMore();
331 void processEvents();
332 void postEvent(QEvent *e);
333
334public slots:
335 void sceneGraphChanged() {
336 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sceneGraphChanged");
337 syncResultedInChanges = true;
338 }
339
340public:
341 enum UpdateRequest {
342 SyncRequest = 0x01,
343 RepaintRequest = 0x02,
344 ExposeRequest = 0x04 | RepaintRequest | SyncRequest
345 };
346
347 QSGThreadedRenderLoop *wm;
348 QOpenGLContext *gl;
349 bool enableRhi;
350 QRhi *rhi;
351 QSGDefaultRenderContext *sgrc;
352 QOffscreenSurface *offscreenSurface;
353
354 QAnimationDriver *animatorDriver;
355
356 uint pendingUpdate;
357 bool sleeping;
358 bool syncResultedInChanges;
359
360 volatile bool active;
361
362 float vsyncDelta;
363
364 QMutex mutex;
365 QWaitCondition waitCondition;
366
367 QElapsedTimer m_timer;
368
369 QQuickWindow *window; // Will be 0 when window is not exposed
370 QSize windowSize;
371 float dpr = 1;
372 int rhiSampleCount = 1;
373
374 // Local event queue stuff...
375 bool stopEventProcessing;
376 QSGRenderThreadEventQueue eventQueue;
377};
378
379bool QSGRenderThread::event(QEvent *e)
380{
381 switch ((int) e->type()) {
382
383 case WM_Obscure: {
384 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Obscure");
385
386 Q_ASSERT(!window || window == static_cast<WMWindowEvent *>(e)->window);
387
388 mutex.lock();
389 if (window) {
390 QQuickWindowPrivate::get(window)->fireAboutToStop();
391 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window removed");
392 window = nullptr;
393 }
394 waitCondition.wakeOne();
395 mutex.unlock();
396
397 return true; }
398
399 case WM_RequestSync: {
400 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestSync");
401 WMSyncEvent *se = static_cast<WMSyncEvent *>(e);
402 if (sleeping)
403 stopEventProcessing = true;
404 window = se->window;
405 windowSize = se->size;
406 dpr = se->dpr;
407
408 pendingUpdate |= SyncRequest;
409 if (se->syncInExpose) {
410 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- triggered from expose");
411 pendingUpdate |= ExposeRequest;
412 }
413 if (se->forceRenderPass) {
414 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- repaint regardless");
415 pendingUpdate |= RepaintRequest;
416 }
417 return true; }
418
419 case WM_TryRelease: {
420 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_TryRelease");
421 mutex.lock();
422 wm->m_lockedForSync = true;
423 WMTryReleaseEvent *wme = static_cast<WMTryReleaseEvent *>(e);
424 if (!window || wme->inDestructor) {
425 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- setting exit flag and invalidating OpenGL");
426 invalidateOpenGL(wme->window, wme->inDestructor, wme->needsFallback ? offscreenSurface : nullptr);
427 active = gl || rhi;
428 Q_ASSERT_X(!wme->inDestructor || !active, "QSGRenderThread::invalidateOpenGL()", "Thread's active state is not set to false when shutting down");
429 if (sleeping)
430 stopEventProcessing = true;
431 } else {
432 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- not releasing because window is still active");
433 if (window) {
434 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
435 if (d->renderer) {
436 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- requesting renderer to release cached resources");
437 d->renderer->releaseCachedResources();
438 }
439 }
440 }
441 waitCondition.wakeOne();
442 wm->m_lockedForSync = false;
443 mutex.unlock();
444 return true;
445 }
446
447 case WM_Grab: {
448 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_Grab");
449 WMGrabEvent *ce = static_cast<WMGrabEvent *>(e);
450 Q_ASSERT(ce->window);
451 Q_ASSERT(ce->window == window || !window);
452 mutex.lock();
453 if (ce->window) {
454 const bool alpha = ce->window->format().alphaBufferSize() > 0 && ce->window->color().alpha() != 255;
455 const QSize readbackSize = windowSize * ce->window->effectiveDevicePixelRatio();
456 if (rhi) {
457 rhi->makeThreadLocalNativeContextCurrent();
458 syncAndRender(ce->image);
459 } else {
460 gl->makeCurrent(ce->window);
461
462 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync scene graph");
463 QQuickWindowPrivate *d = QQuickWindowPrivate::get(ce->window);
464 d->syncSceneGraph();
465 sgrc->endSync();
466
467 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering scene graph");
468 QQuickWindowPrivate::get(ce->window)->renderSceneGraph(ce->window->size());
469
470 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- grabbing result");
471 *ce->image = qt_gl_read_framebuffer(readbackSize, alpha, alpha);
472 }
473 ce->image->setDevicePixelRatio(ce->window->effectiveDevicePixelRatio());
474 }
475 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- waking gui to handle result");
476 waitCondition.wakeOne();
477 mutex.unlock();
478 return true;
479 }
480
481 case WM_PostJob: {
482 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_PostJob");
483 WMJobEvent *ce = static_cast<WMJobEvent *>(e);
484 Q_ASSERT(ce->window == window);
485 if (window) {
486 if (rhi)
487 rhi->makeThreadLocalNativeContextCurrent();
488 else
489 gl->makeCurrent(window);
490 ce->job->run();
491 delete ce->job;
492 ce->job = nullptr;
493 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- job done");
494 }
495 return true;
496 }
497
498 case WM_ReleaseSwapchain: {
499 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_ReleaseSwapchain");
500 WMReleaseSwapchainEvent *ce = static_cast<WMReleaseSwapchainEvent *>(e);
501 // forget about 'window' here that may be null when already unexposed
502 Q_ASSERT(ce->window);
503 mutex.lock();
504 if (ce->window) {
505 wm->releaseSwapchain(ce->window);
506 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- swapchain released");
507 }
508 waitCondition.wakeOne();
509 mutex.unlock();
510 return true;
511 }
512
513 case WM_RequestRepaint:
514 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "WM_RequestPaint");
515 // When GUI posts this event, it is followed by a polishAndSync, so we mustn't
516 // exit the event loop yet.
517 pendingUpdate |= RepaintRequest;
518 break;
519
520 default:
521 break;
522 }
523 return QThread::event(e);
524}
525
526void QSGRenderThread::invalidateOpenGL(QQuickWindow *window, bool inDestructor, QOffscreenSurface *fallback)
527{
528 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "invalidateOpenGL()");
529
530 if (!gl && !rhi)
531 return;
532
533 if (!window) {
534 qCWarning(QSG_LOG_RENDERLOOP, "QSGThreadedRenderLoop:QSGRenderThread: no window to make current...");
535 return;
536 }
537
538
539 bool wipeSG = inDestructor || !window->isPersistentSceneGraph();
540 bool wipeGL = inDestructor || (wipeSG && !window->isPersistentOpenGLContext());
541
542 bool current = true;
543 if (gl)
544 current = gl->makeCurrent(fallback ? static_cast<QSurface *>(fallback) : static_cast<QSurface *>(window));
545 else if (rhi)
546 rhi->makeThreadLocalNativeContextCurrent();
547
548 if (Q_UNLIKELY(!current)) {
549 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- cleanup without an OpenGL context");
550 }
551
552 QQuickWindowPrivate *dd = QQuickWindowPrivate::get(window);
553
554#if QT_CONFIG(quick_shadereffect)
555 QSGRhiShaderEffectNode::cleanupMaterialTypeCache();
556#if QT_CONFIG(opengl)
557 if (current)
558 QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache();
559#endif
560#endif
561
562 // The canvas nodes must be cleaned up regardless if we are in the destructor..
563 if (wipeSG) {
564 dd->cleanupNodesOnShutdown();
565 } else {
566 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent SG, avoiding cleanup");
567 if (current && gl)
568 gl->doneCurrent();
569 return;
570 }
571
572 sgrc->invalidate();
573 QCoreApplication::processEvents();
574 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
575 if (inDestructor)
576 delete dd->animationController;
577 if (current && gl)
578 gl->doneCurrent();
579 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidating scene graph");
580
581 if (wipeGL) {
582 if (dd->swapchain) {
583 if (window->handle()) {
584 // We get here when exiting via QCoreApplication::quit() instead of
585 // through QWindow::close().
586 wm->releaseSwapchain(window);
587 } else {
588 qWarning("QSGThreadedRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
589 window, dd->swapchain);
590 }
591 }
592 delete gl;
593 gl = nullptr;
594 delete rhi;
595 rhi = nullptr;
596 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- invalidated OpenGL");
597 } else {
598 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- persistent GL, avoiding cleanup");
599 }
600}
601
602/*
603 Enters the mutex lock to make sure GUI is blocking and performs
604 sync, then wakes GUI.
605 */
606void QSGRenderThread::sync(bool inExpose, bool inGrab)
607{
608 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "sync()");
609 if (!inGrab)
610 mutex.lock();
611
612 Q_ASSERT_X(wm->m_lockedForSync, "QSGRenderThread::sync()", "sync triggered on bad terms as gui is not already locked...");
613
614 bool current = true;
615 if (gl) {
616 if (windowSize.width() > 0 && windowSize.height() > 0)
617 current = gl->makeCurrent(window);
618 else
619 current = false;
620 // Check for context loss.
621 if (!current && !gl->isValid()) {
622 QQuickWindowPrivate::get(window)->cleanupNodesOnShutdown();
623 sgrc->invalidate();
624 current = gl->create() && gl->makeCurrent(window);
625 if (current) {
626 QSGDefaultRenderContext::InitParams rcParams;
627 rcParams.sampleCount = qMax(1, gl->format().samples());
628 rcParams.openGLContext = gl;
629 rcParams.initialSurfacePixelSize = windowSize * dpr;
630 rcParams.maybeSurface = window;
631 sgrc->initialize(&rcParams);
632 }
633 }
634 } else {
635 // With the rhi making the (OpenGL) context current serves only one
636 // purpose: to enable external OpenGL rendering connected to one of
637 // the QQuickWindow signals (beforeSynchronizing, beforeRendering,
638 // etc.) to function like it did on the direct OpenGL path. For our
639 // own rendering this call would not be necessary.
640 rhi->makeThreadLocalNativeContextCurrent();
641 }
642 if (current) {
643 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
644 bool hadRenderer = d->renderer != nullptr;
645 // If the scene graph was touched since the last sync() make sure it sends the
646 // changed signal.
647 if (d->renderer)
648 d->renderer->clearChangedFlag();
649 d->syncSceneGraph();
650 sgrc->endSync();
651 if (!hadRenderer && d->renderer) {
652 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- renderer was created");
653 syncResultedInChanges = true;
654 connect(d->renderer, SIGNAL(sceneGraphChanged()), this, SLOT(sceneGraphChanged()), Qt::DirectConnection);
655 }
656
657 // Process deferred deletes now, directly after the sync as
658 // deleteLater on the GUI must now also have resulted in SG changes
659 // and the delete is a safe operation.
660 QCoreApplication::sendPostedEvents(nullptr, QEvent::DeferredDelete);
661 } else {
662 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window has bad size, sync aborted");
663 }
664
665 if (!inExpose && !inGrab) {
666 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- sync complete, waking Gui");
667 waitCondition.wakeOne();
668 mutex.unlock();
669 }
670}
671
672void QSGRenderThread::syncAndRender(QImage *grabImage)
673{
674 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
675 if (profileFrames) {
676 sinceLastTime = threadTimer.nsecsElapsed();
677 threadTimer.start();
678 }
679 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphRenderLoopFrame);
680
681 QElapsedTimer waitTimer;
682 waitTimer.start();
683
684 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "syncAndRender()");
685
686 syncResultedInChanges = false;
687 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
688
689 const bool repaintRequested = (pendingUpdate & RepaintRequest) || d->customRenderStage || grabImage;
690 const bool syncRequested = (pendingUpdate & SyncRequest) || grabImage;
691 const bool exposeRequested = (pendingUpdate & ExposeRequest) == ExposeRequest;
692 pendingUpdate = 0;
693 const bool grabRequested = grabImage != nullptr;
694
695 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
696 // Begin the frame before syncing -> sync is where we may invoke
697 // updatePaintNode() on the items and they may want to do resource updates.
698 // Also relevant for applications that connect to the before/afterSynchronizing
699 // signals and want to do graphics stuff already there.
700 if (cd->swapchain && windowSize.width() > 0 && windowSize.height() > 0) {
701 // always prefer what the surface tells us, not the QWindow
702 const QSize effectiveOutputSize = cd->swapchain->surfacePixelSize();
703 // An update request could still be delivered right before we get an
704 // unexpose. With Vulkan on Windows for example attempting to render
705 // leads to failures at this stage since the surface size is already 0.
706 if (effectiveOutputSize.isEmpty())
707 return;
708
709 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
710 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
711 if (cd->swapchainJustBecameRenderable)
712 qDebug("just became exposed");
713 cd->swapchainJustBecameRenderable = false;
714 cd->depthStencilForSwapchain->setPixelSize(effectiveOutputSize);
715
716 cd->depthStencilForSwapchain->build();
717 cd->hasActiveSwapchain = cd->swapchain->buildOrResize();
718
719 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
720 if (!cd->hasActiveSwapchain)
721 qWarning("Failed to build or resize swapchain");
722 else
723 qDebug() << "rhi swapchain size" << effectiveOutputSize;
724 }
725
726 Q_ASSERT(rhi == cd->rhi);
727 QRhi::FrameOpResult frameResult = rhi->beginFrame(cd->swapchain);
728 if (frameResult != QRhi::FrameOpSuccess) {
729 if (frameResult == QRhi::FrameOpDeviceLost)
730 qWarning("Device lost");
731 else if (frameResult == QRhi::FrameOpError)
732 qWarning("Failed to start frame");
733 // try again later
734 if (frameResult == QRhi::FrameOpDeviceLost || frameResult == QRhi::FrameOpSwapChainOutOfDate)
735 QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
736
737 // Before returning we need to ensure the same wake up logic that
738 // would have happened if beginFrame() had suceeded.
739 if (exposeRequested) {
740 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui");
741 waitCondition.wakeOne();
742 mutex.unlock();
743 } else if (syncRequested && !grabRequested) {
744 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- bailing out due to failed beginFrame, wake Gui like sync would do");
745 mutex.lock();
746 waitCondition.wakeOne();
747 mutex.unlock();
748 }
749 return;
750 }
751 }
752
753 if (syncRequested) {
754 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- updatePending, doing sync");
755 sync(exposeRequested, grabRequested);
756 }
757#ifndef QSG_NO_RENDER_TIMING
758 if (profileFrames)
759 syncTime = threadTimer.nsecsElapsed();
760#endif
761 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
762 QQuickProfiler::SceneGraphRenderLoopSync);
763
764 if (!syncResultedInChanges && !repaintRequested && sgrc->isValid() && !grabImage) {
765 if (gl || !rhi->isRecordingFrame()) {
766 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- no changes, render aborted");
767 int waitTime = vsyncDelta - (int) waitTimer.elapsed();
768 if (waitTime > 0)
769 msleep(waitTime);
770 return;
771 }
772 }
773
774 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering started");
775
776
777 if (animatorDriver->isRunning() && !grabImage) {
778 d->animationController->lock();
779 animatorDriver->advance();
780 d->animationController->unlock();
781 }
782
783 bool current = true;
784 if (d->renderer && windowSize.width() > 0 && windowSize.height() > 0) {
785 if (gl)
786 current = gl->makeCurrent(window);
787 else if (rhi)
788 rhi->makeThreadLocalNativeContextCurrent();
789 } else {
790 current = false;
791 }
792 // Check for context loss (GL, RHI case handled after the beginFrame() above)
793 if (gl) {
794 if (!current && !gl->isValid()) {
795 // Cannot do anything here because gui is not locked. Request a new
796 // sync+render round on the gui thread and let the sync handle it.
797 QCoreApplication::postEvent(window, new QEvent(QEvent::Type(QQuickWindowPrivate::FullUpdateRequest)));
798 }
799 }
800 if (current) {
801
802 d->renderSceneGraph(windowSize);
803
804 if (profileFrames)
805 renderTime = threadTimer.nsecsElapsed();
806 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
807 QQuickProfiler::SceneGraphRenderLoopRender);
808
809 // With the rhi grabs can only be done by adding a readback and then
810 // blocking in a real frame. The legacy GL path never gets here with
811 // grabs as it rather invokes sync/render directly without going
812 // through syncAndRender().
813 if (grabImage) {
814 Q_ASSERT(rhi && !gl && cd->swapchain);
815 *grabImage = QSGRhiSupport::instance()->grabAndBlockInCurrentFrame(rhi, cd->swapchain);
816 }
817
818 if (cd->swapchain) {
819 QRhi::EndFrameFlags flags = 0;
820 if (grabImage)
821 flags |= QRhi::SkipPresent;
822 rhi->endFrame(cd->swapchain, flags);
823 } else {
824 if (!cd->customRenderStage || !cd->customRenderStage->swap())
825 gl->swapBuffers(window);
826 }
827
828 if (!grabImage)
829 d->fireFrameSwapped();
830
831 } else {
832 Q_QUICK_SG_PROFILE_SKIP(QQuickProfiler::SceneGraphRenderLoopFrame,
833 QQuickProfiler::SceneGraphRenderLoopSync, 1);
834 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- window not ready, skipping render");
835 }
836
837 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- rendering done");
838
839 // Though it would be more correct to put this block directly after
840 // fireFrameSwapped in the if (current) branch above, we don't do
841 // that to avoid blocking the GUI thread in the case where it
842 // has started rendering with a bad window, causing makeCurrent to
843 // fail or if the window has a bad size.
844 if (exposeRequested) {
845 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "- wake Gui after initial expose");
846 waitCondition.wakeOne();
847 mutex.unlock();
848 }
849
850 qCDebug(QSG_LOG_TIME_RENDERLOOP,
851 "Frame rendered with 'threaded' renderloop in %dms, sync=%d, render=%d, swap=%d - (on render thread)",
852 int(threadTimer.elapsed()),
853 int((syncTime/1000000)),
854 int((renderTime - syncTime) / 1000000),
855 int(threadTimer.elapsed() - renderTime / 1000000));
856
857
858 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
859 QQuickProfiler::SceneGraphRenderLoopSwap);
860
861 QSGRhiProfileConnection::instance()->send(rhi);
862}
863
864
865
866void QSGRenderThread::postEvent(QEvent *e)
867{
868 eventQueue.addEvent(e);
869}
870
871
872
873void QSGRenderThread::processEvents()
874{
875 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEvents()");
876 while (eventQueue.hasMoreEvents()) {
877 QEvent *e = eventQueue.takeEvent(false);
878 event(e);
879 delete e;
880 }
881 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEvents()");
882}
883
884void QSGRenderThread::processEventsAndWaitForMore()
885{
886 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- begin processEventsAndWaitForMore()");
887 stopEventProcessing = false;
888 while (!stopEventProcessing) {
889 QEvent *e = eventQueue.takeEvent(true);
890 event(e);
891 delete e;
892 }
893 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "--- done processEventsAndWaitForMore()");
894}
895
896void QSGRenderThread::run()
897{
898 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run()");
899 animatorDriver = sgrc->sceneGraphContext()->createAnimationDriver(nullptr);
900 animatorDriver->install();
901 if (QQmlDebugConnector::service<QQmlProfilerService>())
902 QQuickProfiler::registerAnimationCallback();
903
904 while (active) {
905#ifdef Q_OS_DARWIN
906 QMacAutoReleasePool frameReleasePool;
907#endif
908
909 if (window) {
910 if (enableRhi) {
911 if (!rhi) {
912 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
913 rhi = rhiSupport->createRhi(window, offscreenSurface);
914 if (rhi) {
915 rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
916 if (rhiSupport->isProfilingRequested())
917 QSGRhiProfileConnection::instance()->initialize(rhi); // ### this breaks down with multiple windows
918 } else {
919 qWarning("Failed to create QRhi on the render thread; scenegraph is not functional");
920 }
921 }
922 if (!sgrc->rhi() && windowSize.width() > 0 && windowSize.height() > 0) {
923 rhi->makeThreadLocalNativeContextCurrent();
924 QSGDefaultRenderContext::InitParams rcParams;
925 rcParams.rhi = rhi;
926 rcParams.sampleCount = rhiSampleCount;
927 rcParams.openGLContext = gl;
928 rcParams.initialSurfacePixelSize = windowSize * dpr;
929 rcParams.maybeSurface = window;
930 sgrc->initialize(&rcParams);
931 }
932 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
933 if (rhi && !cd->swapchain) {
934 cd->rhi = rhi;
935 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
936 cd->swapchain = rhi->newSwapChain();
937 cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
938 QSize(),
939 rhiSampleCount,
940 QRhiRenderBuffer::UsedWithSwapChainOnly);
941 cd->swapchain->setWindow(window);
942 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
943 qDebug("MSAA sample count for the swapchain is %d", rhiSampleCount);
944 cd->swapchain->setSampleCount(rhiSampleCount);
945 cd->swapchain->setFlags(flags);
946 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
947 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
948 }
949 } else {
950 if (!sgrc->openglContext() && windowSize.width() > 0 && windowSize.height() > 0 && gl->makeCurrent(window)) {
951 QSGDefaultRenderContext::InitParams rcParams;
952 rcParams.sampleCount = qMax(1, gl->format().samples());
953 rcParams.openGLContext = gl;
954 rcParams.initialSurfacePixelSize = windowSize * dpr;
955 rcParams.maybeSurface = window;
956 sgrc->initialize(&rcParams);
957 }
958 }
959 syncAndRender();
960 }
961
962 processEvents();
963 QCoreApplication::processEvents();
964
965 if (active && (pendingUpdate == 0 || !window)) {
966 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "done drawing, sleep...");
967 sleeping = true;
968 processEventsAndWaitForMore();
969 sleeping = false;
970 }
971 }
972
973 Q_ASSERT_X(!gl && !rhi, "QSGRenderThread::run()", "The graphics context should be cleaned up before exiting the render thread...");
974
975 qCDebug(QSG_LOG_RENDERLOOP, QSG_RT_PAD, "run() completed");
976
977 delete animatorDriver;
978 animatorDriver = nullptr;
979
980 sgrc->moveToThread(wm->thread());
981 moveToThread(wm->thread());
982}
983
984QSGThreadedRenderLoop::QSGThreadedRenderLoop()
985 : sg(QSGContext::createDefaultContext())
986 , m_animation_timer(0)
987{
988#if defined(QSG_RENDER_LOOP_DEBUG)
989 qsgrl_timer.start();
990#endif
991
992 m_animation_driver = sg->createAnimationDriver(this);
993
994 connect(m_animation_driver, SIGNAL(started()), this, SLOT(animationStarted()));
995 connect(m_animation_driver, SIGNAL(stopped()), this, SLOT(animationStopped()));
996
997 m_animation_driver->install();
998}
999
1000QSGThreadedRenderLoop::~QSGThreadedRenderLoop()
1001{
1002 qDeleteAll(pendingRenderContexts);
1003 delete sg;
1004}
1005
1006QSGRenderContext *QSGThreadedRenderLoop::createRenderContext(QSGContext *sg) const
1007{
1008 auto context = sg->createRenderContext();
1009 pendingRenderContexts.insert(context);
1010 return context;
1011}
1012
1013void QSGThreadedRenderLoop::maybePostPolishRequest(Window *w)
1014{
1015 w->window->requestUpdate();
1016}
1017
1018QAnimationDriver *QSGThreadedRenderLoop::animationDriver() const
1019{
1020 return m_animation_driver;
1021}
1022
1023QSGContext *QSGThreadedRenderLoop::sceneGraphContext() const
1024{
1025 return sg;
1026}
1027
1028bool QSGThreadedRenderLoop::anyoneShowing() const
1029{
1030 for (int i=0; i<m_windows.size(); ++i) {
1031 QQuickWindow *c = m_windows.at(i).window;
1032 if (c->isVisible() && c->isExposed())
1033 return true;
1034 }
1035 return false;
1036}
1037
1038bool QSGThreadedRenderLoop::interleaveIncubation() const
1039{
1040 return m_animation_driver->isRunning() && anyoneShowing();
1041}
1042
1043void QSGThreadedRenderLoop::animationStarted()
1044{
1045 qCDebug(QSG_LOG_RENDERLOOP, "- animationStarted()");
1046 startOrStopAnimationTimer();
1047
1048 for (int i=0; i<m_windows.size(); ++i)
1049 maybePostPolishRequest(const_cast<Window *>(&m_windows.at(i)));
1050}
1051
1052void QSGThreadedRenderLoop::animationStopped()
1053{
1054 qCDebug(QSG_LOG_RENDERLOOP, "- animationStopped()");
1055 startOrStopAnimationTimer();
1056}
1057
1058
1059void QSGThreadedRenderLoop::startOrStopAnimationTimer()
1060{
1061 int exposedWindows = 0;
1062 const Window *theOne = nullptr;
1063 for (int i=0; i<m_windows.size(); ++i) {
1064 const Window &w = m_windows.at(i);
1065 if (w.window->isVisible() && w.window->isExposed()) {
1066 ++exposedWindows;
1067 theOne = &w;
1068 }
1069 }
1070
1071 if (m_animation_timer != 0 && (exposedWindows == 1 || !m_animation_driver->isRunning())) {
1072 qCDebug(QSG_LOG_RENDERLOOP, "*** Stopping animation timer");
1073 killTimer(m_animation_timer);
1074 m_animation_timer = 0;
1075 // If animations are running, make sure we keep on animating
1076 if (m_animation_driver->isRunning())
1077 maybePostPolishRequest(const_cast<Window *>(theOne));
1078 } else if (m_animation_timer == 0 && exposedWindows != 1 && m_animation_driver->isRunning()) {
1079 qCDebug(QSG_LOG_RENDERLOOP, "*** Starting animation timer");
1080 m_animation_timer = startTimer(qsgrl_animation_interval());
1081 }
1082}
1083
1084/*
1085 Removes this window from the list of tracked windowes in this
1086 window manager. hide() will trigger obscure, which in turn will
1087 stop rendering.
1088
1089 This function will be called during QWindow::close() which will
1090 also destroy the QPlatformWindow so it is important that this
1091 triggers handleObscurity() and that rendering for that window
1092 is fully done and over with by the time this function exits.
1093 */
1094
1095void QSGThreadedRenderLoop::hide(QQuickWindow *window)
1096{
1097 qCDebug(QSG_LOG_RENDERLOOP) << "hide()" << window;
1098
1099 if (window->isExposed())
1100 handleObscurity(windowFor(m_windows, window));
1101
1102 releaseResources(window);
1103}
1104
1105
1106/*
1107 If the window is first hide it, then perform a complete cleanup
1108 with releaseResources which will take down the GL context and
1109 exit the rendering thread.
1110 */
1111void QSGThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
1112{
1113 qCDebug(QSG_LOG_RENDERLOOP) << "begin windowDestroyed()" << window;
1114
1115 Window *w = windowFor(m_windows, window);
1116 if (!w)
1117 return;
1118
1119 handleObscurity(w);
1120 releaseResources(w, true);
1121
1122 QSGRenderThread *thread = w->thread;
1123 while (thread->isRunning())
1124 QThread::yieldCurrentThread();
1125 Q_ASSERT(thread->thread() == QThread::currentThread());
1126 delete thread;
1127
1128 for (int i=0; i<m_windows.size(); ++i) {
1129 if (m_windows.at(i).window == window) {
1130 m_windows.removeAt(i);
1131 break;
1132 }
1133 }
1134
1135 // Now that we altered the window list, we may need to stop the animation
1136 // timer even if we didn't via handleObscurity. This covers the case where
1137 // we destroy a visible & exposed QQuickWindow.
1138 startOrStopAnimationTimer();
1139
1140 qCDebug(QSG_LOG_RENDERLOOP) << "done windowDestroyed()" << window;
1141}
1142
1143void QSGThreadedRenderLoop::releaseSwapchain(QQuickWindow *window)
1144{
1145 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1146 delete wd->rpDescForSwapchain;
1147 wd->rpDescForSwapchain = nullptr;
1148 delete wd->swapchain;
1149 wd->swapchain = nullptr;
1150 delete wd->depthStencilForSwapchain;
1151 wd->depthStencilForSwapchain = nullptr;
1152 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
1153}
1154
1155void QSGThreadedRenderLoop::exposureChanged(QQuickWindow *window)
1156{
1157 qCDebug(QSG_LOG_RENDERLOOP) << "exposureChanged()" << window;
1158
1159 // This is tricker than used to be. We want to detect having an empty
1160 // surface size (which may be the case even when window->size() is
1161 // non-empty, on some platforms with some graphics APIs!) as well as the
1162 // case when the window just became "newly exposed" (e.g. after a
1163 // minimize-restore on Windows, or when switching between fully obscured -
1164 // not fully obscured on macOS)
1165 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
1166 if (!window->isExposed())
1167 wd->hasRenderableSwapchain = false;
1168
1169 bool skipThisExpose = false;
1170 if (window->isExposed() && wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()) {
1171 wd->hasRenderableSwapchain = false;
1172 skipThisExpose = true;
1173 }
1174
1175 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
1176 && !wd->swapchain->surfacePixelSize().isEmpty())
1177 {
1178 wd->hasRenderableSwapchain = true;
1179 wd->swapchainJustBecameRenderable = true;
1180 }
1181
1182 if (window->isExposed()) {
1183 if (!skipThisExpose)
1184 handleExposure(window);
1185 } else {
1186 Window *w = windowFor(m_windows, window);
1187 if (w)
1188 handleObscurity(w);
1189 }
1190}
1191
1192/*
1193 Will post an event to the render thread that this window should
1194 start to render.
1195 */
1196void QSGThreadedRenderLoop::handleExposure(QQuickWindow *window)
1197{
1198 qCDebug(QSG_LOG_RENDERLOOP) << "handleExposure()" << window;
1199
1200 Window *w = windowFor(m_windows, window);
1201 if (!w) {
1202 qCDebug(QSG_LOG_RENDERLOOP, "- adding window to list");
1203 Window win;
1204 win.window = window;
1205 win.actualWindowFormat = window->format();
1206 auto renderContext = QQuickWindowPrivate::get(window)->context;
1207 // The thread assumes ownership, so we don't need to delete it later.
1208 pendingRenderContexts.remove(renderContext);
1209 win.thread = new QSGRenderThread(this, renderContext);
1210 win.updateDuringSync = false;
1211 win.forceRenderPass = true; // also covered by polishAndSync(inExpose=true), but doesn't hurt
1212 m_windows << win;
1213 w = &m_windows.last();
1214 }
1215
1216 // set this early as we'll be rendering shortly anyway and this avoids
1217 // specialcasing exposure in polishAndSync.
1218 w->thread->window = window;
1219
1220 if (w->window->width() <= 0 || w->window->height() <= 0
1221 || (w->window->isTopLevel() && !w->window->geometry().intersects(w->window->screen()->availableGeometry()))) {
1222#ifndef QT_NO_DEBUG
1223 qWarning().noquote().nospace() << "QSGThreadedRenderLoop: expose event received for window "
1224 << w->window << " with invalid geometry: " << w->window->geometry()
1225 << " on " << w->window->screen();
1226#endif
1227 }
1228
1229 // Because we are going to bind a GL context to it, make sure it
1230 // is created.
1231 if (!w->window->handle())
1232 w->window->create();
1233
1234 // Start render thread if it is not running
1235 if (!w->thread->isRunning()) {
1236 qCDebug(QSG_LOG_RENDERLOOP, "- starting render thread");
1237
1238 w->thread->enableRhi = QSGRhiSupport::instance()->isRhiEnabled();
1239 if (w->thread->enableRhi) {
1240 if (!w->thread->rhi) {
1241 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
1242 w->thread->offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
1243 window->installEventFilter(this);
1244 }
1245 } else {
1246 if (!w->thread->gl) {
1247 w->thread->gl = new QOpenGLContext();
1248 if (qt_gl_global_share_context())
1249 w->thread->gl->setShareContext(qt_gl_global_share_context());
1250 w->thread->gl->setFormat(w->window->requestedFormat());
1251 w->thread->gl->setScreen(w->window->screen());
1252 if (!w->thread->gl->create()) {
1253 const bool isEs = w->thread->gl->isOpenGLES();
1254 delete w->thread->gl;
1255 w->thread->gl = nullptr;
1256 handleContextCreationFailure(w->window, isEs);
1257 return;
1258 }
1259
1260 QQuickWindowPrivate::get(w->window)->fireOpenGLContextCreated(w->thread->gl);
1261
1262 w->thread->gl->moveToThread(w->thread);
1263 qCDebug(QSG_LOG_RENDERLOOP, "- OpenGL context created");
1264
1265 w->thread->offscreenSurface = new QOffscreenSurface();
1266 w->thread->offscreenSurface->setFormat(w->actualWindowFormat);
1267 w->thread->offscreenSurface->create();
1268 }
1269 }
1270
1271 QQuickAnimatorController *controller = QQuickWindowPrivate::get(w->window)->animationController;
1272 if (controller->thread() != w->thread)
1273 controller->moveToThread(w->thread);
1274
1275 w->thread->active = true;
1276 if (w->thread->thread() == QThread::currentThread()) {
1277 w->thread->sgrc->moveToThread(w->thread);
1278 w->thread->moveToThread(w->thread);
1279 }
1280 w->thread->start();
1281 if (!w->thread->isRunning())
1282 qFatal("Render thread failed to start, aborting application.");
1283
1284 } else {
1285 qCDebug(QSG_LOG_RENDERLOOP, "- render thread already running");
1286 }
1287
1288 polishAndSync(w, true);
1289 qCDebug(QSG_LOG_RENDERLOOP, "- done with handleExposure()");
1290
1291 startOrStopAnimationTimer();
1292}
1293
1294/*
1295 This function posts an event to the render thread to remove the window
1296 from the list of windowses to render.
1297
1298 It also starts up the non-vsync animation tick if no more windows
1299 are showing.
1300 */
1301void QSGThreadedRenderLoop::handleObscurity(Window *w)
1302{
1303 qCDebug(QSG_LOG_RENDERLOOP) << "handleObscurity()" << w->window;
1304 if (w->thread->isRunning()) {
1305 w->thread->mutex.lock();
1306 w->thread->postEvent(new WMWindowEvent(w->window, WM_Obscure));
1307 w->thread->waitCondition.wait(&w->thread->mutex);
1308 w->thread->mutex.unlock();
1309 }
1310 startOrStopAnimationTimer();
1311}
1312
1313bool QSGThreadedRenderLoop::eventFilter(QObject *watched, QEvent *event)
1314{
1315 switch (event->type()) {
1316 case QEvent::PlatformSurface:
1317 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
1318 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
1319 QQuickWindow *window = qobject_cast<QQuickWindow *>(watched);
1320 if (window) {
1321 Window *w = windowFor(m_windows, window);
1322 if (w) {
1323 w->thread->mutex.lock();
1324 w->thread->postEvent(new WMReleaseSwapchainEvent(window));
1325 w->thread->waitCondition.wait(&w->thread->mutex);
1326 w->thread->mutex.unlock();
1327 }
1328 window->removeEventFilter(this);
1329 }
1330 }
1331 break;
1332 default:
1333 break;
1334 }
1335 return QObject::eventFilter(watched, event);
1336}
1337
1338void QSGThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
1339{
1340 qCDebug(QSG_LOG_RENDERLOOP, "- polish and sync update request");
1341 Window *w = windowFor(m_windows, window);
1342 if (w)
1343 polishAndSync(w);
1344}
1345
1346void QSGThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
1347{
1348 Window *w = windowFor(m_windows, window);
1349 if (w)
1350 maybeUpdate(w);
1351}
1352
1353/*
1354 Called whenever the QML scene has changed. Will post an event to
1355 ourselves that a sync is needed.
1356 */
1357void QSGThreadedRenderLoop::maybeUpdate(Window *w)
1358{
1359 if (!QCoreApplication::instance())
1360 return;
1361
1362 if (!w || !w->thread->isRunning())
1363 return;
1364
1365 QThread *current = QThread::currentThread();
1366 if (current != QCoreApplication::instance()->thread() && (current != w->thread || !m_lockedForSync)) {
1367 qWarning() << "Updates can only be scheduled from GUI thread or from QQuickItem::updatePaintNode()";
1368 return;
1369 }
1370
1371 qCDebug(QSG_LOG_RENDERLOOP) << "update from item" << w->window;
1372
1373 // Call this function from the Gui thread later as startTimer cannot be
1374 // called from the render thread.
1375 if (current == w->thread) {
1376 qCDebug(QSG_LOG_RENDERLOOP, "- on render thread");
1377 w->updateDuringSync = true;
1378 return;
1379 }
1380
1381 maybePostPolishRequest(w);
1382}
1383
1384/*
1385 Called when the QQuickWindow should be explicitly repainted. This function
1386 can also be called on the render thread when the GUI thread is blocked to
1387 keep render thread animations alive.
1388 */
1389void QSGThreadedRenderLoop::update(QQuickWindow *window)
1390{
1391 Window *w = windowFor(m_windows, window);
1392 if (!w)
1393 return;
1394
1395 if (w->thread == QThread::currentThread()) {
1396 qCDebug(QSG_LOG_RENDERLOOP) << "update on window - on render thread" << w->window;
1397 w->thread->requestRepaint();
1398 return;
1399 }
1400
1401 qCDebug(QSG_LOG_RENDERLOOP) << "update on window" << w->window;
1402 // We set forceRenderPass because we want to make sure the QQuickWindow
1403 // actually does a full render pass after the next sync.
1404 w->forceRenderPass = true;
1405 maybeUpdate(w);
1406}
1407
1408
1409void QSGThreadedRenderLoop::releaseResources(QQuickWindow *window)
1410{
1411 Window *w = windowFor(m_windows, window);
1412 if (w)
1413 releaseResources(w, false);
1414}
1415
1416/*
1417 * Release resources will post an event to the render thread to
1418 * free up the SG and GL resources and exists the render thread.
1419 */
1420void QSGThreadedRenderLoop::releaseResources(Window *w, bool inDestructor)
1421{
1422 qCDebug(QSG_LOG_RENDERLOOP) << "releaseResources()" << (inDestructor ? "in destructor" : "in api-call") << w->window;
1423
1424 w->thread->mutex.lock();
1425 if (w->thread->isRunning() && w->thread->active) {
1426 QQuickWindow *window = w->window;
1427
1428 // The platform window might have been destroyed before
1429 // hide/release/windowDestroyed is called, so we may need to have a
1430 // fallback surface to perform the cleanup of the scene graph and the
1431 // OpenGL resources. QOffscreenSurface must be created on the GUI
1432 // thread so that is done for us already.
1433
1434 qCDebug(QSG_LOG_RENDERLOOP, "- posting release request to render thread");
1435 w->thread->postEvent(new WMTryReleaseEvent(window, inDestructor, window->handle() == nullptr));
1436 w->thread->waitCondition.wait(&w->thread->mutex);
1437
1438 // Avoid a shutdown race condition.
1439 // If SG is invalidated and 'active' becomes false, the thread's run()
1440 // method will exit. handleExposure() relies on QThread::isRunning() (because it
1441 // potentially needs to start the thread again) and our mutex cannot be used to
1442 // track the thread stopping, so we wait a few nanoseconds extra so the thread
1443 // can exit properly.
1444 if (!w->thread->active) {
1445 qCDebug(QSG_LOG_RENDERLOOP) << " - waiting for render thread to exit" << w->window;
1446 w->thread->wait();
1447 qCDebug(QSG_LOG_RENDERLOOP) << " - render thread finished" << w->window;
1448 }
1449 }
1450 w->thread->mutex.unlock();
1451}
1452
1453
1454/* Calls polish on all items, then requests synchronization with the render thread
1455 * and blocks until that is complete. Returns false if it aborted; otherwise true.
1456 */
1457void QSGThreadedRenderLoop::polishAndSync(Window *w, bool inExpose)
1458{
1459 qCDebug(QSG_LOG_RENDERLOOP) << "polishAndSync" << (inExpose ? "(in expose)" : "(normal)") << w->window;
1460
1461 QQuickWindow *window = w->window;
1462 if (!w->thread || !w->thread->window) {
1463 qCDebug(QSG_LOG_RENDERLOOP, "- not exposed, abort");
1464 return;
1465 }
1466
1467 // Flush pending touch events.
1468 QQuickWindowPrivate::get(window)->flushFrameSynchronousEvents();
1469 // The delivery of the event might have caused the window to stop rendering
1470 w = windowFor(m_windows, window);
1471 if (!w || !w->thread || !w->thread->window) {
1472 qCDebug(QSG_LOG_RENDERLOOP, "- removed after event flushing, abort");
1473 return;
1474 }
1475
1476
1477 QElapsedTimer timer;
1478 qint64 polishTime = 0;
1479 qint64 waitTime = 0;
1480 qint64 syncTime = 0;
1481 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
1482 if (profileFrames)
1483 timer.start();
1484 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishAndSync);
1485
1486 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
1487 d->polishItems();
1488
1489 if (profileFrames)
1490 polishTime = timer.nsecsElapsed();
1491 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1492 QQuickProfiler::SceneGraphPolishAndSyncPolish);
1493
1494 w->updateDuringSync = false;
1495
1496 emit window->afterAnimating();
1497
1498 qCDebug(QSG_LOG_RENDERLOOP, "- lock for sync");
1499 w->thread->mutex.lock();
1500 m_lockedForSync = true;
1501 w->thread->postEvent(new WMSyncEvent(window, inExpose, w->forceRenderPass));
1502 w->forceRenderPass = false;
1503
1504 qCDebug(QSG_LOG_RENDERLOOP, "- wait for sync");
1505 if (profileFrames)
1506 waitTime = timer.nsecsElapsed();
1507 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1508 QQuickProfiler::SceneGraphPolishAndSyncWait);
1509 w->thread->waitCondition.wait(&w->thread->mutex);
1510 m_lockedForSync = false;
1511 w->thread->mutex.unlock();
1512 qCDebug(QSG_LOG_RENDERLOOP, "- unlock after sync");
1513
1514 if (profileFrames)
1515 syncTime = timer.nsecsElapsed();
1516 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphPolishAndSync,
1517 QQuickProfiler::SceneGraphPolishAndSyncSync);
1518
1519 if (m_animation_timer == 0 && m_animation_driver->isRunning()) {
1520 qCDebug(QSG_LOG_RENDERLOOP, "- advancing animations");
1521 m_animation_driver->advance();
1522 qCDebug(QSG_LOG_RENDERLOOP, "- animations done..");
1523 // We need to trigger another sync to keep animations running...
1524 maybePostPolishRequest(w);
1525 emit timeToIncubate();
1526 } else if (w->updateDuringSync) {
1527 maybePostPolishRequest(w);
1528 }
1529
1530 qCDebug(QSG_LOG_TIME_RENDERLOOP()).nospace()
1531 << "Frame prepared with 'threaded' renderloop"
1532 << ", polish=" << (polishTime / 1000000)
1533 << ", lock=" << (waitTime - polishTime) / 1000000
1534 << ", blockedForSync=" << (syncTime - waitTime) / 1000000
1535 << ", animations=" << (timer.nsecsElapsed() - syncTime) / 1000000
1536 << " - (on Gui thread) " << window;
1537
1538 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphPolishAndSync,
1539 QQuickProfiler::SceneGraphPolishAndSyncAnimations);
1540}
1541
1542bool QSGThreadedRenderLoop::event(QEvent *e)
1543{
1544 switch ((int) e->type()) {
1545
1546 case QEvent::Timer: {
1547 QTimerEvent *te = static_cast<QTimerEvent *>(e);
1548 if (te->timerId() == m_animation_timer) {
1549 qCDebug(QSG_LOG_RENDERLOOP, "- ticking non-visual timer");
1550 m_animation_driver->advance();
1551 emit timeToIncubate();
1552 return true;
1553 }
1554 }
1555
1556 default:
1557 break;
1558 }
1559
1560 return QObject::event(e);
1561}
1562
1563
1564
1565/*
1566 Locks down GUI and performs a grab the scene graph, then returns the result.
1567
1568 Since the QML scene could have changed since the last time it was rendered,
1569 we need to polish and sync the scene graph. This might seem superfluous, but
1570 - QML changes could have triggered deleteLater() which could have removed
1571 textures or other objects from the scene graph, causing render to crash.
1572 - Autotests rely on grab(), setProperty(), grab(), compare behavior.
1573 */
1574
1575QImage QSGThreadedRenderLoop::grab(QQuickWindow *window)
1576{
1577 qCDebug(QSG_LOG_RENDERLOOP) << "grab()" << window;
1578
1579 Window *w = windowFor(m_windows, window);
1580 Q_ASSERT(w);
1581
1582 if (!w->thread->isRunning())
1583 return QImage();
1584
1585 if (!window->handle())
1586 window->create();
1587
1588 qCDebug(QSG_LOG_RENDERLOOP, "- polishing items");
1589 QQuickWindowPrivate *d = QQuickWindowPrivate::get(window);
1590 d->polishItems();
1591
1592 QImage result;
1593 w->thread->mutex.lock();
1594 m_lockedForSync = true;
1595 qCDebug(QSG_LOG_RENDERLOOP, "- posting grab event");
1596 w->thread->postEvent(new WMGrabEvent(window, &result));
1597 w->thread->waitCondition.wait(&w->thread->mutex);
1598 m_lockedForSync = false;
1599 w->thread->mutex.unlock();
1600
1601 qCDebug(QSG_LOG_RENDERLOOP, "- grab complete");
1602
1603 return result;
1604}
1605
1606/*
1607 * Posts a new job event to the render thread.
1608 * Returns true if posting succeeded.
1609 */
1610void QSGThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
1611{
1612 Window *w = windowFor(m_windows, window);
1613 if (w && w->thread && w->thread->window)
1614 w->thread->postEvent(new WMJobEvent(window, job));
1615 else
1616 delete job;
1617}
1618
1619#include "qsgthreadedrenderloop.moc"
1620#include "moc_qsgthreadedrenderloop_p.cpp"
1621
1622QT_END_NAMESPACE
1623