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