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 "qsgrenderloop_p.h"
41#include "qsgthreadedrenderloop_p.h"
42#include "qsgwindowsrenderloop_p.h"
43#include "qsgrhisupport_p.h"
44#include <private/qquickanimatorcontroller_p.h>
45
46#include <QtCore/QCoreApplication>
47#include <QtCore/QTime>
48#include <QtCore/QLibraryInfo>
49#include <QtCore/private/qabstractanimation_p.h>
50
51#include <QtGui/QOffscreenSurface>
52#include <QtGui/private/qguiapplication_p.h>
53#include <qpa/qplatformintegration.h>
54#include <QPlatformSurfaceEvent>
55
56#include <QtQml/private/qqmlglobal_p.h>
57
58#include <QtQuick/QQuickWindow>
59#include <QtQuick/private/qquickwindow_p.h>
60#include <QtQuick/private/qsgcontext_p.h>
61#include <QtQuick/private/qsgrenderer_p.h>
62#include <private/qquickprofiler_p.h>
63#include <qtquick_tracepoints_p.h>
64
65#include <private/qsgrhishadereffectnode_p.h>
66
67#if QT_CONFIG(opengl)
68#include <QtGui/QOpenGLContext>
69#if QT_CONFIG(quick_shadereffect)
70#include <private/qquickopenglshadereffectnode_p.h>
71#endif
72#include <private/qsgdefaultrendercontext_p.h>
73#endif
74
75#ifdef Q_OS_WIN
76#include <QtCore/qt_windows.h>
77#endif
78
79QT_BEGIN_NAMESPACE
80
81extern bool qsg_useConsistentTiming();
82extern Q_GUI_EXPORT QImage qt_gl_read_framebuffer(const QSize &size, bool alpha_format, bool include_alpha);
83
84// ### We do not yet support using Qt Quick with QRhi (and Vulkan, D3D
85// or Metal) in -no-opengl builds as of Qt 5.14. This is due to to the
86// widespread direct OpenGL usage all over the place in qsgdefault*
87// and the related classes. To be cleaned up in Qt 6 when the direct
88// GL code path is removed.
89
90#if QT_CONFIG(opengl) /* || QT_CONFIG(vulkan) || defined(Q_OS_WIN) || defined(Q_OS_DARWIN) */
91
92#define ENABLE_DEFAULT_BACKEND
93
94/*
95 expectations for this manager to work:
96 - one opengl context to render multiple windows
97 - OpenGL pipeline will not block for vsync in swap
98 - OpenGL pipeline will block based on a full buffer queue.
99 - Multiple screens can share the OpenGL context
100 - Animations are advanced for all windows once per swap
101 */
102
103DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP);
104DEFINE_BOOL_CONFIG_OPTION(qmlForceThreadedRenderer, QML_FORCE_THREADED_RENDERER); // Might trigger graphics driver threading bugs, use at own risk
105#endif
106
107QSGRenderLoop *QSGRenderLoop::s_instance = nullptr;
108
109QSGRenderLoop::~QSGRenderLoop()
110{
111}
112
113void QSGRenderLoop::cleanup()
114{
115 if (!s_instance)
116 return;
117 for (QQuickWindow *w : s_instance->windows()) {
118 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: w);
119 if (wd->windowManager == s_instance) {
120 s_instance->windowDestroyed(window: w);
121 wd->windowManager = nullptr;
122 }
123 }
124 delete s_instance;
125 s_instance = nullptr;
126
127#ifdef ENABLE_DEFAULT_BACKEND
128 QSGRhiSupport::instance()->cleanup();
129 QSGRhiProfileConnection::instance()->cleanup();
130#endif
131}
132
133QSurface::SurfaceType QSGRenderLoop::windowSurfaceType() const
134{
135#ifdef ENABLE_DEFAULT_BACKEND
136 return QSGRhiSupport::instance()->windowSurfaceType();
137#else
138 return QSurface::RasterSurface;
139#endif
140}
141
142void QSGRenderLoop::postJob(QQuickWindow *window, QRunnable *job)
143{
144 Q_ASSERT(job);
145#ifdef ENABLE_DEFAULT_BACKEND
146 Q_ASSERT(window);
147 if (!QSGRhiSupport::instance()->isRhiEnabled()) {
148 if (window->openglContext()) {
149 window->openglContext()->makeCurrent(surface: window);
150 job->run();
151 }
152 } else {
153 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
154 if (cd->rhi)
155 cd->rhi->makeThreadLocalNativeContextCurrent();
156 job->run();
157 }
158#else
159 Q_UNUSED(window)
160 job->run();
161#endif
162 delete job;
163}
164
165#ifdef ENABLE_DEFAULT_BACKEND
166class QSGGuiThreadRenderLoop : public QSGRenderLoop
167{
168 Q_OBJECT
169public:
170 QSGGuiThreadRenderLoop();
171 ~QSGGuiThreadRenderLoop();
172
173 void show(QQuickWindow *window) override;
174 void hide(QQuickWindow *window) override;
175
176 void windowDestroyed(QQuickWindow *window) override;
177
178 void renderWindow(QQuickWindow *window);
179 void exposureChanged(QQuickWindow *window) override;
180 QImage grab(QQuickWindow *window) override;
181
182 void maybeUpdate(QQuickWindow *window) override;
183 void update(QQuickWindow *window) override { maybeUpdate(window); } // identical for this implementation.
184 void handleUpdateRequest(QQuickWindow *) override;
185
186 void releaseResources(QQuickWindow *) override;
187
188 QAnimationDriver *animationDriver() const override { return nullptr; }
189
190 QSGContext *sceneGraphContext() const override;
191 QSGRenderContext *createRenderContext(QSGContext *) const override { return rc; }
192
193 void releaseSwapchain(QQuickWindow *window);
194 void handleDeviceLoss();
195
196 bool eventFilter(QObject *watched, QEvent *event) override;
197
198 struct WindowData {
199 WindowData()
200 : updatePending(false),
201 grabOnly(false),
202 rhiDeviceLost(false),
203 rhiDoomed(false)
204 { }
205 bool updatePending : 1;
206 bool grabOnly : 1;
207 bool rhiDeviceLost : 1;
208 bool rhiDoomed : 1;
209 };
210
211 QHash<QQuickWindow *, WindowData> m_windows;
212
213 QOpenGLContext *gl = nullptr;
214 QOffscreenSurface *offscreenSurface = nullptr;
215 QRhi *rhi = nullptr;
216 QSGContext *sg;
217 QSGRenderContext *rc;
218
219 QImage grabContent;
220};
221#endif
222
223QSGRenderLoop *QSGRenderLoop::instance()
224{
225 if (!s_instance) {
226
227 QSGRhiSupport::checkEnvQSgInfo();
228
229 s_instance = QSGContext::createWindowManager();
230#ifdef ENABLE_DEFAULT_BACKEND
231 if (!s_instance) {
232 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
233
234 QSGRenderLoopType loopType;
235 if (rhiSupport->isRhiEnabled() && rhiSupport->rhiBackend() != QRhi::OpenGLES2) {
236 loopType = ThreadedRenderLoop;
237 } else {
238 loopType = BasicRenderLoop;
239#ifdef Q_OS_WIN
240 // With desktop OpenGL (opengl32.dll), use threaded. Otherwise (ANGLE) use windows.
241 if (QOpenGLContext::openGLModuleType() == QOpenGLContext::LibGL
242 && QGuiApplicationPrivate::platformIntegration()->hasCapability(QPlatformIntegration::ThreadedOpenGL))
243 {
244 loopType = ThreadedRenderLoop;
245 } else {
246 loopType = WindowsRenderLoop;
247 }
248#else
249 if (QGuiApplicationPrivate::platformIntegration()->hasCapability(cap: QPlatformIntegration::ThreadedOpenGL))
250 loopType = ThreadedRenderLoop;
251#endif
252 }
253
254 if (rhiSupport->isRhiEnabled()) {
255 switch (rhiSupport->rhiBackend()) {
256 case QRhi::Null:
257 loopType = BasicRenderLoop;
258 break;
259
260 case QRhi::D3D11:
261 // The threaded loop's model may not be suitable for DXGI
262 // due to the possibility of having the main thread (with
263 // the Windows message pump) blocked while issuing a
264 // Present on the render thread. However, according to the
265 // docs this can be a problem for fullscreen swapchains
266 // only. So leave threaded enabled by default for now and
267 // revisit later if there are problems.
268 break;
269
270 default:
271 break;
272 }
273
274 // no 'windows' because that's not yet ported to the rhi
275 if (loopType == WindowsRenderLoop)
276 loopType = BasicRenderLoop;
277 }
278
279 // The environment variables can always override. This is good
280 // because in some situations it makes perfect sense to try out a
281 // render loop that is otherwise disabled by default.
282
283 if (qmlNoThreadedRenderer())
284 loopType = BasicRenderLoop;
285 else if (qmlForceThreadedRenderer())
286 loopType = ThreadedRenderLoop;
287
288 if (Q_UNLIKELY(qEnvironmentVariableIsSet("QSG_RENDER_LOOP"))) {
289 const QByteArray loopName = qgetenv(varName: "QSG_RENDER_LOOP");
290 if (loopName == "windows")
291 loopType = WindowsRenderLoop;
292 else if (loopName == "basic")
293 loopType = BasicRenderLoop;
294 else if (loopName == "threaded")
295 loopType = ThreadedRenderLoop;
296 }
297
298 switch (loopType) {
299#if QT_CONFIG(thread)
300 case ThreadedRenderLoop:
301 qCDebug(QSG_LOG_INFO, "threaded render loop");
302 s_instance = new QSGThreadedRenderLoop();
303 break;
304#endif
305 case WindowsRenderLoop:
306 qCDebug(QSG_LOG_INFO, "windows render loop");
307 s_instance = new QSGWindowsRenderLoop();
308 break;
309 default:
310 qCDebug(QSG_LOG_INFO, "QSG: basic render loop");
311 s_instance = new QSGGuiThreadRenderLoop();
312 break;
313 }
314 }
315#endif
316 qAddPostRoutine(QSGRenderLoop::cleanup);
317 }
318
319 return s_instance;
320}
321
322void QSGRenderLoop::setInstance(QSGRenderLoop *instance)
323{
324 Q_ASSERT(!s_instance);
325 s_instance = instance;
326}
327
328void QSGRenderLoop::handleContextCreationFailure(QQuickWindow *window)
329{
330 QString translatedMessage;
331 QString untranslatedMessage;
332 if (QSGRhiSupport::instance()->isRhiEnabled()) {
333 QQuickWindowPrivate::rhiCreationFailureMessage(backendName: QSGRhiSupport::instance()->rhiBackendName(),
334 translatedMessage: &translatedMessage,
335 untranslatedMessage: &untranslatedMessage);
336 } else {
337 QQuickWindowPrivate::contextCreationFailureMessage(format: window->requestedFormat(),
338 translatedMessage: &translatedMessage,
339 untranslatedMessage: &untranslatedMessage);
340 }
341 // If there is a slot connected to the error signal, emit it and leave it to
342 // the application to do something with the message. If nothing is connected,
343 // show a message on our own and terminate.
344 const bool signalEmitted =
345 QQuickWindowPrivate::get(c: window)->emitError(error: QQuickWindow::ContextNotAvailable,
346 msg: translatedMessage);
347#if defined(Q_OS_WIN) && !defined(Q_OS_WINRT)
348 if (!signalEmitted && !QLibraryInfo::isDebugBuild() && !GetConsoleWindow()) {
349 MessageBox(0, (LPCTSTR) translatedMessage.utf16(),
350 (LPCTSTR)(QCoreApplication::applicationName().utf16()),
351 MB_OK | MB_ICONERROR);
352 }
353#endif // Q_OS_WIN && !Q_OS_WINRT
354 if (!signalEmitted)
355 qFatal(msg: "%s", qPrintable(untranslatedMessage));
356}
357
358#ifdef ENABLE_DEFAULT_BACKEND
359QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop()
360{
361 if (qsg_useConsistentTiming()) {
362 QUnifiedTimer::instance(create: true)->setConsistentTiming(true);
363 qCDebug(QSG_LOG_INFO, "using fixed animation steps");
364 }
365
366 sg = QSGContext::createDefaultContext();
367 rc = sg->createRenderContext();
368}
369
370QSGGuiThreadRenderLoop::~QSGGuiThreadRenderLoop()
371{
372 delete rc;
373 delete sg;
374}
375
376void QSGGuiThreadRenderLoop::show(QQuickWindow *window)
377{
378 m_windows[window] = WindowData();
379
380 maybeUpdate(window);
381}
382
383void QSGGuiThreadRenderLoop::hide(QQuickWindow *window)
384{
385 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
386 cd->fireAboutToStop();
387 if (m_windows.contains(akey: window))
388 m_windows[window].updatePending = false;
389}
390
391void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
392{
393 m_windows.remove(akey: window);
394 hide(window);
395 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: window);
396
397 bool current = false;
398 if (gl || rhi) {
399 if (rhi) {
400 // Direct OpenGL calls in user code need a current context, like
401 // when rendering; ensure this (no-op when not running on GL).
402 // Also works when there is no handle() anymore.
403 rhi->makeThreadLocalNativeContextCurrent();
404 current = true;
405 } else {
406 QSurface *surface = window;
407 // There may be no platform window if the window got closed.
408 if (!window->handle())
409 surface = offscreenSurface;
410 current = gl->makeCurrent(surface);
411 }
412 if (Q_UNLIKELY(!current))
413 qCDebug(QSG_LOG_RENDERLOOP, "cleanup without an OpenGL context");
414 }
415
416 if (d->swapchain) {
417 if (window->handle()) {
418 // We get here when exiting via QCoreApplication::quit() instead of
419 // through QWindow::close().
420 releaseSwapchain(window);
421 } else {
422 qWarning(msg: "QSGGuiThreadRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
423 window, d->swapchain);
424 }
425 }
426
427 d->cleanupNodesOnShutdown();
428
429#if QT_CONFIG(quick_shadereffect)
430 QSGRhiShaderEffectNode::cleanupMaterialTypeCache();
431#if QT_CONFIG(opengl)
432 QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache();
433#endif
434#endif
435
436 if (m_windows.size() == 0) {
437 rc->invalidate();
438 d->rhi = nullptr;
439 delete rhi;
440 rhi = nullptr;
441 delete gl;
442 gl = nullptr;
443 delete offscreenSurface;
444 offscreenSurface = nullptr;
445 } else if (gl && window == gl->surface() && current) {
446 if (!rhi)
447 gl->doneCurrent();
448 }
449
450 d->animationController.reset();
451}
452
453void QSGGuiThreadRenderLoop::handleDeviceLoss()
454{
455 if (!rhi || !rhi->isDeviceLost())
456 return;
457
458 qWarning(msg: "Graphics device lost, cleaning up scenegraph and releasing RHI");
459
460 for (auto it = m_windows.constBegin(), itEnd = m_windows.constEnd(); it != itEnd; ++it)
461 QQuickWindowPrivate::get(c: it.key())->cleanupNodesOnShutdown();
462
463 rc->invalidate();
464
465 for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
466 releaseSwapchain(window: it.key());
467 it->rhiDeviceLost = true;
468 }
469
470 delete rhi;
471 rhi = nullptr;
472}
473
474void QSGGuiThreadRenderLoop::releaseSwapchain(QQuickWindow *window)
475{
476 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
477 delete wd->rpDescForSwapchain;
478 wd->rpDescForSwapchain = nullptr;
479 delete wd->swapchain;
480 wd->swapchain = nullptr;
481 delete wd->depthStencilForSwapchain;
482 wd->depthStencilForSwapchain = nullptr;
483 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
484}
485
486bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event)
487{
488 switch (event->type()) {
489 case QEvent::PlatformSurface:
490 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
491 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
492 QQuickWindow *w = qobject_cast<QQuickWindow *>(object: watched);
493 if (w) {
494 releaseSwapchain(window: w);
495 w->removeEventFilter(obj: this);
496 }
497 }
498 break;
499 default:
500 break;
501 }
502 return QObject::eventFilter(watched, event);
503}
504
505void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
506{
507 if (!m_windows.contains(akey: window))
508 return;
509
510 WindowData &data = const_cast<WindowData &>(m_windows[window]);
511 bool alsoSwap = data.updatePending;
512 data.updatePending = false;
513
514 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
515 if (!cd->isRenderable())
516 return;
517
518 bool current = false;
519 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
520 int rhiSampleCount = 1;
521 const bool enableRhi = rhiSupport->isRhiEnabled();
522
523 if (enableRhi && !rhi) {
524 // This block below handles both the initial QRhi initialization and
525 // also the subsequent reinitialization attempts after a device lost
526 // (reset) situation.
527
528 if (data.rhiDoomed) // no repeated attempts if the initial attempt failed
529 return;
530
531 if (!offscreenSurface)
532 offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
533
534 rhi = rhiSupport->createRhi(window, offscreenSurface);
535
536 if (rhi) {
537 if (rhiSupport->isProfilingRequested())
538 QSGRhiProfileConnection::instance()->initialize(rhi);
539
540 data.rhiDeviceLost = false;
541
542 current = true;
543 rhi->makeThreadLocalNativeContextCurrent();
544
545 // The sample count cannot vary between windows as we use the same
546 // rendercontext for all of them. Decide it here and now.
547 rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
548
549 cd->rhi = rhi; // set this early in case something hooked up to rc initialized() accesses it
550
551 QSGDefaultRenderContext::InitParams rcParams;
552 rcParams.rhi = rhi;
553 rcParams.sampleCount = rhiSampleCount;
554 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
555 rcParams.maybeSurface = window;
556 cd->context->initialize(params: &rcParams);
557 } else {
558 if (!data.rhiDeviceLost) {
559 data.rhiDoomed = true;
560 handleContextCreationFailure(window);
561 }
562 // otherwise no error, will retry on a subsequent rendering attempt
563 }
564 } else if (!enableRhi && !gl) {
565 gl = new QOpenGLContext();
566 gl->setFormat(window->requestedFormat());
567 gl->setScreen(window->screen());
568 if (qt_gl_global_share_context())
569 gl->setShareContext(qt_gl_global_share_context());
570 if (!gl->create()) {
571 delete gl;
572 gl = nullptr;
573 handleContextCreationFailure(window);
574 } else {
575 if (!offscreenSurface) {
576 offscreenSurface = new QOffscreenSurface;
577 offscreenSurface->setFormat(gl->format());
578 offscreenSurface->create();
579 }
580 cd->fireOpenGLContextCreated(context: gl);
581 current = gl->makeCurrent(surface: window);
582 }
583 if (current) {
584 QSGDefaultRenderContext::InitParams rcParams;
585 rcParams.sampleCount = qMax(a: 1, b: gl->format().samples());
586 rcParams.openGLContext = gl;
587 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
588 rcParams.maybeSurface = window;
589 cd->context->initialize(params: &rcParams);
590 }
591 } else {
592 if (rhi) {
593 current = true;
594 // With the rhi making the (OpenGL) context current serves only one
595 // purpose: to enable external OpenGL rendering connected to one of
596 // the QQuickWindow signals (beforeSynchronizing, beforeRendering,
597 // etc.) to function like it did on the direct OpenGL path. For our
598 // own rendering this call would not be necessary.
599 rhi->makeThreadLocalNativeContextCurrent();
600 } else {
601 current = gl->makeCurrent(surface: window);
602 }
603 }
604
605 if (enableRhi && rhi && !cd->swapchain) {
606 // if it's not the first window then the rhi is not yet stored to
607 // QQuickWindowPrivate, do it now
608 cd->rhi = rhi;
609
610 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
611
612 // QQ is always premul alpha. Decide based on alphaBufferSize in
613 // requestedFormat(). (the platform plugin can override format() but
614 // what matters here is what the application wanted, hence using the
615 // requested one)
616 const bool alpha = window->requestedFormat().alphaBufferSize() > 0;
617 if (alpha)
618 flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
619
620 cd->swapchain = rhi->newSwapChain();
621 cd->depthStencilForSwapchain = rhi->newRenderBuffer(type: QRhiRenderBuffer::DepthStencil,
622 pixelSize: QSize(),
623 sampleCount: rhiSampleCount,
624 flags: QRhiRenderBuffer::UsedWithSwapChainOnly);
625 cd->swapchain->setWindow(window);
626 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
627 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
628 rhiSampleCount, alpha ? "yes" : "no");
629 cd->swapchain->setSampleCount(rhiSampleCount);
630 cd->swapchain->setFlags(flags);
631 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
632 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
633
634 window->installEventFilter(filterObj: this);
635 }
636
637 bool lastDirtyWindow = true;
638 auto i = m_windows.constBegin();
639 while (i != m_windows.constEnd()) {
640 if (i.value().updatePending) {
641 lastDirtyWindow = false;
642 break;
643 }
644 i++;
645 }
646
647 // Check for context loss. (legacy GL only)
648 if (!current && !rhi && !gl->isValid()) {
649 for (auto it = m_windows.constBegin() ; it != m_windows.constEnd(); it++) {
650 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(c: it.key());
651 windowPrivate->cleanupNodesOnShutdown();
652 }
653 rc->invalidate();
654 current = gl->create() && gl->makeCurrent(surface: window);
655 if (current) {
656 QSGDefaultRenderContext::InitParams rcParams;
657 rcParams.sampleCount = qMax(a: 1, b: gl->format().samples());
658 rcParams.openGLContext = gl;
659 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
660 rcParams.maybeSurface = window;
661 rc->initialize(params: &rcParams);
662 }
663 }
664
665 if (!current)
666 return;
667
668 if (!data.grabOnly) {
669 cd->flushFrameSynchronousEvents();
670 // Event delivery/processing triggered the window to be deleted or stop rendering.
671 if (!m_windows.contains(akey: window))
672 return;
673 }
674
675 QSize effectiveOutputSize; // always prefer what the surface tells us, not the QWindow
676 if (cd->swapchain) {
677 effectiveOutputSize = cd->swapchain->surfacePixelSize();
678 // An update request could still be delivered right before we get an
679 // unexpose. With Vulkan on Windows for example attempting to render
680 // leads to failures at this stage since the surface size is already 0.
681 if (effectiveOutputSize.isEmpty())
682 return;
683 }
684
685 Q_TRACE_SCOPE(QSG_renderWindow);
686 QElapsedTimer renderTimer;
687 qint64 renderTime = 0, syncTime = 0, polishTime = 0;
688 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
689 if (profileFrames)
690 renderTimer.start();
691 Q_TRACE(QSG_polishItems_entry);
692 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
693
694 cd->polishItems();
695
696 if (profileFrames)
697 polishTime = renderTimer.nsecsElapsed();
698
699 Q_TRACE(QSG_polishItems_exit);
700 Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
701 QQuickProfiler::SceneGraphRenderLoopFrame,
702 QQuickProfiler::SceneGraphPolishPolish);
703 Q_TRACE(QSG_sync_entry);
704
705 emit window->afterAnimating();
706
707 // Begin the frame before syncing -> sync is where we may invoke
708 // updatePaintNode() on the items and they may want to do resource updates.
709 // Also relevant for applications that connect to the before/afterSynchronizing
710 // signals and want to do graphics stuff already there.
711 if (cd->swapchain) {
712 Q_ASSERT(!effectiveOutputSize.isEmpty());
713 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
714 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
715 if (cd->swapchainJustBecameRenderable)
716 qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
717
718 cd->hasActiveSwapchain = cd->swapchain->buildOrResize();
719 if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
720 handleDeviceLoss();
721 return;
722 }
723
724 cd->swapchainJustBecameRenderable = false;
725 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
726
727 if (cd->hasActiveSwapchain) {
728 // surface size atomicity: now that buildOrResize() succeeded,
729 // query the size that was used in there by the swapchain, and
730 // that is the size we will use while preparing the next frame.
731 effectiveOutputSize = cd->swapchain->currentPixelSize();
732 qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize;
733 } else {
734 qWarning(msg: "Failed to build or resize swapchain");
735 }
736 }
737
738 Q_ASSERT(rhi == cd->rhi);
739 // ### the flag should only be set when the app requests it, but there's no way to do that right now
740 QRhi::BeginFrameFlags frameFlags = QRhi::ExternalContentsInPass;
741 QRhi::FrameOpResult frameResult = rhi->beginFrame(swapChain: cd->swapchain, flags: frameFlags);
742 if (frameResult != QRhi::FrameOpSuccess) {
743 if (frameResult == QRhi::FrameOpDeviceLost)
744 handleDeviceLoss();
745 else if (frameResult == QRhi::FrameOpError)
746 qWarning(msg: "Failed to start frame");
747 // out of date is not worth warning about - it may happen even during resizing on some platforms
748 return;
749 }
750 }
751
752 cd->syncSceneGraph();
753 if (lastDirtyWindow)
754 rc->endSync();
755
756 if (profileFrames)
757 syncTime = renderTimer.nsecsElapsed();
758
759 Q_TRACE(QSG_sync_exit);
760 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
761 QQuickProfiler::SceneGraphRenderLoopSync);
762 Q_TRACE(QSG_render_entry);
763
764 cd->renderSceneGraph(size: window->size(), surfaceSize: effectiveOutputSize);
765
766 if (profileFrames)
767 renderTime = renderTimer.nsecsElapsed();
768 Q_TRACE(QSG_render_exit);
769 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
770 QQuickProfiler::SceneGraphRenderLoopRender);
771 Q_TRACE(QSG_swap_entry);
772
773 if (data.grabOnly) {
774 const bool alpha = window->format().alphaBufferSize() > 0 && window->color().alpha() != 255;
775 if (cd->swapchain)
776 grabContent = rhiSupport->grabAndBlockInCurrentFrame(rhi, swapchain: cd->swapchain);
777 else
778 grabContent = qt_gl_read_framebuffer(size: window->size() * window->effectiveDevicePixelRatio(), alpha_format: alpha, include_alpha: alpha);
779 grabContent.setDevicePixelRatio(window->effectiveDevicePixelRatio());
780 data.grabOnly = false;
781 }
782
783 const bool needsPresent = alsoSwap && window->isVisible();
784 if (cd->swapchain) {
785 QRhi::EndFrameFlags flags;
786 if (!needsPresent)
787 flags |= QRhi::SkipPresent;
788 QRhi::FrameOpResult frameResult = rhi->endFrame(swapChain: cd->swapchain, flags);
789 if (frameResult != QRhi::FrameOpSuccess) {
790 if (frameResult == QRhi::FrameOpDeviceLost)
791 handleDeviceLoss();
792 else if (frameResult == QRhi::FrameOpError)
793 qWarning(msg: "Failed to end frame");
794 }
795 } else if (needsPresent) {
796 if (!cd->customRenderStage || !cd->customRenderStage->swap())
797 gl->swapBuffers(surface: window);
798 }
799 if (needsPresent)
800 cd->fireFrameSwapped();
801
802 qint64 swapTime = 0;
803 if (profileFrames)
804 swapTime = renderTimer.nsecsElapsed();
805
806 Q_TRACE(QSG_swap_exit);
807 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
808 QQuickProfiler::SceneGraphRenderLoopSwap);
809
810 if (QSG_LOG_TIME_RENDERLOOP().isDebugEnabled()) {
811 static QTime lastFrameTime = QTime::currentTime();
812 qCDebug(QSG_LOG_TIME_RENDERLOOP,
813 "Frame rendered with 'basic' renderloop in %dms, polish=%d, sync=%d, render=%d, swap=%d, frameDelta=%d",
814 int(swapTime / 1000000),
815 int(polishTime / 1000000),
816 int((syncTime - polishTime) / 1000000),
817 int((renderTime - syncTime) / 1000000),
818 int((swapTime - renderTime) / 10000000),
819 int(lastFrameTime.msecsTo(QTime::currentTime())));
820 lastFrameTime = QTime::currentTime();
821 }
822
823 QSGRhiProfileConnection::instance()->send(rhi);
824
825 // Might have been set during syncSceneGraph()
826 if (data.updatePending)
827 maybeUpdate(window);
828}
829
830void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window)
831{
832 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: window);
833
834 // This is tricker than used to be. We want to detect having an empty
835 // surface size (which may be the case even when window->size() is
836 // non-empty, on some platforms with some graphics APIs!) as well as the
837 // case when the window just became "newly exposed" (e.g. after a
838 // minimize-restore on Windows, or when switching between fully obscured -
839 // not fully obscured on macOS)
840
841 if (!window->isExposed() || (wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()))
842 wd->hasRenderableSwapchain = false;
843
844 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
845 && !wd->swapchain->surfacePixelSize().isEmpty())
846 {
847 wd->hasRenderableSwapchain = true;
848 wd->swapchainJustBecameRenderable = true;
849 }
850
851 if (window->isExposed() && (!rhi || !wd->hasActiveSwapchain || wd->hasRenderableSwapchain)) {
852 m_windows[window].updatePending = true;
853 renderWindow(window);
854 }
855}
856
857QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
858{
859 if (!m_windows.contains(akey: window))
860 return QImage();
861
862 m_windows[window].grabOnly = true;
863
864 renderWindow(window);
865
866 QImage grabbed = grabContent;
867 grabContent = QImage();
868 return grabbed;
869}
870
871void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
872{
873 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window);
874 if (!m_windows.contains(akey: window))
875 return;
876
877 // Even if the window is not renderable,
878 // renderWindow() called on different window
879 // should not delete QSGTexture's
880 // from this unrenderable window.
881 m_windows[window].updatePending = true;
882
883 if (!cd->isRenderable())
884 return;
885
886 window->requestUpdate();
887}
888
889QSGContext *QSGGuiThreadRenderLoop::sceneGraphContext() const
890{
891 return sg;
892}
893
894void QSGGuiThreadRenderLoop::releaseResources(QQuickWindow *w)
895{
896 // No full invalidation of the rendercontext, just clear some caches.
897 QQuickWindowPrivate *d = QQuickWindowPrivate::get(c: w);
898 if (d->renderer)
899 d->renderer->releaseCachedResources();
900}
901
902void QSGGuiThreadRenderLoop::handleUpdateRequest(QQuickWindow *window)
903{
904 renderWindow(window);
905}
906
907#endif // ENABLE_DEFAULT_BACKEND
908
909#include "qsgrenderloop.moc"
910#include "moc_qsgrenderloop_p.cpp"
911
912QT_END_NAMESPACE
913

source code of qtdeclarative/src/quick/scenegraph/qsgrenderloop.cpp