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(w);
119 if (wd->windowManager == s_instance) {
120 s_instance->windowDestroyed(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(window);
150 job->run();
151 }
152 } else {
153 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(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(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("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(QSGRhiSupport::instance()->rhiBackendName(),
334 &translatedMessage,
335 &untranslatedMessage);
336 } else {
337 QQuickWindowPrivate::contextCreationFailureMessage(window->requestedFormat(),
338 &translatedMessage,
339 &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(window)->emitError(QQuickWindow::ContextNotAvailable,
346 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("%s", qPrintable(untranslatedMessage));
356}
357
358#ifdef ENABLE_DEFAULT_BACKEND
359QSGGuiThreadRenderLoop::QSGGuiThreadRenderLoop()
360{
361 if (qsg_useConsistentTiming()) {
362 QUnifiedTimer::instance(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(window);
386 cd->fireAboutToStop();
387 if (m_windows.contains(window))
388 m_windows[window].updatePending = false;
389}
390
391void QSGGuiThreadRenderLoop::windowDestroyed(QQuickWindow *window)
392{
393 m_windows.remove(window);
394 hide(window);
395 QQuickWindowPrivate *d = QQuickWindowPrivate::get(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 QT_CONFIG(quick_shadereffect)
417 QSGRhiShaderEffectNode::cleanupMaterialTypeCache();
418#if QT_CONFIG(opengl)
419 QQuickOpenGLShaderEffectMaterial::cleanupMaterialCache();
420#endif
421#endif
422
423 if (d->swapchain) {
424 if (window->handle()) {
425 // We get here when exiting via QCoreApplication::quit() instead of
426 // through QWindow::close().
427 releaseSwapchain(window);
428 } else {
429 qWarning("QSGGuiThreadRenderLoop cleanup with QQuickWindow %p swapchain %p still alive, this should not happen.",
430 window, d->swapchain);
431 }
432 }
433
434 d->cleanupNodesOnShutdown();
435 if (m_windows.size() == 0) {
436 rc->invalidate();
437 d->rhi = nullptr;
438 delete rhi;
439 rhi = nullptr;
440 delete gl;
441 gl = nullptr;
442 delete offscreenSurface;
443 offscreenSurface = nullptr;
444 } else if (gl && window == gl->surface() && current) {
445 if (!rhi)
446 gl->doneCurrent();
447 }
448
449 d->animationController.reset();
450}
451
452void QSGGuiThreadRenderLoop::handleDeviceLoss()
453{
454 if (!rhi || !rhi->isDeviceLost())
455 return;
456
457 qWarning("Graphics device lost, cleaning up scenegraph and releasing RHI");
458
459 for (auto it = m_windows.constBegin(), itEnd = m_windows.constEnd(); it != itEnd; ++it)
460 QQuickWindowPrivate::get(it.key())->cleanupNodesOnShutdown();
461
462 rc->invalidate();
463
464 for (auto it = m_windows.begin(), itEnd = m_windows.end(); it != itEnd; ++it) {
465 releaseSwapchain(it.key());
466 it->rhiDeviceLost = true;
467 }
468
469 delete rhi;
470 rhi = nullptr;
471}
472
473void QSGGuiThreadRenderLoop::releaseSwapchain(QQuickWindow *window)
474{
475 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
476 delete wd->rpDescForSwapchain;
477 wd->rpDescForSwapchain = nullptr;
478 delete wd->swapchain;
479 wd->swapchain = nullptr;
480 delete wd->depthStencilForSwapchain;
481 wd->depthStencilForSwapchain = nullptr;
482 wd->hasActiveSwapchain = wd->hasRenderableSwapchain = wd->swapchainJustBecameRenderable = false;
483}
484
485bool QSGGuiThreadRenderLoop::eventFilter(QObject *watched, QEvent *event)
486{
487 switch (event->type()) {
488 case QEvent::PlatformSurface:
489 // this is the proper time to tear down the swapchain (while the native window and surface are still around)
490 if (static_cast<QPlatformSurfaceEvent *>(event)->surfaceEventType() == QPlatformSurfaceEvent::SurfaceAboutToBeDestroyed) {
491 QQuickWindow *w = qobject_cast<QQuickWindow *>(watched);
492 if (w) {
493 releaseSwapchain(w);
494 w->removeEventFilter(this);
495 }
496 }
497 break;
498 default:
499 break;
500 }
501 return QObject::eventFilter(watched, event);
502}
503
504void QSGGuiThreadRenderLoop::renderWindow(QQuickWindow *window)
505{
506 if (!m_windows.contains(window))
507 return;
508
509 WindowData &data = const_cast<WindowData &>(m_windows[window]);
510 bool alsoSwap = data.updatePending;
511 data.updatePending = false;
512
513 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
514 if (!cd->isRenderable())
515 return;
516
517 bool current = false;
518 QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();
519 int rhiSampleCount = 1;
520 const bool enableRhi = rhiSupport->isRhiEnabled();
521
522 if (enableRhi && !rhi) {
523 // This block below handles both the initial QRhi initialization and
524 // also the subsequent reinitialization attempts after a device lost
525 // (reset) situation.
526
527 if (data.rhiDoomed) // no repeated attempts if the initial attempt failed
528 return;
529
530 if (!offscreenSurface)
531 offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window);
532
533 rhi = rhiSupport->createRhi(window, offscreenSurface);
534
535 if (rhi) {
536 if (rhiSupport->isProfilingRequested())
537 QSGRhiProfileConnection::instance()->initialize(rhi);
538
539 data.rhiDeviceLost = false;
540
541 current = true;
542 rhi->makeThreadLocalNativeContextCurrent();
543
544 // The sample count cannot vary between windows as we use the same
545 // rendercontext for all of them. Decide it here and now.
546 rhiSampleCount = rhiSupport->chooseSampleCountForWindowWithRhi(window, rhi);
547
548 cd->rhi = rhi; // set this early in case something hooked up to rc initialized() accesses it
549
550 QSGDefaultRenderContext::InitParams rcParams;
551 rcParams.rhi = rhi;
552 rcParams.sampleCount = rhiSampleCount;
553 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
554 rcParams.maybeSurface = window;
555 cd->context->initialize(&rcParams);
556 } else {
557 if (!data.rhiDeviceLost) {
558 data.rhiDoomed = true;
559 handleContextCreationFailure(window);
560 }
561 // otherwise no error, will retry on a subsequent rendering attempt
562 }
563 } else if (!enableRhi && !gl) {
564 gl = new QOpenGLContext();
565 gl->setFormat(window->requestedFormat());
566 gl->setScreen(window->screen());
567 if (qt_gl_global_share_context())
568 gl->setShareContext(qt_gl_global_share_context());
569 if (!gl->create()) {
570 delete gl;
571 gl = nullptr;
572 handleContextCreationFailure(window);
573 } else {
574 if (!offscreenSurface) {
575 offscreenSurface = new QOffscreenSurface;
576 offscreenSurface->setFormat(gl->format());
577 offscreenSurface->create();
578 }
579 cd->fireOpenGLContextCreated(gl);
580 current = gl->makeCurrent(window);
581 }
582 if (current) {
583 QSGDefaultRenderContext::InitParams rcParams;
584 rcParams.sampleCount = qMax(1, gl->format().samples());
585 rcParams.openGLContext = gl;
586 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
587 rcParams.maybeSurface = window;
588 cd->context->initialize(&rcParams);
589 }
590 } else {
591 if (rhi) {
592 current = true;
593 // With the rhi making the (OpenGL) context current serves only one
594 // purpose: to enable external OpenGL rendering connected to one of
595 // the QQuickWindow signals (beforeSynchronizing, beforeRendering,
596 // etc.) to function like it did on the direct OpenGL path. For our
597 // own rendering this call would not be necessary.
598 rhi->makeThreadLocalNativeContextCurrent();
599 } else {
600 current = gl->makeCurrent(window);
601 }
602 }
603
604 if (enableRhi && rhi && !cd->swapchain) {
605 // if it's not the first window then the rhi is not yet stored to
606 // QQuickWindowPrivate, do it now
607 cd->rhi = rhi;
608
609 QRhiSwapChain::Flags flags = QRhiSwapChain::UsedAsTransferSource; // may be used in a grab
610
611 // QQ is always premul alpha. Decide based on alphaBufferSize in
612 // requestedFormat(). (the platform plugin can override format() but
613 // what matters here is what the application wanted, hence using the
614 // requested one)
615 const bool alpha = window->requestedFormat().alphaBufferSize() > 0;
616 if (alpha)
617 flags |= QRhiSwapChain::SurfaceHasPreMulAlpha;
618
619 cd->swapchain = rhi->newSwapChain();
620 cd->depthStencilForSwapchain = rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil,
621 QSize(),
622 rhiSampleCount,
623 QRhiRenderBuffer::UsedWithSwapChainOnly);
624 cd->swapchain->setWindow(window);
625 cd->swapchain->setDepthStencil(cd->depthStencilForSwapchain);
626 qCDebug(QSG_LOG_INFO, "MSAA sample count for the swapchain is %d. Alpha channel requested = %s",
627 rhiSampleCount, alpha ? "yes" : "no");
628 cd->swapchain->setSampleCount(rhiSampleCount);
629 cd->swapchain->setFlags(flags);
630 cd->rpDescForSwapchain = cd->swapchain->newCompatibleRenderPassDescriptor();
631 cd->swapchain->setRenderPassDescriptor(cd->rpDescForSwapchain);
632
633 window->installEventFilter(this);
634 }
635
636 bool lastDirtyWindow = true;
637 auto i = m_windows.constBegin();
638 while (i != m_windows.constEnd()) {
639 if (i.value().updatePending) {
640 lastDirtyWindow = false;
641 break;
642 }
643 i++;
644 }
645
646 // Check for context loss. (legacy GL only)
647 if (!current && !rhi && !gl->isValid()) {
648 for (auto it = m_windows.constBegin() ; it != m_windows.constEnd(); it++) {
649 QQuickWindowPrivate *windowPrivate = QQuickWindowPrivate::get(it.key());
650 windowPrivate->cleanupNodesOnShutdown();
651 }
652 rc->invalidate();
653 current = gl->create() && gl->makeCurrent(window);
654 if (current) {
655 QSGDefaultRenderContext::InitParams rcParams;
656 rcParams.sampleCount = qMax(1, gl->format().samples());
657 rcParams.openGLContext = gl;
658 rcParams.initialSurfacePixelSize = window->size() * window->effectiveDevicePixelRatio();
659 rcParams.maybeSurface = window;
660 rc->initialize(&rcParams);
661 }
662 }
663
664 if (!current)
665 return;
666
667 if (!data.grabOnly) {
668 cd->flushFrameSynchronousEvents();
669 // Event delivery/processing triggered the window to be deleted or stop rendering.
670 if (!m_windows.contains(window))
671 return;
672 }
673
674 QSize effectiveOutputSize; // always prefer what the surface tells us, not the QWindow
675 if (cd->swapchain) {
676 effectiveOutputSize = cd->swapchain->surfacePixelSize();
677 // An update request could still be delivered right before we get an
678 // unexpose. With Vulkan on Windows for example attempting to render
679 // leads to failures at this stage since the surface size is already 0.
680 if (effectiveOutputSize.isEmpty())
681 return;
682 }
683
684 Q_TRACE_SCOPE(QSG_renderWindow);
685 QElapsedTimer renderTimer;
686 qint64 renderTime = 0, syncTime = 0, polishTime = 0;
687 bool profileFrames = QSG_LOG_TIME_RENDERLOOP().isDebugEnabled();
688 if (profileFrames)
689 renderTimer.start();
690 Q_TRACE(QSG_polishItems_entry);
691 Q_QUICK_SG_PROFILE_START(QQuickProfiler::SceneGraphPolishFrame);
692
693 cd->polishItems();
694
695 if (profileFrames)
696 polishTime = renderTimer.nsecsElapsed();
697
698 Q_TRACE(QSG_polishItems_exit);
699 Q_QUICK_SG_PROFILE_SWITCH(QQuickProfiler::SceneGraphPolishFrame,
700 QQuickProfiler::SceneGraphRenderLoopFrame,
701 QQuickProfiler::SceneGraphPolishPolish);
702 Q_TRACE(QSG_sync_entry);
703
704 emit window->afterAnimating();
705
706 // Begin the frame before syncing -> sync is where we may invoke
707 // updatePaintNode() on the items and they may want to do resource updates.
708 // Also relevant for applications that connect to the before/afterSynchronizing
709 // signals and want to do graphics stuff already there.
710 if (cd->swapchain) {
711 Q_ASSERT(!effectiveOutputSize.isEmpty());
712 const QSize previousOutputSize = cd->swapchain->currentPixelSize();
713 if (previousOutputSize != effectiveOutputSize || cd->swapchainJustBecameRenderable) {
714 if (cd->swapchainJustBecameRenderable)
715 qCDebug(QSG_LOG_RENDERLOOP, "just became exposed");
716
717 cd->hasActiveSwapchain = cd->swapchain->buildOrResize();
718 if (!cd->hasActiveSwapchain && rhi->isDeviceLost()) {
719 handleDeviceLoss();
720 return;
721 }
722
723 cd->swapchainJustBecameRenderable = false;
724 cd->hasRenderableSwapchain = cd->hasActiveSwapchain;
725
726 if (cd->hasActiveSwapchain) {
727 // surface size atomicity: now that buildOrResize() succeeded,
728 // query the size that was used in there by the swapchain, and
729 // that is the size we will use while preparing the next frame.
730 effectiveOutputSize = cd->swapchain->currentPixelSize();
731 qCDebug(QSG_LOG_RENDERLOOP) << "rhi swapchain size" << effectiveOutputSize;
732 } else {
733 qWarning("Failed to build or resize swapchain");
734 }
735 }
736
737 Q_ASSERT(rhi == cd->rhi);
738 // ### the flag should only be set when the app requests it, but there's no way to do that right now
739 QRhi::BeginFrameFlags frameFlags = QRhi::ExternalContentsInPass;
740 QRhi::FrameOpResult frameResult = rhi->beginFrame(cd->swapchain, frameFlags);
741 if (frameResult != QRhi::FrameOpSuccess) {
742 if (frameResult == QRhi::FrameOpDeviceLost)
743 handleDeviceLoss();
744 else if (frameResult == QRhi::FrameOpError)
745 qWarning("Failed to start frame");
746 // out of date is not worth warning about - it may happen even during resizing on some platforms
747 return;
748 }
749 }
750
751 cd->syncSceneGraph();
752 if (lastDirtyWindow)
753 rc->endSync();
754
755 if (profileFrames)
756 syncTime = renderTimer.nsecsElapsed();
757
758 Q_TRACE(QSG_sync_exit);
759 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
760 QQuickProfiler::SceneGraphRenderLoopSync);
761 Q_TRACE(QSG_render_entry);
762
763 cd->renderSceneGraph(window->size(), effectiveOutputSize);
764
765 if (profileFrames)
766 renderTime = renderTimer.nsecsElapsed();
767 Q_TRACE(QSG_render_exit);
768 Q_QUICK_SG_PROFILE_RECORD(QQuickProfiler::SceneGraphRenderLoopFrame,
769 QQuickProfiler::SceneGraphRenderLoopRender);
770 Q_TRACE(QSG_swap_entry);
771
772 if (data.grabOnly) {
773 const bool alpha = window->format().alphaBufferSize() > 0 && window->color().alpha() != 255;
774 if (cd->swapchain)
775 grabContent = rhiSupport->grabAndBlockInCurrentFrame(rhi, cd->swapchain);
776 else
777 grabContent = qt_gl_read_framebuffer(window->size() * window->effectiveDevicePixelRatio(), alpha, alpha);
778 grabContent.setDevicePixelRatio(window->effectiveDevicePixelRatio());
779 data.grabOnly = false;
780 }
781
782 const bool needsPresent = alsoSwap && window->isVisible();
783 if (cd->swapchain) {
784 QRhi::EndFrameFlags flags;
785 if (!needsPresent)
786 flags |= QRhi::SkipPresent;
787 QRhi::FrameOpResult frameResult = rhi->endFrame(cd->swapchain, flags);
788 if (frameResult != QRhi::FrameOpSuccess) {
789 if (frameResult == QRhi::FrameOpDeviceLost)
790 handleDeviceLoss();
791 else if (frameResult == QRhi::FrameOpError)
792 qWarning("Failed to end frame");
793 }
794 } else if (needsPresent) {
795 if (!cd->customRenderStage || !cd->customRenderStage->swap())
796 gl->swapBuffers(window);
797 }
798 if (needsPresent)
799 cd->fireFrameSwapped();
800
801 qint64 swapTime = 0;
802 if (profileFrames)
803 swapTime = renderTimer.nsecsElapsed();
804
805 Q_TRACE(QSG_swap_exit);
806 Q_QUICK_SG_PROFILE_END(QQuickProfiler::SceneGraphRenderLoopFrame,
807 QQuickProfiler::SceneGraphRenderLoopSwap);
808
809 if (QSG_LOG_TIME_RENDERLOOP().isDebugEnabled()) {
810 static QTime lastFrameTime = QTime::currentTime();
811 qCDebug(QSG_LOG_TIME_RENDERLOOP,
812 "Frame rendered with 'basic' renderloop in %dms, polish=%d, sync=%d, render=%d, swap=%d, frameDelta=%d",
813 int(swapTime / 1000000),
814 int(polishTime / 1000000),
815 int((syncTime - polishTime) / 1000000),
816 int((renderTime - syncTime) / 1000000),
817 int((swapTime - renderTime) / 10000000),
818 int(lastFrameTime.msecsTo(QTime::currentTime())));
819 lastFrameTime = QTime::currentTime();
820 }
821
822 QSGRhiProfileConnection::instance()->send(rhi);
823
824 // Might have been set during syncSceneGraph()
825 if (data.updatePending)
826 maybeUpdate(window);
827}
828
829void QSGGuiThreadRenderLoop::exposureChanged(QQuickWindow *window)
830{
831 QQuickWindowPrivate *wd = QQuickWindowPrivate::get(window);
832
833 // This is tricker than used to be. We want to detect having an empty
834 // surface size (which may be the case even when window->size() is
835 // non-empty, on some platforms with some graphics APIs!) as well as the
836 // case when the window just became "newly exposed" (e.g. after a
837 // minimize-restore on Windows, or when switching between fully obscured -
838 // not fully obscured on macOS)
839
840 if (!window->isExposed() || (wd->hasActiveSwapchain && wd->swapchain->surfacePixelSize().isEmpty()))
841 wd->hasRenderableSwapchain = false;
842
843 if (window->isExposed() && !wd->hasRenderableSwapchain && wd->hasActiveSwapchain
844 && !wd->swapchain->surfacePixelSize().isEmpty())
845 {
846 wd->hasRenderableSwapchain = true;
847 wd->swapchainJustBecameRenderable = true;
848 }
849
850 if (window->isExposed() && (!rhi || !wd->hasActiveSwapchain || wd->hasRenderableSwapchain)) {
851 m_windows[window].updatePending = true;
852 renderWindow(window);
853 }
854}
855
856QImage QSGGuiThreadRenderLoop::grab(QQuickWindow *window)
857{
858 if (!m_windows.contains(window))
859 return QImage();
860
861 m_windows[window].grabOnly = true;
862
863 renderWindow(window);
864
865 QImage grabbed = grabContent;
866 grabContent = QImage();
867 return grabbed;
868}
869
870void QSGGuiThreadRenderLoop::maybeUpdate(QQuickWindow *window)
871{
872 QQuickWindowPrivate *cd = QQuickWindowPrivate::get(window);
873 if (!m_windows.contains(window))
874 return;
875
876 // Even if the window is not renderable,
877 // renderWindow() called on different window
878 // should not delete QSGTexture's
879 // from this unrenderable window.
880 m_windows[window].updatePending = true;
881
882 if (!cd->isRenderable())
883 return;
884
885 window->requestUpdate();
886}
887
888QSGContext *QSGGuiThreadRenderLoop::sceneGraphContext() const
889{
890 return sg;
891}
892
893void QSGGuiThreadRenderLoop::releaseResources(QQuickWindow *w)
894{
895 // No full invalidation of the rendercontext, just clear some caches.
896 QQuickWindowPrivate *d = QQuickWindowPrivate::get(w);
897 if (d->renderer)
898 d->renderer->releaseCachedResources();
899}
900
901void QSGGuiThreadRenderLoop::handleUpdateRequest(QQuickWindow *window)
902{
903 renderWindow(window);
904}
905
906#endif // ENABLE_DEFAULT_BACKEND
907
908#include "qsgrenderloop.moc"
909#include "moc_qsgrenderloop_p.cpp"
910
911QT_END_NAMESPACE
912

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