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
65QT_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.
69const 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.
72const 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.
76const QEvent::Type WM_TryRelease = QEvent::Type(QEvent::User + 4);
77
78// Passed by the RL to the RT when a QQuickWindow::grabWindow() is called.
79const QEvent::Type WM_Grab = QEvent::Type(QEvent::User + 5);
80
81// Passed by the window when there is a render job to run.
82const QEvent::Type WM_PostJob = QEvent::Type(QEvent::User + 6);
83
84class QSGSoftwareWindowEvent : public QEvent
85{
86public:
87 QSGSoftwareWindowEvent(QQuickWindow *c, QEvent::Type type) : QEvent(type), window(c) { }
88 QQuickWindow *window;
89};
90
91class QSGSoftwareTryReleaseEvent : public QSGSoftwareWindowEvent
92{
93public:
94 QSGSoftwareTryReleaseEvent(QQuickWindow *win, bool destroy)
95 : QSGSoftwareWindowEvent(win, WM_TryRelease), destroying(destroy) { }
96 bool destroying;
97};
98
99class QSGSoftwareSyncEvent : public QSGSoftwareWindowEvent
100{
101public:
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
114class QSGSoftwareGrabEvent : public QSGSoftwareWindowEvent
115{
116public:
117 QSGSoftwareGrabEvent(QQuickWindow *c, QImage *result)
118 : QSGSoftwareWindowEvent(c, WM_Grab), image(result) { }
119 QImage *image;
120};
121
122class QSGSoftwareJobEvent : public QSGSoftwareWindowEvent
123{
124public:
125 QSGSoftwareJobEvent(QQuickWindow *c, QRunnable *postedJob)
126 : QSGSoftwareWindowEvent(c, WM_PostJob), job(postedJob) { }
127 ~QSGSoftwareJobEvent() { delete job; }
128 QRunnable *job;
129};
130
131class QSGSoftwareEventQueue : public QQueue<QEvent *>
132{
133public:
134 void addEvent(QEvent *e) {
135 mutex.lock();
136 enqueue(t: e);
137 if (waiting)
138 condition.wakeOne();
139 mutex.unlock();
140 }
141
142 QEvent *takeEvent(bool wait) {
143 mutex.lock();
144 if (isEmpty() && wait) {
145 waiting = true;
146 condition.wait(lockedMutex: &mutex);
147 waiting = false;
148 }
149 QEvent *e = dequeue();
150 mutex.unlock();
151 return e;
152 }
153
154 bool hasMoreEvents() {
155 mutex.lock();
156 bool has = !isEmpty();
157 mutex.unlock();
158 return has;
159 }
160
161private:
162 QMutex mutex;
163 QWaitCondition condition;
164 bool waiting = false;
165};
166
167static 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
173class QSGSoftwareRenderThread : public QThread
174{
175 Q_OBJECT
176public:
177 QSGSoftwareRenderThread(QSGSoftwareThreadedRenderLoop *rl, QSGRenderContext *renderContext)
178 : renderLoop(rl)
179 {
180 rc = static_cast<QSGSoftwareRenderContext *>(renderContext);
181 vsyncDelta = qsgrl_animation_interval();
182 }
183
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
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
232public slots:
233 void onSceneGraphChanged() {
234 syncResultedInChanges = true;
235 }
236};
237
238bool 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(c: 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(size: 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(c: wme->window);
289 if (wme->destroying) {
290 // Bye bye nodes...
291 wd->cleanupNodesOnShutdown();
292 }
293 rc->invalidate();
294 QCoreApplication::processEvents();
295 QCoreApplication::sendPostedEvents(receiver: nullptr, event_type: 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(c: wme->window);
326 auto softwareRenderer = static_cast<QSGSoftwareRenderer*>(wd->renderer);
327 if (softwareRenderer)
328 softwareRenderer->setBackingStore(backingStore);
329 rc->initialize(params: nullptr);
330 wd->syncSceneGraph();
331 rc->endSync();
332 wd->renderSceneGraph(size: 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(event: e);
359}
360
361void QSGSoftwareRenderThread::postEvent(QEvent *e)
362{
363 eventQueue.addEvent(e);
364}
365
366void QSGSoftwareRenderThread::processEvents()
367{
368 while (eventQueue.hasMoreEvents()) {
369 QEvent *e = eventQueue.takeEvent(wait: false);
370 event(e);
371 delete e;
372 }
373}
374
375void QSGSoftwareRenderThread::processEventsAndWaitForMore()
376{
377 stopEventProcessing = false;
378 while (!stopEventProcessing) {
379 QEvent *e = eventQueue.takeEvent(wait: true);
380 event(e);
381 delete e;
382 }
383}
384
385void QSGSoftwareRenderThread::run()
386{
387 qCDebug(QSG_RASTER_LOG_RENDERLOOP, "RT - run()");
388
389 rtAnim = rc->sceneGraphContext()->createAnimationDriver(parent: 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(thread: renderLoop->thread());
418 moveToThread(thread: renderLoop->thread());
419}
420
421void 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(c: 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(params: 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(sender: wd->renderer, signal: &QSGRenderer::sceneGraphChanged, receiver: this,
444 slot: &QSGSoftwareRenderThread::onSceneGraphChanged, type: 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(receiver: nullptr, event_type: 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
460void 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(c: 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(inExpose: 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(size: 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(region: 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
550template<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
560QSGSoftwareThreadedRenderLoop::QSGSoftwareThreadedRenderLoop()
561{
562 qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop constructor");
563 m_sg = new QSGSoftwareContext;
564 m_anim = m_sg->createAnimationDriver(parent: this);
565 connect(sender: m_anim, signal: &QAnimationDriver::started, receiver: this, slot: &QSGSoftwareThreadedRenderLoop::onAnimationStarted);
566 connect(sender: m_anim, signal: &QAnimationDriver::stopped, receiver: this, slot: &QSGSoftwareThreadedRenderLoop::onAnimationStopped);
567 m_anim->install();
568}
569
570QSGSoftwareThreadedRenderLoop::~QSGSoftwareThreadedRenderLoop()
571{
572 qCDebug(QSG_RASTER_LOG_RENDERLOOP, "software threaded render loop destructor");
573 delete m_sg;
574}
575
576void QSGSoftwareThreadedRenderLoop::show(QQuickWindow *window)
577{
578 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "show" << window;
579}
580
581void QSGSoftwareThreadedRenderLoop::hide(QQuickWindow *window)
582{
583 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "hide" << window;
584
585 if (window->isExposed())
586 handleObscurity(w: windowFor(list: m_windows, window));
587
588 releaseResources(window);
589}
590
591void 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
599void QSGSoftwareThreadedRenderLoop::windowDestroyed(QQuickWindow *window)
600{
601 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "window destroyed" << window;
602
603 WindowData *w = windowFor(list: m_windows, window);
604 if (!w)
605 return;
606
607 handleObscurity(w);
608 handleResourceRelease(w, destroying: 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
630void 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(list: m_windows, window);
638 if (w)
639 handleObscurity(w);
640 }
641}
642
643QImage QSGSoftwareThreadedRenderLoop::grab(QQuickWindow *window)
644{
645 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "grab" << window;
646
647 WindowData *w = windowFor(list: 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(list: 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(c: window);
664 wd->polishItems();
665
666 QImage result;
667 w->thread->mutex.lock();
668 lockedForSync = true;
669 w->thread->postEvent(e: new QSGSoftwareGrabEvent(window, &result));
670 w->thread->waitCondition.wait(lockedMutex: &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
682void QSGSoftwareThreadedRenderLoop::update(QQuickWindow *window)
683{
684 WindowData *w = windowFor(list: 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
699void QSGSoftwareThreadedRenderLoop::maybeUpdate(QQuickWindow *window)
700{
701 WindowData *w = windowFor(list: m_windows, window);
702 if (w)
703 scheduleUpdate(w);
704}
705
706void QSGSoftwareThreadedRenderLoop::handleUpdateRequest(QQuickWindow *window)
707{
708 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleUpdateRequest" << window;
709
710 WindowData *w = windowFor(list: m_windows, window);
711 if (w)
712 polishAndSync(w, inExpose: false);
713}
714
715QAnimationDriver *QSGSoftwareThreadedRenderLoop::animationDriver() const
716{
717 return m_anim;
718}
719
720QSGContext *QSGSoftwareThreadedRenderLoop::sceneGraphContext() const
721{
722 return m_sg;
723}
724
725QSGRenderContext *QSGSoftwareThreadedRenderLoop::createRenderContext(QSGContext *) const
726{
727 return m_sg->createRenderContext();
728}
729
730void QSGSoftwareThreadedRenderLoop::releaseResources(QQuickWindow *window)
731{
732 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "releaseResources" << window;
733
734 WindowData *w = windowFor(list: m_windows, window);
735 if (w)
736 handleResourceRelease(w, destroying: false);
737}
738
739void QSGSoftwareThreadedRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
740{
741 WindowData *w = windowFor(list: m_windows, window);
742 if (w && w->thread && w->thread->exposedWindow)
743 w->thread->postEvent(e: new QSGSoftwareJobEvent(window, job));
744 else
745 delete job;
746}
747
748QSurface::SurfaceType QSGSoftwareThreadedRenderLoop::windowSurfaceType() const
749{
750 return QSurface::RasterSurface;
751}
752
753bool 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
765int QSGSoftwareThreadedRenderLoop::flags() const
766{
767 return SupportsGrabWithoutExpose;
768}
769
770bool 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(event: e);
782}
783
784void QSGSoftwareThreadedRenderLoop::onAnimationStarted()
785{
786 startOrStopAnimationTimer();
787
788 for (const WindowData &w : qAsConst(t&: m_windows))
789 w.window->requestUpdate();
790}
791
792void QSGSoftwareThreadedRenderLoop::onAnimationStopped()
793{
794 startOrStopAnimationTimer();
795}
796
797void 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(id: 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(interval: qsgrl_animation_interval());
818 }
819}
820
821void QSGSoftwareThreadedRenderLoop::handleExposure(QQuickWindow *window)
822{
823 qCDebug(QSG_RASTER_LOG_RENDERLOOP) << "handleExposure" << window;
824
825 WindowData *w = windowFor(list: 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(c: 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(t: 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(r: 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(c: w->window)->animationController.get();
860 if (controller->thread() != w->thread)
861 controller->moveToThread(thread: w->thread);
862 if (w->thread->thread() == QThread::currentThread()) {
863 w->thread->rc->moveToThread(thread: w->thread);
864 w->thread->moveToThread(thread: w->thread);
865 }
866
867 w->thread->active = true;
868 w->thread->start();
869
870 if (!w->thread->isRunning())
871 qFatal(msg: "Render thread failed to start, aborting application.");
872 }
873
874 polishAndSync(w, inExpose: true);
875
876 startOrStopAnimationTimer();
877}
878
879void 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(e: new QSGSoftwareWindowEvent(w->window, WM_Obscure));
886 w->thread->waitCondition.wait(lockedMutex: &w->thread->mutex);
887 w->thread->mutex.unlock();
888 }
889
890 startOrStopAnimationTimer();
891}
892
893void 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
915void 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(e: new QSGSoftwareTryReleaseEvent(window, destroying));
928 w->thread->waitCondition.wait(lockedMutex: &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
942void 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(c: window)->flushFrameSynchronousEvents();
954 // The delivery of the event might have caused the window to stop rendering
955 w = windowFor(list: 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(c: 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(e: 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(lockedMutex: &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
1018QT_END_NAMESPACE
1019

source code of qtdeclarative/src/quick/scenegraph/adaptations/software/qsgsoftwarethreadedrenderloop.cpp