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 "qquickrendercontrol.h" |
5 | #include "qquickrendercontrol_p.h" |
6 | |
7 | #include <QtCore/QCoreApplication> |
8 | #include <QtCore/QTime> |
9 | #include <QtQuick/private/qquickanimatorcontroller_p.h> |
10 | #include <QtQuick/private/qsgdefaultrendercontext_p.h> |
11 | #include <QtQuick/private/qsgrhisupport_p.h> |
12 | |
13 | #include <private/qsgrhishadereffectnode_p.h> |
14 | |
15 | #include <QtGui/private/qguiapplication_p.h> |
16 | #include <qpa/qplatformintegration.h> |
17 | #include <QtGui/qoffscreensurface.h> |
18 | |
19 | #include <QtQml/private/qqmlglobal_p.h> |
20 | |
21 | #include <QtQuick/QQuickWindow> |
22 | #include <QtQuick/QQuickRenderTarget> |
23 | #include <QtQuick/private/qquickwindow_p.h> |
24 | #include <QtQuick/private/qquickitem_p.h> |
25 | #include <QtQuick/private/qsgsoftwarerenderer_p.h> |
26 | #include <QtCore/private/qobject_p.h> |
27 | |
28 | #include <QtQuick/private/qquickwindow_p.h> |
29 | #include <rhi/qrhi.h> |
30 | |
31 | QT_BEGIN_NAMESPACE |
32 | |
33 | /*! |
34 | \class QQuickRenderControl |
35 | |
36 | \brief The QQuickRenderControl class provides a mechanism for rendering the Qt |
37 | Quick scenegraph onto an offscreen render target in a fully |
38 | application-controlled manner. |
39 | |
40 | \since 5.4 |
41 | |
42 | QQuickWindow and QQuickView and their associated internal render loops render |
43 | the Qt Quick scene onto a native window. In some cases, for example when |
44 | integrating with 3rd party OpenGL, Vulkan, Metal, or Direct 3D renderers, it |
45 | can be useful to get the scene into a texture that can then be used in |
46 | arbitrary ways by the external rendering engine. Such a mechanism is also |
47 | essential when integrating with a VR framework. QQuickRenderControl makes this |
48 | possible in a hardware accelerated manner, unlike the performance-wise limited |
49 | alternative of using QQuickWindow::grabWindow() |
50 | |
51 | When using a QQuickRenderControl, the QQuickWindow must not be |
52 | \l{QWindow::show()}{shown} (it will not be visible on-screen) and there will |
53 | not be an underlying native window for it. Instead, the QQuickWindow instance |
54 | is associated with the render control object, using the overload of the |
55 | QQuickWindow constructor, and a texture or image object specified via |
56 | QQuickWindow::setRenderTarget(). The QQuickWindow object is still essential, |
57 | because it represents the Qt Quick scene and provides the bulk of the scene |
58 | management and event delivery mechanisms. It does not however act as a real |
59 | on-screen window from the windowing system's perspective. |
60 | |
61 | Management of the graphics devices, contexts, image and texture objects is up |
62 | to the application. The device or context that will be used by Qt Quick must |
63 | be created before calling initialize(). The creation of the texture object |
64 | can be deferred, see below. Qt 5.4 introduces the ability for QOpenGLContext |
65 | to adopt existing native contexts. Together with QQuickRenderControl this |
66 | makes it possible to create a QOpenGLContext that shares with an external |
67 | rendering engine's existing context. This new QOpenGLContext can then be used |
68 | to render the Qt Quick scene into a texture that is accessible by the other |
69 | engine's context too. For Vulkan, Metal, and Direct 3D there are no |
70 | Qt-provided wrappers for device objects, so existing ones can be passed as-is |
71 | via QQuickWindow::setGraphicsDevice(). |
72 | |
73 | Loading and instantiation of the QML components happen by using a |
74 | QQmlEngine. Once the root object is created, it will need to be parented to |
75 | the QQuickWindow's contentItem(). |
76 | |
77 | Applications will usually have to connect to 4 important signals: |
78 | |
79 | \list |
80 | |
81 | \li QQuickWindow::sceneGraphInitialized() Emitted at some point after calling |
82 | QQuickRenderControl::initialize(). Upon this signal, the application is |
83 | expected to create its framebuffer object and associate it with the |
84 | QQuickWindow. |
85 | |
86 | \li QQuickWindow::sceneGraphInvalidated() When the scenegraph resources are |
87 | released, the framebuffer object can be destroyed too. |
88 | |
89 | \li QQuickRenderControl::renderRequested() Indicates that the scene has to be |
90 | rendered by calling render(). After making the context current, applications |
91 | are expected to call render(). |
92 | |
93 | \li QQuickRenderControl::sceneChanged() Indicates that the scene has changed |
94 | meaning that, before rendering, polishing and synchronizing is also necessary. |
95 | |
96 | \endlist |
97 | |
98 | To send events, for example mouse or keyboard events, to the scene, use |
99 | QCoreApplication::sendEvent() with the QQuickWindow instance as the receiver. |
100 | |
101 | For key events it may be also necessary to set the focus manually on the |
102 | desired item. In practice this involves calling |
103 | \l{QQuickItem::forceActiveFocus()}{forceActiveFocus()} on the desired item, |
104 | for example the scene's root item, once it is associated with the scene (the |
105 | QQuickWindow). |
106 | |
107 | \note In general QQuickRenderControl is supported in combination with all Qt |
108 | Quick backends. However, some functionality, in particular grab(), may not be |
109 | available in all cases. |
110 | |
111 | \inmodule QtQuick |
112 | */ |
113 | |
114 | QSGContext *QQuickRenderControlPrivate::sg = nullptr; |
115 | |
116 | QQuickRenderControlPrivate::QQuickRenderControlPrivate(QQuickRenderControl *renderControl) |
117 | : q(renderControl), |
118 | initialized(false), |
119 | window(nullptr), |
120 | rhi(nullptr), |
121 | ownRhi(true), |
122 | cb(nullptr), |
123 | offscreenSurface(nullptr), |
124 | sampleCount(1), |
125 | frameStatus(NotRecordingFrame) |
126 | { |
127 | if (!sg) { |
128 | qAddPostRoutine(cleanup); |
129 | sg = QSGContext::createDefaultContext(); |
130 | } |
131 | rc = sg->createRenderContext(); |
132 | } |
133 | |
134 | void QQuickRenderControlPrivate::cleanup() |
135 | { |
136 | delete sg; |
137 | sg = nullptr; |
138 | } |
139 | |
140 | /*! |
141 | Constructs a QQuickRenderControl object, with parent |
142 | object \a parent. |
143 | */ |
144 | QQuickRenderControl::QQuickRenderControl(QObject *parent) |
145 | : QObject(*(new QQuickRenderControlPrivate(this)), parent) |
146 | { |
147 | } |
148 | |
149 | /*! |
150 | \internal |
151 | */ |
152 | QQuickRenderControl::QQuickRenderControl(QQuickRenderControlPrivate &dd, QObject * parent) |
153 | : QObject(dd, parent) |
154 | { |
155 | } |
156 | |
157 | /*! |
158 | Destroys the instance. Releases all scenegraph resources. |
159 | |
160 | \sa invalidate() |
161 | */ |
162 | QQuickRenderControl::~QQuickRenderControl() |
163 | { |
164 | Q_D(QQuickRenderControl); |
165 | |
166 | invalidate(); |
167 | |
168 | QQuickGraphicsConfiguration config; |
169 | if (d->window) { |
170 | QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: d->window); |
171 | wd->renderControl = nullptr; |
172 | config = wd->graphicsConfig; |
173 | } |
174 | |
175 | // It is likely that the cleanup in windowDestroyed() is not called since |
176 | // the standard pattern is to destroy the rendercontrol before the QQuickWindow. |
177 | // Do it here. |
178 | d->windowDestroyed(); |
179 | |
180 | delete d->rc; |
181 | |
182 | // Only call rhi related cleanup when we actually got to initialize() and |
183 | // managed to get a QRhi. The software backend for instance would mean |
184 | // using the rendercontrol without ever calling initialize() - it is then |
185 | // important to completely skip calling any QSGRhiSupport functions. |
186 | if (d->rhi) |
187 | d->resetRhi(config); |
188 | } |
189 | |
190 | void QQuickRenderControlPrivate::windowDestroyed() |
191 | { |
192 | if (window) { |
193 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window); |
194 | cd->cleanupNodesOnShutdown(); |
195 | |
196 | rc->invalidate(); |
197 | |
198 | QQuickWindowPrivate::get(c: window)->animationController.reset(); |
199 | |
200 | #if QT_CONFIG(quick_shadereffect) |
201 | QSGRhiShaderEffectNode::resetMaterialTypeCache(materialTypeCacheKey: window); |
202 | #endif |
203 | |
204 | window = nullptr; |
205 | } |
206 | } |
207 | |
208 | /*! |
209 | Prepares rendering the Qt Quick scene outside the GUI thread. |
210 | |
211 | \a targetThread specifies the thread on which synchronization and |
212 | rendering will happen. There is no need to call this function in a |
213 | single threaded scenario. |
214 | */ |
215 | void QQuickRenderControl::prepareThread(QThread *targetThread) |
216 | { |
217 | Q_D(QQuickRenderControl); |
218 | d->rc->moveToThread(thread: targetThread); |
219 | QQuickWindowPrivate::get(c: d->window)->animationController->moveToThread(thread: targetThread); |
220 | } |
221 | |
222 | /*! |
223 | Sets the number of samples to use for multisampling. When \a sampleCount is |
224 | 0 or 1, multisampling is disabled. |
225 | |
226 | \note This function is always used in combination with a multisample render |
227 | target, which means \a sampleCount must match the sample count passed to |
228 | QQuickRenderTarget::fromNativeTexture(), which in turn must match the |
229 | sample count of the native texture. |
230 | |
231 | \since 6.0 |
232 | |
233 | \sa initialize(), QQuickRenderTarget |
234 | */ |
235 | void QQuickRenderControl::setSamples(int sampleCount) |
236 | { |
237 | Q_D(QQuickRenderControl); |
238 | d->sampleCount = qMax(a: 1, b: sampleCount); |
239 | } |
240 | |
241 | /*! |
242 | \return the current sample count. 1 or 0 means no multisampling. |
243 | |
244 | \since 6.0 |
245 | */ |
246 | int QQuickRenderControl::samples() const |
247 | { |
248 | Q_D(const QQuickRenderControl); |
249 | return d->sampleCount; |
250 | } |
251 | |
252 | /*! |
253 | Initializes the scene graph resources. When using a graphics API, such as |
254 | Vulkan, Metal, OpenGL, or Direct3D, for Qt Quick rendering, |
255 | QQuickRenderControl will set up an appropriate rendering engine when this |
256 | function is called. This rendering infrastructure exists as long as the |
257 | QQuickRenderControl exists. |
258 | |
259 | To control what graphics API Qt Quick uses, call |
260 | QQuickWindow::setGraphicsApi() with one of the |
261 | QSGRendererInterface:GraphicsApi constants. That must be done before |
262 | calling this function. |
263 | |
264 | To prevent the scenegraph from creating its own device and context objects, |
265 | specify an appropriate QQuickGraphicsDevice, wrapping existing graphics |
266 | objects, by calling QQuickWindow::setGraphicsDevice(). |
267 | |
268 | To configure which device extensions to enable (for example, for Vulkan), |
269 | call QQuickWindow::setGraphicsConfiguration() before this function. |
270 | |
271 | \note When using Vulkan, QQuickRenderControl does not create a QVulkanInstance |
272 | automatically. Rather, it is the application's responsibility to create a |
273 | suitable QVulkanInstance and \l{QWindow::setVulkanInstance()}{associate it} with |
274 | the QQuickWindow. Before initializing the QVulkanInstance, it is strongly |
275 | encouraged to query the list of Qt Quick's desired instance extensions by calling |
276 | the static function QQuickGraphicsConfiguration::preferredInstanceExtensions() |
277 | and to pass the returned list to QVulkanInstance::setExtensions(). |
278 | |
279 | Returns \c true on success, \c false otherwise. |
280 | |
281 | \note This function does not need to be, and must not be, called when using |
282 | the \c software adaptation of Qt Quick. |
283 | |
284 | With the default Qt Quick adaptation this function creates a new \l QRhi |
285 | object, similarly to what would happen with an on-screen QQuickWindow when |
286 | QQuickRenderControl was not used. To make this new QRhi object adopt some |
287 | existing device or context resource (e.g. use an existing QOpenGLContext |
288 | instead of creating a new one), use QQuickWindow::setGraphicsDevice() as |
289 | mentioned above. When the application wants to make the Qt Quick rendering |
290 | use an already existing \l QRhi object, that is possible as well via |
291 | \l QQuickGraphicsDevice::fromRhi(). When such a QQuickGraphicsDevice, |
292 | referencing an already existing QRhi, is set, there will be no new, |
293 | dedicated \l QRhi object created in initialize(). |
294 | |
295 | \since 6.0 |
296 | |
297 | \sa QQuickRenderTarget, QQuickGraphicsDevice, QQuickGraphicsConfiguration::preferredInstanceExtensions() |
298 | */ |
299 | bool QQuickRenderControl::initialize() |
300 | { |
301 | Q_D(QQuickRenderControl); |
302 | if (!d->window) { |
303 | qWarning(msg: "QQuickRenderControl::initialize called with no associated window" ); |
304 | return false; |
305 | } |
306 | |
307 | if (!d->initRhi()) |
308 | return false; |
309 | |
310 | QQuickWindowPrivate *wd = QQuickWindowPrivate::get(c: d->window); |
311 | wd->rhi = d->rhi; |
312 | |
313 | QSGDefaultRenderContext *renderContext = qobject_cast<QSGDefaultRenderContext *>(object: d->rc); |
314 | if (renderContext) { |
315 | QSGDefaultRenderContext::InitParams params; |
316 | params.rhi = d->rhi; |
317 | params.sampleCount = d->sampleCount; |
318 | params.initialSurfacePixelSize = d->window->size() * d->window->effectiveDevicePixelRatio(); |
319 | params.maybeSurface = d->window; |
320 | renderContext->initialize(params: ¶ms); |
321 | d->initialized = true; |
322 | } else { |
323 | qWarning(msg: "QRhi is only compatible with default adaptation" ); |
324 | return false; |
325 | } |
326 | return true; |
327 | } |
328 | |
329 | /*! |
330 | This function should be called as late as possible before |
331 | sync(). In a threaded scenario, rendering can happen in parallel |
332 | with this function. |
333 | */ |
334 | void QQuickRenderControl::polishItems() |
335 | { |
336 | Q_D(QQuickRenderControl); |
337 | if (!d->window) |
338 | return; |
339 | |
340 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: d->window); |
341 | cd->deliveryAgentPrivate()->flushFrameSynchronousEvents(win: d->window); |
342 | if (!d->window) |
343 | return; |
344 | cd->polishItems(); |
345 | emit d->window->afterAnimating(); |
346 | } |
347 | |
348 | /*! |
349 | This function is used to synchronize the QML scene with the rendering scene |
350 | graph. |
351 | |
352 | If a dedicated render thread is used, the GUI thread should be blocked for the |
353 | duration of this call. |
354 | |
355 | \return \e true if the synchronization changed the scene graph. |
356 | */ |
357 | bool QQuickRenderControl::sync() |
358 | { |
359 | Q_D(QQuickRenderControl); |
360 | if (!d->window) |
361 | return false; |
362 | |
363 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: d->window); |
364 | // we may not have a d->rhi (software backend) hence the check is important |
365 | if (d->rhi) { |
366 | if (!d->rhi->isRecordingFrame()) { |
367 | qWarning(msg: "QQuickRenderControl can only sync when beginFrame() has been called" ); |
368 | return false; |
369 | } |
370 | if (!d->cb) { |
371 | qWarning(msg: "QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided " |
372 | "(perhaps beginFrame() was not called or it was unsuccessful?)" ); |
373 | return false; |
374 | } |
375 | cd->setCustomCommandBuffer(d->cb); |
376 | } |
377 | |
378 | cd->syncSceneGraph(); |
379 | d->rc->endSync(); |
380 | |
381 | return true; |
382 | } |
383 | |
384 | /*! |
385 | Stop rendering and release resources. |
386 | |
387 | This is the equivalent of the cleanup operations that happen with a |
388 | real QQuickWindow when the window becomes hidden. |
389 | |
390 | This function is called from the destructor. Therefore there will |
391 | typically be no need to call it directly. |
392 | |
393 | Once invalidate() has been called, it is possible to reuse the |
394 | QQuickRenderControl instance by calling initialize() again. |
395 | |
396 | \note This function does not take |
397 | QQuickWindow::persistentSceneGraph() or |
398 | QQuickWindow::persistentGraphics() into account. This means |
399 | that context-specific resources are always released. |
400 | */ |
401 | void QQuickRenderControl::invalidate() |
402 | { |
403 | Q_D(QQuickRenderControl); |
404 | if (!d->window) |
405 | return; |
406 | |
407 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: d->window); |
408 | cd->fireAboutToStop(); |
409 | cd->cleanupNodesOnShutdown(); |
410 | |
411 | if (!d->initialized) |
412 | return; |
413 | |
414 | // We must invalidate since the context can potentially be destroyed by the |
415 | // application right after returning from this function. Invalidating is |
416 | // also essential to allow a subsequent initialize() to succeed. |
417 | d->rc->invalidate(); |
418 | |
419 | d->frameStatus = QQuickRenderControlPrivate::NotRecordingFrame; |
420 | d->initialized = false; |
421 | } |
422 | |
423 | /*! |
424 | Renders the scenegraph using the current context. |
425 | */ |
426 | void QQuickRenderControl::render() |
427 | { |
428 | Q_D(QQuickRenderControl); |
429 | if (!d->window) |
430 | return; |
431 | |
432 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: d->window); |
433 | // we may not have a d->rhi (software backend) hence the check is important |
434 | if (d->rhi) { |
435 | if (!d->rhi->isRecordingFrame()) { |
436 | qWarning(msg: "QQuickRenderControl can only render when beginFrame() has been called" ); |
437 | return; |
438 | } |
439 | if (!d->cb) { |
440 | qWarning(msg: "QQuickRenderControl cannot be used with QRhi when no QRhiCommandBuffer is provided" ); |
441 | return; |
442 | } |
443 | cd->setCustomCommandBuffer(d->cb); |
444 | } |
445 | |
446 | cd->renderSceneGraph(); |
447 | } |
448 | |
449 | /*! |
450 | \fn void QQuickRenderControl::renderRequested() |
451 | |
452 | This signal is emitted when the scene graph needs to be rendered. It is not necessary to call sync(). |
453 | |
454 | \note Avoid triggering rendering directly when this signal is |
455 | emitted. Instead, prefer deferring it by using a timer for example. This |
456 | will lead to better performance. |
457 | */ |
458 | |
459 | /*! |
460 | \fn void QQuickRenderControl::sceneChanged() |
461 | |
462 | This signal is emitted when the scene graph is updated, meaning that |
463 | polishItems() and sync() needs to be called. If sync() returns |
464 | true, then render() needs to be called. |
465 | |
466 | \note Avoid triggering polishing, synchronization and rendering directly |
467 | when this signal is emitted. Instead, prefer deferring it by using a timer |
468 | for example. This will lead to better performance. |
469 | */ |
470 | |
471 | QImage QQuickRenderControlPrivate::grab() |
472 | { |
473 | if (!window) |
474 | return QImage(); |
475 | |
476 | QImage grabContent; |
477 | |
478 | if (rhi) { |
479 | |
480 | // As documented by QQuickWindow::grabWindow(): Nothing to do here, we |
481 | // do not support "grabbing" with an application-provided render target |
482 | // in Qt 6. (with the exception of the software backend because that |
483 | // does not support custom render targets, so the grab implementation |
484 | // here is still valuable) |
485 | |
486 | #if QT_CONFIG(thread) |
487 | } else if (window->rendererInterface()->graphicsApi() == QSGRendererInterface::Software) { |
488 | QQuickWindowPrivate *cd = QQuickWindowPrivate::get(c: window); |
489 | cd->polishItems(); |
490 | cd->syncSceneGraph(); |
491 | QSGSoftwareRenderer *softwareRenderer = static_cast<QSGSoftwareRenderer *>(cd->renderer); |
492 | if (softwareRenderer) { |
493 | const qreal dpr = window->effectiveDevicePixelRatio(); |
494 | const QSize imageSize = window->size() * dpr; |
495 | grabContent = QImage(imageSize, QImage::Format_ARGB32_Premultiplied); |
496 | grabContent.setDevicePixelRatio(dpr); |
497 | QPaintDevice *prevDev = softwareRenderer->currentPaintDevice(); |
498 | softwareRenderer->setCurrentPaintDevice(&grabContent); |
499 | softwareRenderer->markDirty(); |
500 | rc->endSync(); |
501 | q->render(); |
502 | softwareRenderer->setCurrentPaintDevice(prevDev); |
503 | } |
504 | #endif |
505 | } else { |
506 | qWarning(msg: "QQuickRenderControl: grabs are not supported with the current Qt Quick backend" ); |
507 | } |
508 | |
509 | return grabContent; |
510 | } |
511 | |
512 | void QQuickRenderControlPrivate::update() |
513 | { |
514 | Q_Q(QQuickRenderControl); |
515 | emit q->renderRequested(); |
516 | } |
517 | |
518 | void QQuickRenderControlPrivate::maybeUpdate() |
519 | { |
520 | Q_Q(QQuickRenderControl); |
521 | emit q->sceneChanged(); |
522 | } |
523 | |
524 | /*! |
525 | \fn QWindow *QQuickRenderControl::renderWindow(QPoint *offset) |
526 | |
527 | Reimplemented in subclasses to return the real window this render control |
528 | is rendering into. |
529 | |
530 | If \a offset is non-null, it is set to the offset of the control |
531 | inside the window. |
532 | |
533 | \note While not mandatory, reimplementing this function becomes essential for |
534 | supporting multiple screens with different device pixel ratios and properly positioning |
535 | popup windows opened from QML. Therefore providing it in subclasses is highly |
536 | recommended. |
537 | */ |
538 | |
539 | /*! |
540 | Returns the real window that \a win is being rendered to, if any. |
541 | |
542 | If \a offset is non-null, it is set to the offset of the rendering |
543 | inside its window. |
544 | |
545 | */ |
546 | QWindow *QQuickRenderControl::renderWindowFor(QQuickWindow *win, QPoint *offset) |
547 | { |
548 | if (!win) |
549 | return nullptr; |
550 | QQuickRenderControl *rc = QQuickWindowPrivate::get(c: win)->renderControl; |
551 | if (rc) |
552 | return rc->renderWindow(offset); |
553 | return nullptr; |
554 | } |
555 | |
556 | bool QQuickRenderControlPrivate::isRenderWindowFor(QQuickWindow *quickWin, const QWindow *renderWin) |
557 | { |
558 | QQuickRenderControl *rc = QQuickWindowPrivate::get(c: quickWin)->renderControl; |
559 | if (rc) |
560 | return QQuickRenderControlPrivate::get(renderControl: rc)->isRenderWindow(w: renderWin); |
561 | return false; |
562 | } |
563 | |
564 | bool QQuickRenderControlPrivate::isRenderWindow(const QWindow *w) |
565 | { |
566 | Q_Q(QQuickRenderControl); |
567 | |
568 | if (window && w) |
569 | return q->renderWindowFor(win: window, offset: nullptr) == w; |
570 | |
571 | return false; |
572 | } |
573 | |
574 | /*! |
575 | \return the QQuickWindow this QQuickRenderControl is associated with. |
576 | |
577 | \note A QQuickRenderControl gets associated with a QQuickWindow when |
578 | constructing the QQuickWindow. The return value from this function is null |
579 | before that point. |
580 | |
581 | \since 6.0 |
582 | */ |
583 | QQuickWindow *QQuickRenderControl::window() const |
584 | { |
585 | Q_D(const QQuickRenderControl); |
586 | return d->window; |
587 | } |
588 | |
589 | /*! |
590 | \return the QRhi this QQuickRenderControl is associated with. |
591 | |
592 | \note The QRhi exists only when initialize() has successfully completed. |
593 | Before that the return value is null. |
594 | |
595 | \note This function is not applicable and returns null when using the |
596 | \c software adaptation of Qt Quick. |
597 | |
598 | \since 6.6 |
599 | |
600 | \sa commandBuffer(), beginFrame(), endFrame() |
601 | */ |
602 | QRhi *QQuickRenderControl::rhi() const |
603 | { |
604 | Q_D(const QQuickRenderControl); |
605 | return d->rhi; |
606 | } |
607 | |
608 | /*! |
609 | \return the current command buffer. |
610 | |
611 | Once beginFrame() is called, a QRhiCommandBuffer is set up automatically. |
612 | That is the command buffer Qt Quick scenegraph uses, but in some cases |
613 | applications may also want to query it, for example to issue resource |
614 | updates (for example, a texture readback). |
615 | |
616 | The command buffer is only valid for use between beginFrame() and |
617 | endFrame(). |
618 | |
619 | \note This function is not applicable and returns null when using the |
620 | \c software adaptation of Qt Quick. |
621 | |
622 | \since 6.6 |
623 | |
624 | \sa rhi(), beginFrame(), endFrame() |
625 | */ |
626 | QRhiCommandBuffer *QQuickRenderControl::commandBuffer() const |
627 | { |
628 | Q_D(const QQuickRenderControl); |
629 | return d->cb; |
630 | } |
631 | |
632 | /*! |
633 | Specifies the start of a graphics frame. Calls to sync() or render() must |
634 | be enclosed by calls to beginFrame() and endFrame(). |
635 | |
636 | Unlike the earlier OpenGL-only world of Qt 5, rendering with other graphics |
637 | APIs requires more well-defined points of starting and ending a frame. When |
638 | manually driving the rendering loop via QQuickRenderControl, it now falls |
639 | to the user of QQuickRenderControl to specify these points. |
640 | |
641 | A typical update step, including initialization of rendering into an |
642 | existing texture, could look like the following. The example snippet |
643 | assumes Direct3D 11 but the same concepts apply other graphics APIs as |
644 | well. |
645 | |
646 | \badcode |
647 | if (!m_quickInitialized) { |
648 | m_quickWindow->setGraphicsDevice(QQuickGraphicsDevice::fromDeviceAndContext(m_engine->device(), m_engine->context())); |
649 | |
650 | if (!m_renderControl->initialize()) |
651 | qWarning("Failed to initialize redirected Qt Quick rendering"); |
652 | |
653 | m_quickWindow->setRenderTarget(QQuickRenderTarget::fromNativeTexture({ quint64(m_res.texture), 0 }, |
654 | QSize(QML_WIDTH, QML_HEIGHT), |
655 | SAMPLE_COUNT)); |
656 | |
657 | m_quickInitialized = true; |
658 | } |
659 | |
660 | m_renderControl->polishItems(); |
661 | |
662 | m_renderControl->beginFrame(); |
663 | m_renderControl->sync(); |
664 | m_renderControl->render(); |
665 | m_renderControl->endFrame(); // Qt Quick's rendering commands are submitted to the device context here |
666 | \endcode |
667 | |
668 | \note This function does not need to be, and must not be, called when using |
669 | the \c software adaptation of Qt Quick. |
670 | |
671 | \note Internally beginFrame() and endFrame() invoke |
672 | \l{QRhi::}beginOffscreenFrame() and \l{QRhi::}endOffscreenFrame(), |
673 | respectively. This implies that there must not be a frame (neither |
674 | offscreen, nor swapchain-based) being recorded on the QRhi when |
675 | this function is called. |
676 | |
677 | \since 6.0 |
678 | |
679 | \sa endFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget |
680 | */ |
681 | void QQuickRenderControl::beginFrame() |
682 | { |
683 | Q_D(QQuickRenderControl); |
684 | if (!d->rhi) { |
685 | qWarning(msg: "QQuickRenderControl: No QRhi in beginFrame()" ); |
686 | return; |
687 | } |
688 | if (d->frameStatus == QQuickRenderControlPrivate::RecordingFrame) { |
689 | qWarning(msg: "QQuickRenderControl: beginFrame() must be followed by a call to endFrame() before calling beginFrame() again" ); |
690 | return; |
691 | } |
692 | if (d->rhi->isRecordingFrame()) { |
693 | qWarning(msg: "QQuickRenderControl: Attempted to beginFrame() while the QRhi is already recording a frame" ); |
694 | return; |
695 | } |
696 | |
697 | emit d->window->beforeFrameBegin(); |
698 | |
699 | QRhi::FrameOpResult result = d->rhi->beginOffscreenFrame(cb: &d->cb); |
700 | |
701 | switch (result) { |
702 | case QRhi::FrameOpSuccess: |
703 | case QRhi::FrameOpSwapChainOutOfDate: |
704 | d->frameStatus = QQuickRenderControlPrivate::RecordingFrame; |
705 | break; |
706 | case QRhi::FrameOpError: |
707 | d->frameStatus = QQuickRenderControlPrivate::ErrorInBeginFrame; |
708 | break; |
709 | case QRhi::FrameOpDeviceLost: |
710 | d->frameStatus = QQuickRenderControlPrivate::DeviceLostInBeginFrame; |
711 | break; |
712 | default: |
713 | d->frameStatus = QQuickRenderControlPrivate::NotRecordingFrame; |
714 | break; |
715 | } |
716 | } |
717 | |
718 | /*! |
719 | Specifies the end of a graphics frame. Calls to sync() or render() must be |
720 | enclosed by calls to beginFrame() and endFrame(). |
721 | |
722 | When this function is called, any graphics commands enqueued by the |
723 | scenegraph are submitted to the context or command queue, whichever is |
724 | applicable. |
725 | |
726 | \note This function does not need to be, and must not be, called when using |
727 | the \c software adaptation of Qt Quick. |
728 | |
729 | \since 6.0 |
730 | |
731 | \sa beginFrame(), initialize(), sync(), render(), QQuickGraphicsDevice, QQuickRenderTarget |
732 | */ |
733 | void QQuickRenderControl::endFrame() |
734 | { |
735 | Q_D(QQuickRenderControl); |
736 | if (!d->rhi) { |
737 | qWarning(msg: "QQuickRenderControl: No QRhi in endFrame()" ); |
738 | return; |
739 | } |
740 | if (d->frameStatus != QQuickRenderControlPrivate::RecordingFrame) { |
741 | qWarning(msg: "QQuickRenderControl: endFrame() must only be called after a successful beginFrame()" ); |
742 | return; |
743 | } |
744 | if (!d->rhi->isRecordingFrame()) { |
745 | qWarning(msg: "QQuickRenderControl: Attempted to endFrame() while the QRhi is not recording a frame" ); |
746 | return; |
747 | } |
748 | |
749 | d->rhi->endOffscreenFrame(); |
750 | d->cb = nullptr; |
751 | d->frameStatus = QQuickRenderControlPrivate::NotRecordingFrame; |
752 | |
753 | emit d->window->afterFrameEnd(); |
754 | } |
755 | |
756 | bool QQuickRenderControlPrivate::initRhi() |
757 | { |
758 | // initialize() - invalidate() - initialize() uses the QRhi the first |
759 | // initialize() created, so if already exists, we are done. Does not apply |
760 | // when wrapping an externally created QRhi, because we may be associated |
761 | // with a new one now. |
762 | if (rhi && ownRhi) |
763 | return true; |
764 | |
765 | QSGRhiSupport *rhiSupport = QSGRhiSupport::instance(); |
766 | |
767 | // sanity check for Vulkan |
768 | #if QT_CONFIG(vulkan) |
769 | if (rhiSupport->rhiBackend() == QRhi::Vulkan && !window->vulkanInstance()) { |
770 | qWarning(msg: "QQuickRenderControl: No QVulkanInstance set for QQuickWindow, cannot initialize" ); |
771 | return false; |
772 | } |
773 | #endif |
774 | |
775 | // for OpenGL |
776 | if (!offscreenSurface) |
777 | offscreenSurface = rhiSupport->maybeCreateOffscreenSurface(window); |
778 | |
779 | QSGRhiSupport::RhiCreateResult result = rhiSupport->createRhi(window, offscreenSurface); |
780 | if (!result.rhi) { |
781 | qWarning(msg: "QQuickRenderControl: Failed to initialize QRhi" ); |
782 | return false; |
783 | } |
784 | |
785 | rhi = result.rhi; |
786 | ownRhi = result.own; |
787 | |
788 | return true; |
789 | } |
790 | |
791 | void QQuickRenderControlPrivate::resetRhi(const QQuickGraphicsConfiguration &config) |
792 | { |
793 | if (ownRhi) |
794 | QSGRhiSupport::instance()->destroyRhi(rhi, config); |
795 | |
796 | rhi = nullptr; |
797 | |
798 | delete offscreenSurface; |
799 | offscreenSurface = nullptr; |
800 | } |
801 | |
802 | QT_END_NAMESPACE |
803 | |
804 | #include "moc_qquickrendercontrol.cpp" |
805 | |