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

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