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