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 | |
79 | QT_BEGIN_NAMESPACE |
80 | |
81 | extern bool qsg_useConsistentTiming(); |
82 | extern 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 | |
103 | DEFINE_BOOL_CONFIG_OPTION(qmlNoThreadedRenderer, QML_BAD_GUI_RENDER_LOOP); |
104 | DEFINE_BOOL_CONFIG_OPTION(qmlForceThreadedRenderer, QML_FORCE_THREADED_RENDERER); // Might trigger graphics driver threading bugs, use at own risk |
105 | #endif |
106 | |
107 | QSGRenderLoop *QSGRenderLoop::s_instance = nullptr; |
108 | |
109 | QSGRenderLoop::~QSGRenderLoop() |
110 | { |
111 | } |
112 | |
113 | void 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 | |
133 | QSurface::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 | |
142 | void 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 |
166 | class QSGGuiThreadRenderLoop : public QSGRenderLoop |
167 | { |
168 | Q_OBJECT |
169 | public: |
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 | |
223 | QSGRenderLoop *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 | |
322 | void QSGRenderLoop::setInstance(QSGRenderLoop *instance) |
323 | { |
324 | Q_ASSERT(!s_instance); |
325 | s_instance = instance; |
326 | } |
327 | |
328 | void 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 |
359 | QSGGuiThreadRenderLoop::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 | |
370 | QSGGuiThreadRenderLoop::~QSGGuiThreadRenderLoop() |
371 | { |
372 | delete rc; |
373 | delete sg; |
374 | } |
375 | |
376 | void QSGGuiThreadRenderLoop::show(QQuickWindow *window) |
377 | { |
378 | m_windows[window] = WindowData(); |
379 | |
380 | maybeUpdate(window); |
381 | } |
382 | |
383 | void 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 | |
391 | void 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 | |
453 | void 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 | |
474 | void 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 | |
486 | bool 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 | |
505 | void 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 | |
830 | void 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 | |
857 | QImage 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 | |
871 | void 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 | |
889 | QSGContext *QSGGuiThreadRenderLoop::sceneGraphContext() const |
890 | { |
891 | return sg; |
892 | } |
893 | |
894 | void 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 | |
902 | void 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 | |
912 | QT_END_NAMESPACE |
913 | |