Qt
Internal/Contributor docs for the Qt SDK. Note: These are NOT official API docs; those are found at https://doc.qt.io/
Loading...
Searching...
No Matches
qquickrhiitem.cpp
Go to the documentation of this file.
1// Copyright (C) 2023 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// Qt-Security score:significant reason:default
4
6#include <QtQuick/private/qsgrhisupport_p.h>
7
9
10/*!
11 \class QQuickRhiItem
12 \inmodule QtQuick
13 \since 6.7
14
15 \brief The QQuickRhiItem class is a portable alternative to
16 QQuickFramebufferObject that is not tied to OpenGL, but rather allows
17 integrating rendering with the QRhi APIs with Qt Quick.
18
19 QQuickRhiItem is effectively the counterpart of \l QRhiWidget in the world of
20 Qt Quick. Both of these are meant to be subclassed, and they both enable
21 recording QRhi-based rendering that targets an offscreen color buffer. The
22 resulting 2D image is then composited with the rest of the Qt Quick scene.
23
24 \note While QQuickRhiItem is a public Qt API, the QRhi family of classes in
25 the Qt Gui module, including QShader and QShaderDescription, offer limited
26 compatibility guarantees. There are no source or binary compatibility
27 guarantees for these classes, meaning the API is only guaranteed to work
28 with the Qt version the application was developed against. Source
29 incompatible changes are however aimed to be kept at a minimum and will
30 only be made in minor releases (6.7, 6.8, and so on). \c{qquickrhiitem.h}
31 does not directly include any QRhi-related headers. To use those classes
32 when implementing a QQuickRhiItem subclass, link to
33 \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers
34 with the \c rhi prefix, for example \c{#include <rhi/qrhi.h>}.
35
36 QQuickRhiItem is a replacement for the legacy \l QQuickFramebufferObject
37 class. The latter is inherently tied to OpenGL / OpenGL ES, whereas
38 QQuickRhiItem works with the QRhi classes, allowing to run the same
39 rendering code with Vulkan, Metal, Direct 3D 11/12, and OpenGL / OpenGL ES.
40 Conceptually and functionally they are very close, and migrating from
41 QQuickFramebufferObject to QQuickRhiItem is straightforward.
42 QQuickFramebufferObject continues to be available to ensure compatibility
43 for existing application code that works directly with the OpenGL API.
44
45 \note QQuickRhiItem will not be functional when using the \c software
46 adaptation of the Qt Quick scene graph.
47
48 On most platforms, the scene graph rendering, and thus the rendering
49 performed by the QQuickRhiItem will occur on a \l {Scene Graph and
50 Rendering}{dedicated thread}. For this reason, the QQuickRhiItem class
51 enforces a strict separation between the item implementation (the
52 QQuickItem subclass) and the actual rendering logic. All item logic, such
53 as properties and UI-related helper functions exposed to QML must be
54 located in the QQuickRhiItem subclass. Everything that relates to rendering
55 must be located in the QQuickRhiItemRenderer class. To avoid race
56 conditions and read/write issues from two threads it is important that the
57 renderer and the item never read or write shared variables. Communication
58 between the item and the renderer should primarily happen via the
59 QQuickRhiItem::synchronize() function. This function will be called on the
60 render thread while the GUI thread is blocked. Using queued connections or
61 events for communication between item and renderer is also possible.
62
63 Applications must subclass both QQuickRhiItem and QQuickRhiItemRenderer.
64 The pure virtual createRenderer() function must be reimplemented to return
65 a new instance of the QQuickRhiItemRenderer subclass.
66
67 As with QRhiWidget, QQuickRhiItem automatically managed the color buffer,
68 which is a 2D texture (QRhiTexture) normally, or a QRhiRenderBuffer when
69 multisampling is in use. (some 3D APIs differentiate between textures and
70 renderbuffers, while with some others the underlying native resource is the
71 same; renderbuffers are used mainly to allow multisampling with OpenGL ES
72 3.0)
73
74 The size of the texture will by default adapt to the size of the item (with
75 the \l{QQuickWindow::effectiveDevicePixelRatio()}{device pixel ratio} taken
76 into account). If the item size changes, the texture is recreated with the
77 correct size. If a fixed size is preferred, set \l fixedColorBufferWidth and
78 \l fixedColorBufferHeight to non-zero values.
79
80 QQuickRhiItem is a \l{QSGTextureProvider}{texture provider} and can be used
81 directly in \l {ShaderEffect}{ShaderEffects} and other classes that consume
82 texture providers.
83
84 While not a primary use case, QQuickRhiItem also allows incorporating
85 rendering code that directly uses a 3D graphics API such as Vulkan, Metal,
86 Direct 3D, or OpenGL. See \l QRhiCommandBuffer::beginExternal() for details
87 on recording native commands within a QRhi render pass, as well as
88 \l QRhiTexture::createFrom() for a way to wrap an existing native texture and
89 then use it with QRhi in a subsequent render pass. See also
90 \l QQuickGraphicsConfiguration regarding configuring the native 3D API
91 environment (e.g. device extensions) and note that the \l QQuickWindow can be
92 associated with a custom \l QVulkanInstance by calling
93 \l QWindow::setVulkanInstance() early enough.
94
95 \note QQuickRhiItem always uses the same QRhi instance the QQuickWindow
96 uses (and by extension, the same OpenGL context, Vulkan device, etc.). To
97 choose which underlying 3D graphics API is used, call
98 \l{QQuickWindow::setGraphicsApi()}{setGraphicsApi()} on the QQuickWindow
99 early enough. Changing it is not possible once the scene graph has
100 initialized, and all QQuickRhiItem instances in the scene will render using
101 the same 3D API.
102
103 \section2 A simple example
104
105 Take the following subclass of QQuickRhiItem. It is shown here in complete
106 form. It renders a single triangle with a perspective projection, where the
107 triangle is rotated based on the \c angle property of the custom item.
108 (meaning it can be driven for example with animations such as
109 \l NumberAnimation from QML)
110
111 \snippet qquickrhiitem/qquickrhiitem_intro.cpp 0
112
113 It is notable that this simple class is almost exactly the same as the code
114 shown in the \l QRhiWidget introduction. The vertex and fragment shaders are
115 the same as well. These are provided as Vulkan-style GLSL source code and
116 must be processed first by the Qt shader infrastructure first. This is
117 achieved either by running the \c qsb command-line tool manually, or by
118 using the \l qt_add_shaders() function in CMake.
119 The QQuickRhiItem loads these pre-processed \c{.qsb}
120 files that are shipped with the application. See \l{Qt Shader Tools} for
121 more information about Qt's shader translation infrastructure.
122
123 \c{color.vert}
124
125 \snippet qquickrhiitem/qquickrhiitem_intro.vert 0
126
127 \c{color.frag}
128
129 \snippet qquickrhiitem/qquickrhiitem_intro.frag 0
130
131 Once exposed to QML (note the \c QML_NAMED_ELEMENT), our custom item can be
132 instantiated in any scene. (after importing the appropriate \c URI specified
133 for \l qt_add_qml_module() in the CMake project)
134
135 \code
136 ExampleRhiItem {
137 anchors.fill: parent
138 anchors.margins: 10
139 NumberAnimation on angle { from: 0; to: 360; duration: 5000; loops: Animation.Infinite }
140 }
141 \endcode
142
143 See \l{Scene Graph - RHI Texture Item} for a more complex example.
144
145 \sa QQuickRhiItemRenderer, {Scene Graph - RHI Texture Item}, QRhi, {Scene Graph and Rendering}
146 */
147
148/*!
149 \class QQuickRhiItemRenderer
150 \inmodule QtQuick
151 \since 6.7
152
153 \brief A QQuickRhiItemRenderer implements the rendering logic of a
154 QQuickRhiItem.
155
156 \preliminary
157
158 \note QQuickRhiItem and QQuickRhiItemRenderer are in tech preview in Qt
159 6.7. \b {The API is under development and subject to change.}
160
161 \sa QQuickRhiItem, QRhi
162 */
163
165 : m_item(item)
166{
167 m_window = m_item->window();
168 connect(m_window, &QQuickWindow::beforeRendering, this, &QQuickRhiItemNode::render,
169 Qt::DirectConnection);
170 connect(m_window, &QQuickWindow::screenChanged, this, [this]() {
171 if (m_window->effectiveDevicePixelRatio() != m_dpr)
172 m_item->update();
173 }, Qt::DirectConnection);
174}
175
176QSGTexture *QQuickRhiItemNode::texture() const
177{
178 return m_sgTexture.get();
179}
180
182{
183 // owns either m_colorTexture or m_resolveTexture
184 m_sgTexture.reset();
185
186 m_colorTexture = nullptr;
187 m_resolveTexture = nullptr;
188
189 m_msaaColorBuffer.reset();
190}
191
193{
194 m_renderTarget.reset();
195 m_renderPassDescriptor.reset();
196 m_depthStencilBuffer.reset();
197}
198
200{
201 if (!m_rhi) {
202 m_rhi = m_window->rhi();
203 if (!m_rhi) {
204 qWarning("No QRhi found for window %p, QQuickRhiItem will not be functional", m_window);
205 return;
206 }
207 }
208
209 m_dpr = m_window->effectiveDevicePixelRatio();
210 const int minTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMin);
211 const int maxTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMax);
212
213 QQuickRhiItemPrivate *itemD = m_item->d_func();
214 QSize newSize = QSize(itemD->fixedTextureWidth, itemD->fixedTextureHeight);
215 if (newSize.isEmpty())
216 newSize = (QSizeF(m_item->width(), m_item->height()) * m_dpr).toSize();
217
218 newSize.setWidth(qMin(maxTexSize, qMax(minTexSize, newSize.width())));
219 newSize.setHeight(qMin(maxTexSize, qMax(minTexSize, newSize.height())));
220
221 if (m_colorTexture) {
222 if (m_colorTexture->format() != itemD->rhiTextureFormat
223 || m_colorTexture->sampleCount() != itemD->samples)
224 {
227 }
228 }
229
230 if (m_msaaColorBuffer) {
231 if (m_msaaColorBuffer->backingFormat() != itemD->rhiTextureFormat
232 || m_msaaColorBuffer->sampleCount() != itemD->samples)
233 {
236 }
237 }
238
239 if (m_sgTexture && m_sgTexture->hasAlphaChannel() != itemD->blend) {
242 }
243
244 if (!m_colorTexture && itemD->samples <= 1) {
245 if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) {
246 qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the "
247 "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat));
248 }
249 m_colorTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, itemD->samples,
250 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
251 if (!m_colorTexture->create()) {
252 qWarning("Failed to create backing texture for QQuickRhiItem");
253 delete m_colorTexture;
254 m_colorTexture = nullptr;
255 return;
256 }
257 }
258
259 if (itemD->samples > 1) {
260 if (!m_msaaColorBuffer) {
261 if (!m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) {
262 qWarning("QQuickRhiItem: Multisample renderbuffers are reported as unsupported; "
263 "sample count %d will not work as expected", itemD->samples);
264 }
265 if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) {
266 qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the "
267 "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat));
268 }
269 m_msaaColorBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, newSize, itemD->samples,
270 {}, itemD->rhiTextureFormat));
271 if (!m_msaaColorBuffer->create()) {
272 qWarning("Failed to create multisample color buffer for QQuickRhiItem");
273 m_msaaColorBuffer.reset();
274 return;
275 }
276 }
277 if (!m_resolveTexture) {
278 m_resolveTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, 1,
279 QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
280 if (!m_resolveTexture->create()) {
281 qWarning("Failed to create resolve texture for QQuickRhiItem");
282 delete m_resolveTexture;
283 m_resolveTexture = nullptr;
284 return;
285 }
286 }
287 } else if (m_resolveTexture) {
288 m_resolveTexture->deleteLater();
289 m_resolveTexture = nullptr;
290 }
291
292 if (m_colorTexture && m_colorTexture->pixelSize() != newSize) {
293 m_colorTexture->setPixelSize(newSize);
294 if (!m_colorTexture->create())
295 qWarning("Failed to rebuild texture for QQuickRhiItem after resizing");
296 }
297
298 if (m_msaaColorBuffer && m_msaaColorBuffer->pixelSize() != newSize) {
299 m_msaaColorBuffer->setPixelSize(newSize);
300 if (!m_msaaColorBuffer->create())
301 qWarning("Failed to rebuild multisample color buffer for QQuickRhiitem after resizing");
302 }
303
304 if (m_resolveTexture && m_resolveTexture->pixelSize() != newSize) {
305 m_resolveTexture->setPixelSize(newSize);
306 if (!m_resolveTexture->create())
307 qWarning("Failed to rebuild resolve texture for QQuickRhiItem after resizing");
308 }
309
310 if (!m_sgTexture) {
311 QQuickWindow::CreateTextureOptions options;
312 if (itemD->blend)
313 options |= QQuickWindow::TextureHasAlphaChannel;
314 // the QSGTexture takes ownership of the QRhiTexture
315 m_sgTexture.reset(m_window->createTextureFromRhiTexture(m_colorTexture ? m_colorTexture : m_resolveTexture,
316 options));
317 setTexture(m_sgTexture.get());
318 }
319
320 if (itemD->autoRenderTarget) {
321 const QSize pixelSize = m_colorTexture ? m_colorTexture->pixelSize()
322 : m_msaaColorBuffer->pixelSize();
323 if (!m_depthStencilBuffer) {
324 m_depthStencilBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, itemD->samples,
325 QSGRhiSupport::depthStencilBufferFlags()));
326 if (!m_depthStencilBuffer->create()) {
327 qWarning("Failed to create depth-stencil buffer for QQuickRhiItem");
329 return;
330 }
331 } else if (m_depthStencilBuffer->pixelSize() != pixelSize) {
332 m_depthStencilBuffer->setPixelSize(pixelSize);
333 if (!m_depthStencilBuffer->create()) {
334 qWarning("Failed to rebuild depth-stencil buffer for QQuickRhiItem with new size");
335 return;
336 }
337 }
338 if (!m_renderTarget) {
339 QRhiColorAttachment color0;
340 if (m_colorTexture)
341 color0.setTexture(m_colorTexture);
342 else
343 color0.setRenderBuffer(m_msaaColorBuffer.get());
344 if (itemD->samples > 1)
345 color0.setResolveTexture(m_resolveTexture);
346 QRhiTextureRenderTargetDescription rtDesc(color0, m_depthStencilBuffer.get());
347 m_renderTarget.reset(m_rhi->newTextureRenderTarget(rtDesc));
348 m_renderPassDescriptor.reset(m_renderTarget->newCompatibleRenderPassDescriptor());
349 m_renderTarget->setRenderPassDescriptor(m_renderPassDescriptor.get());
350 if (!m_renderTarget->create()) {
351 qWarning("Failed to create render target for QQuickRhiitem");
353 return;
354 }
355 }
356 } else {
358 }
359
360 if (newSize != itemD->effectiveTextureSize) {
361 itemD->effectiveTextureSize = newSize;
362 emit m_item->effectiveColorBufferSizeChanged();
363 }
364
365 QRhiCommandBuffer *cb = queryCommandBuffer();
366 if (cb)
367 m_renderer->initialize(cb);
368
369 m_renderer->synchronize(m_item);
370}
371
373{
374 QRhiSwapChain *swapchain = m_window->swapChain();
375 QSGRendererInterface *rif = m_window->rendererInterface();
376
377 // Handle both cases: on-screen QQuickWindow vs. off-screen QQuickWindow
378 // e.g. by using QQuickRenderControl to redirect into a texture.
379 QRhiCommandBuffer *cb = swapchain ? swapchain->currentFrameCommandBuffer()
380 : static_cast<QRhiCommandBuffer *>(
381 rif->getResource(m_window, QSGRendererInterface::RhiRedirectCommandBuffer));
382
383 if (!cb) {
384 qWarning("QQuickRhiItem: Neither swapchain nor redirected command buffer are available.");
385 return nullptr;
386 }
387
388 return cb;
389}
390
391void QQuickRhiItemNode::render()
392{
393 // called before Qt Quick starts recording its main render pass
394
395 if (!isValid() || !m_renderPending)
396 return;
397
398 QRhiCommandBuffer *cb = queryCommandBuffer();
399 if (!cb)
400 return;
401
402 m_renderPending = false;
403 m_renderer->render(cb);
404
405 markDirty(QSGNode::DirtyMaterial);
406 emit textureChanged();
407}
408
409/*!
410 Constructs a new QQuickRhiItem with the given \a parent.
411 */
412QQuickRhiItem::QQuickRhiItem(QQuickItem *parent)
413 : QQuickItem(*new QQuickRhiItemPrivate, parent)
414{
415 setFlag(ItemHasContents);
416}
417
418/*!
419 * \internal
420 */
421QQuickRhiItem::QQuickRhiItem(QQuickRhiItemPrivate &dd, QQuickItem *parent)
422 : QQuickItem(dd, parent)
423{
424 setFlag(ItemHasContents);
425}
426
427/*!
428 Destructor.
429*/
430QQuickRhiItem::~QQuickRhiItem()
431{
432}
433
434/*!
435 \internal
436 */
437QSGNode *QQuickRhiItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
438{
439 // Changing to an empty size should not involve destroying and then later
440 // recreating the node, because we do not know how expensive the user's
441 // renderer setup is. Rather, keep the node if it already exist, and clamp
442 // all accesses to width and height. Hence the unusual !oldNode condition here.
443 if (!oldNode && (width() <= 0 || height() <= 0))
444 return nullptr;
445
446 Q_D(QQuickRhiItem);
447 QQuickRhiItemNode *n = static_cast<QQuickRhiItemNode *>(oldNode);
448 if (!n) {
449 if (!d->node)
450 d->node = new QQuickRhiItemNode(this);
451 if (!d->node->hasRenderer()) {
452 QQuickRhiItemRenderer *r = createRenderer();
453 if (r) {
454 r->node = d->node;
455 d->node->setRenderer(r);
456 } else {
457 qWarning("No QQuickRhiItemRenderer was created; the item will not render");
458 delete d->node;
459 d->node = nullptr;
460 return nullptr;
461 }
462 }
463 n = d->node;
464 }
465
466 n->sync();
467
468 if (!n->isValid()) {
469 delete n;
470 d->node = nullptr;
471 return nullptr;
472 }
473
474 if (window()->rhi()->isYUpInFramebuffer()) {
475 n->setTextureCoordinatesTransform(d->mirrorVertically
476 ? QSGSimpleTextureNode::NoTransform
477 : QSGSimpleTextureNode::MirrorVertically);
478 } else {
479 n->setTextureCoordinatesTransform(d->mirrorVertically
480 ? QSGSimpleTextureNode::MirrorVertically
481 : QSGSimpleTextureNode::NoTransform);
482 }
483 n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
484 n->setRect(0, 0, qMax(qreal(0), width()), qMax(qreal(0), height()));
485
486 n->scheduleUpdate();
487
488 return n;
489}
490
491/*!
492 \reimp
493 */
494bool QQuickRhiItem::event(QEvent *e)
495{
496 return QQuickItem::event(e);
497}
498
499/*!
500 \reimp
501 */
502void QQuickRhiItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
503{
504 QQuickItem::geometryChange(newGeometry, oldGeometry);
505 if (newGeometry.size() != oldGeometry.size())
506 update();
507}
508
509/*!
510 \reimp
511 */
512void QQuickRhiItem::releaseResources()
513{
514 // called on the gui thread if the item is removed from scene
515
516 Q_D(QQuickRhiItem);
517 d->node = nullptr;
518}
519
520void QQuickRhiItem::invalidateSceneGraph()
521{
522 // called on the render thread when the scenegraph is invalidated
523
524 Q_D(QQuickRhiItem);
525 d->node = nullptr;
526}
527
528/*!
529 \reimp
530 */
531bool QQuickRhiItem::isTextureProvider() const
532{
533 return true;
534}
535
536/*!
537 \reimp
538 */
539QSGTextureProvider *QQuickRhiItem::textureProvider() const
540{
541 if (QQuickItem::isTextureProvider()) // e.g. if Item::layer::enabled == true
542 return QQuickItem::textureProvider();
543
544 Q_D(const QQuickRhiItem);
545 if (!d->node) // create a node to have a provider, the texture will be null but that's ok
546 d->node = new QQuickRhiItemNode(const_cast<QQuickRhiItem *>(this));
547
548 return d->node;
549}
550
551/*!
552 \property QQuickRhiItem::sampleCount
553
554 This property controls for sample count for multisample antialiasing.
555 By default the value is \c 1 which means MSAA is disabled.
556
557 Valid values are 1, 4, 8, and sometimes 16 and 32.
558 \l QRhi::supportedSampleCounts() can be used to query the supported sample
559 counts at run time, but typically applications should request 1 (no MSAA),
560 4x (normal MSAA) or 8x (high MSAA).
561
562 \note Setting a new value implies that all QRhiGraphicsPipeline objects
563 created by the renderer must use the same sample count from then on.
564 Existing QRhiGraphicsPipeline objects created with a different sample count
565 must not be used anymore. When the value changes, all color and
566 depth-stencil buffers are destroyed and recreated automatically, and
567 \l {QQuickRhiItemRenderer::}{initialize()} is invoked again. However, when
568 isAutoRenderTargetEnabled() is \c false, it will be up to the application to
569 manage this with regards to the depth-stencil buffer or additional color
570 buffers.
571
572 Changing the sample count from the default 1 to a higher value implies that
573 \l {QQuickRhiItemRenderer::}{colorTexture()} becomes \nullptr and
574 \l {QQuickRhiItemRenderer::}{msaaColorBuffer()} starts returning a
575 valid object. Switching back to 1 (or 0), implies the opposite: in the next
576 call to initialize() msaaColorBuffer() is going to return \nullptr, whereas
577 colorTexture() becomes once again valid. In addition,
578 \l {QQuickRhiItemRenderer::}{resolveTexture()}
579 returns a valid (non-multisample) QRhiTexture whenever the sample count is
580 greater than 1 (i.e., MSAA is in use).
581
582 \sa QQuickRhiItemRenderer::msaaColorBuffer(),
583 QQuickRhiItemRenderer::resolveTexture()
584 */
585
586int QQuickRhiItem::sampleCount() const
587{
588 Q_D(const QQuickRhiItem);
589 return d->samples;
590}
591
592void QQuickRhiItem::setSampleCount(int samples)
593{
594 Q_D(QQuickRhiItem);
595 if (d->samples == samples)
596 return;
597
598 d->samples = samples;
599 emit sampleCountChanged();
600 update();
601}
602
603/*!
604 \enum QQuickRhiItem::TextureFormat
605 Specifies the format of the backing texture to which the QQuickRhiItem renders.
606
607 \value RGBA8 See QRhiTexture::RGBA8. This is the default.
608 \value RGBA16F See QRhiTexture::RGBA16F.
609 \value RGBA32F See QRhiTexture::RGBA32F.
610 \value RGB10A2 See QRhiTexture::RGB10A2.
611
612 \sa QRhiTexture
613 */
614
615/*!
616 \property QQuickRhiItem::colorBufferFormat
617
618 This property controls the texture format for the texture used as the color
619 buffer. The default value is TextureFormat::RGBA8. QQuickRhiItem supports
620 rendering to a subset of the formats supported by \l QRhiTexture. Only
621 formats that are reported as supported from
622 \l QRhi::isTextureFormatSupported() should be specified, rendering will not be
623 functional otherwise.
624
625 \note Setting a new format when the item and its renderer are already
626 initialized and have rendered implies that all QRhiGraphicsPipeline objects
627 created by the renderer may become unusable, if the associated
628 QRhiRenderPassDescriptor is now incompatible due to the different texture
629 format. Similarly to changing
630 \l sampleCount dynamically, this means that initialize() or render()
631 implementations must then take care of releasing the existing pipelines and
632 creating new ones.
633 */
634
635QQuickRhiItem::TextureFormat QQuickRhiItem::colorBufferFormat() const
636{
637 Q_D(const QQuickRhiItem);
638 return d->itemTextureFormat;
639}
640
641void QQuickRhiItem::setColorBufferFormat(TextureFormat format)
642{
643 Q_D(QQuickRhiItem);
644 if (d->itemTextureFormat == format)
645 return;
646
647 d->itemTextureFormat = format;
648 switch (format) {
649 case TextureFormat::RGBA8:
650 d->rhiTextureFormat = QRhiTexture::RGBA8;
651 break;
652 case TextureFormat::RGBA16F:
653 d->rhiTextureFormat = QRhiTexture::RGBA16F;
654 break;
655 case TextureFormat::RGBA32F:
656 d->rhiTextureFormat = QRhiTexture::RGBA32F;
657 break;
658 case TextureFormat::RGB10A2:
659 d->rhiTextureFormat = QRhiTexture::RGB10A2;
660 break;
661 }
662 emit colorBufferFormatChanged();
663 update();
664}
665
666/*!
667 \return the current automatic depth-stencil buffer and render target management setting.
668
669 By default this value is \c true.
670
671 \sa setAutoRenderTarget()
672 */
673bool QQuickRhiItem::isAutoRenderTargetEnabled() const
674{
675 Q_D(const QQuickRhiItem);
676 return d->autoRenderTarget;
677}
678
679/*!
680 Controls if a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
681 is created and maintained automatically by the item. The default value is
682 \c true. Call this function early on, for example from the derived class'
683 constructor, with \a enabled set to \c false to disable this.
684
685 In automatic mode, the size and sample count of the depth-stencil buffer
686 follows the color buffer texture's settings. In non-automatic mode,
687 renderTarget() and depthStencilBuffer() always return \nullptr and it is
688 then up to the application's implementation of initialize() to take care of
689 setting up and managing these objects.
690 */
691void QQuickRhiItem::setAutoRenderTarget(bool enabled)
692{
693 Q_D(QQuickRhiItem);
694 if (d->autoRenderTarget == enabled)
695 return;
696
697 d->autoRenderTarget = enabled;
698 emit autoRenderTargetChanged();
699 update();
700}
701
702/*!
703 \property QQuickRhiItem::mirrorVertically
704
705 This property controls if texture UVs are flipped when drawing the textured
706 quad. It has no effect on the contents of the offscreen color buffer and
707 the rendering implemented by the QQuickRhiItemRenderer.
708
709 The default value is \c false.
710 */
711
712bool QQuickRhiItem::isMirrorVerticallyEnabled() const
713{
714 Q_D(const QQuickRhiItem);
715 return d->mirrorVertically;
716}
717
718void QQuickRhiItem::setMirrorVertically(bool enable)
719{
720 Q_D(QQuickRhiItem);
721 if (d->mirrorVertically == enable)
722 return;
723
724 d->mirrorVertically = enable;
725 emit mirrorVerticallyChanged();
726 update();
727}
728
729/*!
730 \property QQuickRhiItem::fixedColorBufferWidth
731
732 The fixed width, in pixels, of the item's associated texture or
733 renderbuffer. Relevant when a fixed color buffer size is desired that does
734 not depend on the item's size. This size has no effect on the geometry of
735 the item (its size and placement within the scene), which means the
736 texture's content will appear stretched (scaled up) or scaled down onto the
737 item's area.
738
739 For example, setting a size that is exactly twice the item's (pixel) size
740 effectively performs 2x supersampling (rendering at twice the resolution and
741 then implicitly scaling down when texturing the quad corresponding to the
742 item in the scene). On the other hand, setting a size that is half of the
743 item's pixel size effectively achieves rendering at half resolution and then
744 upscaling the results.
745
746 By default the value is \c 0. A value of 0 means that texture's size
747 follows the item's size. (\c{texture size} = \c{item size} * \c{device
748 pixel ratio}).
749
750 \note The device pixel ratio (the system compositor's scale factor) can have
751 a big impact on performance, since a scale factor of 2 (200%) means
752 rendering at twice the resolution, so twice of what the developer and UI
753 designer perceives as the item's size, and then effectively downscaling the
754 content, similarly to what happens when setting this property to twice the
755 item's pixel size on a system where the device pixel ratio is 1. Therefore,
756 this property is expected to be rarely used with sizes bigger than the
757 item's pixel size, since many modern desktop systems have neither the need
758 nor have the performance budget for it, when a larger than 1 device pixel
759 ratio is used anyway by the system. Instead, the main use case for this
760 property is to set a smaller size, in order to render at a reasonable
761 smaller resolution instead of blindly following the item (and perhaps
762 window) geometry, however big that may be.
763 */
764int QQuickRhiItem::fixedColorBufferWidth() const
765{
766 Q_D(const QQuickRhiItem);
767 return d->fixedTextureWidth;
768}
769
770void QQuickRhiItem::setFixedColorBufferWidth(int width)
771{
772 Q_D(QQuickRhiItem);
773 if (d->fixedTextureWidth == width)
774 return;
775
776 d->fixedTextureWidth = width;
777 emit fixedColorBufferWidthChanged();
778 update();
779}
780
781/*!
782 \property QQuickRhiItem::fixedColorBufferHeight
783
784 The fixed height, in pixels, of the item's associated texture. Relevant when
785 a fixed texture size is desired that does not depend on the item's size.
786 This size has no effect on the geometry of the item (its size and placement
787 within the scene), which means the texture's content will appear stretched
788 (scaled up) or scaled down onto the item's area.
789
790 By default the value is \c 0. A value of 0 means that texture's size
791 follows the item's size. (\c{texture size} = \c{item size} * \c{device
792 pixel ratio}).
793
794 See \l fixedColorBufferWidth for more information on the use cases for
795 setting a fixed width and height.
796 */
797
798int QQuickRhiItem::fixedColorBufferHeight() const
799{
800 Q_D(const QQuickRhiItem);
801 return d->fixedTextureHeight;
802}
803
804void QQuickRhiItem::setFixedColorBufferHeight(int height)
805{
806 Q_D(QQuickRhiItem);
807 if (d->fixedTextureHeight == height)
808 return;
809
810 d->fixedTextureHeight = height;
811 emit fixedColorBufferHeightChanged();
812 update();
813}
814
815/*!
816 \property QQuickRhiItem::effectiveColorBufferSize
817
818 This property exposes the size, in pixels, of the underlying color buffer
819 (the QRhiTexture or QRhiRenderBuffer). It is provided for use on the GUI
820 (main) thread, in QML bindings or JavaScript.
821
822 \note QQuickRhiItemRenderer implementations, operating on the scene graph
823 render thread, should not use this property. Those should rather query the
824 size from the
825 \l{QQuickRhiItemRenderer::renderTarget()}{render target}.
826
827 \note The value becomes available asynchronously from the main thread's
828 perspective in the sense that the value changes when rendering happens on
829 the render thread. This means that this property is useful mainly in QML
830 bindings. Application code must not assume that the value is up to date
831 already when the QQuickRhiItem object is constructed.
832
833 This is a read-only property.
834 */
835
836QSize QQuickRhiItem::effectiveColorBufferSize() const
837{
838 Q_D(const QQuickRhiItem);
839 return d->effectiveTextureSize;
840}
841
842/*!
843 \property QQuickRhiItem::alphaBlending
844
845 Controls if blending is always enabled when drawing the quad textured with
846 the content generated by the QQuickRhiItem and its renderer.
847
848 The default value is \c false. This is for performance reasons: if
849 semi-transparency is not involved, because the QQuickRhiItemRenderer clears
850 to an opaque color and never renders fragments with alpha smaller than 1,
851 then there is no point in enabling blending.
852
853 If the QQuickRhiItemRenderer subclass renders with semi-transparency involved,
854 set this property to true.
855
856 \note Under certain conditions blending is still going to happen regardless
857 of the value of this property. For example, if the item's
858 \l{QQuickItem::opacity}{opacity} (more precisely, the combined opacity
859 inherited from the parent chain) is smaller than 1, blending will be
860 automatically enabled even when this property is set to false.
861
862 \note The Qt Quick scene graph relies on and expect pre-multiplied alpha.
863 For example, if the intention is to clear the background in the renderer to
864 an alpha value of 0.5, then make sure to multiply the red, green, and blue
865 clear color values with 0.5 as well. Otherwise the blending results will be
866 incorrect.
867 */
868
869bool QQuickRhiItem::alphaBlending() const
870{
871 Q_D(const QQuickRhiItem);
872 return d->blend;
873}
874
875void QQuickRhiItem::setAlphaBlending(bool enable)
876{
877 Q_D(QQuickRhiItem);
878 if (d->blend == enable)
879 return;
880
881 d->blend = enable;
882 emit alphaBlendingChanged();
883 update();
884}
885
886/*!
887 Constructs a new renderer.
888
889 This function is called on the rendering thread during the scene graph sync
890 phase when the GUI thread is blocked.
891
892 \sa QQuickRhiItem::createRenderer()
893 */
894QQuickRhiItemRenderer::QQuickRhiItemRenderer()
895{
896}
897
898/*!
899 The Renderer is automatically deleted when the scene graph resources for
900 the QQuickRhiItem item are cleaned up.
901
902 This function is called on the rendering thread.
903
904 Under certain conditions it is normal and expected that the renderer object
905 is destroyed and then recreated. This is because the renderer's lifetime
906 effectively follows the underlying scene graph node. For example, when
907 changing the parent of a QQuickRhiItem object so that it then belongs to a
908 different \l QQuickWindow, the scene graph nodes are all dropped and
909 recreated due to the window change. This will also involve dropping and
910 creating a new QQuickRhiItemRenderer.
911
912 Unlike \l QRhiWidget, QQuickRhiItemRenderer has no need to implement
913 additional code paths for releasing (or early-relasing) graphics resources
914 created via QRhi. It is sufficient to release everything in the destructor,
915 or rely on smart pointers.
916 */
917QQuickRhiItemRenderer::~QQuickRhiItemRenderer()
918{
919}
920
921/*!
922 Call this function when the content of the offscreen color buffer should be
923 updated. (i.e. to request that render() is called again; the call will
924 happen at a later point, and note that updates are typically throttled to
925 the presentation rate)
926
927 This function can be called from render() to schedule an update.
928
929 \note This function should be used from inside the renderer. To update
930 the item on the GUI thread, use QQuickRhiItem::update().
931 */
932void QQuickRhiItemRenderer::update()
933{
934 if (node)
935 node->scheduleUpdate();
936}
937
938/*!
939 \return the current QRhi object.
940
941 Must only be called from initialize() and render().
942 */
943QRhi *QQuickRhiItemRenderer::rhi() const
944{
945 return node ? node->m_rhi : nullptr;
946}
947
948/*!
949 \return the texture serving as the color buffer for the item.
950
951 Must only be called from initialize() and render().
952
953 Unlike the depth-stencil buffer and the QRhiRenderTarget, this texture is
954 always available and is managed by the QQuickRhiItem, independent of the
955 value of \l {QQuickRhiItem::}{isAutoRenderTargetEnabled}.
956
957 \note When \l {QQuickRhiItem::}{sampleCount} is larger than 1, and so
958 multisample antialiasing is enabled, the return value is \nullptr. Instead,
959 query the \l QRhiRenderBuffer by calling msaaColorBuffer().
960
961 \note The backing texture size and sample count can also be queried via the
962 QRhiRenderTarget returned from renderTarget(). This can be more convenient
963 and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
964 it works regardless of multisampling is in use or not.
965
966 \sa msaaColorBuffer(), depthStencilBuffer(), renderTarget(), resolveTexture()
967 */
968QRhiTexture *QQuickRhiItemRenderer::colorTexture() const
969{
970 return node ? node->m_colorTexture : nullptr;
971}
972
973/*!
974 \return the renderbuffer serving as the multisample color buffer for the item.
975
976 Must only be called from initialize() and render().
977
978 When \l {QQuickRhiItem::}{sampleCount} is larger than 1, and so multisample
979 antialising is enabled, the returned QRhiRenderBuffer has a matching sample
980 count and serves as the color buffer. Graphics pipelines used to render
981 into this buffer must be created with the same sample count, and the
982 depth-stencil buffer's sample count must match as well. The multisample
983 content is expected to be resolved into the texture returned from
984 resolveTexture(). When \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is
985 \c true, renderTarget() is set up automatically to do this, by setting up
986 msaaColorBuffer() as the
987 \l{QRhiColorAttachment::renderBuffer()}{renderbuffer} of color attachment 0
988 and resolveTexture() as its
989 \l{QRhiColorAttachment::resolveTexture()}{resolveTexture}.
990
991 When MSAA is not in use, the return value is \nullptr. Use colorTexture()
992 instead then.
993
994 Depending on the underlying 3D graphics API, there may be no practical
995 difference between multisample textures and color renderbuffers with a
996 sample count larger than 1 (QRhi may just map both to the same native
997 resource type). Some older APIs however may differentiate between textures
998 and renderbuffers. In order to support OpenGL ES 3.0, where multisample
999 renderbuffers are available, but multisample textures are not, QQuickRhiItem
1000 always performs MSAA by using a multisample QRhiRenderBuffer as the color
1001 attachment (and never a multisample QRhiTexture).
1002
1003 \note The backing texture size and sample count can also be queried via the
1004 QRhiRenderTarget returned from renderTarget(). This can be more convenient
1005 and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
1006 it works regardless of multisampling is in use or not.
1007
1008 \sa colorTexture(), depthStencilBuffer(), renderTarget(), resolveTexture()
1009 */
1010QRhiRenderBuffer *QQuickRhiItemRenderer::msaaColorBuffer() const
1011{
1012 return node ? node->m_msaaColorBuffer.get() : nullptr;
1013}
1014
1015/*!
1016 \return the non-multisample texture to which the multisample content is resolved.
1017
1018 The result is \nullptr when multisample antialiasing is not enabled.
1019
1020 Must only be called from initialize() and render().
1021
1022 With MSAA enabled, this is the texture that gets used by the item's
1023 underlying scene graph node when texturing a quad in the main render pass
1024 of Qt Quick. However, the QQuickRhiItemRenderer's rendering must target the
1025 (multisample) QRhiRenderBuffer returned from msaaColorBuffer(). When \l
1026 {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c true, this is taken care
1027 of by the QRhiRenderTarget returned from renderTarget(). Otherwise, it is
1028 up to the subclass code to correctly configure a render target object with
1029 both the color buffer and resolve textures.
1030
1031 \sa colorTexture()
1032 */
1033QRhiTexture *QQuickRhiItemRenderer::resolveTexture() const
1034{
1035 return node ? node->m_resolveTexture : nullptr;
1036}
1037
1038/*!
1039 \return the depth-stencil buffer used by the item's rendering.
1040
1041 Must only be called from initialize() and render().
1042
1043 Available only when \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c
1044 true. Otherwise the returned value is \nullptr and it is up the
1045 reimplementation of initialize() to create and manage a depth-stencil
1046 buffer and a QRhiTextureRenderTarget.
1047
1048 \sa colorTexture(), renderTarget()
1049 */
1050QRhiRenderBuffer *QQuickRhiItemRenderer::depthStencilBuffer() const
1051{
1052 return node ? node->m_depthStencilBuffer.get() : nullptr;
1053}
1054
1055/*!
1056 \return the render target object that must be used with
1057 \l QRhiCommandBuffer::beginPass() in reimplementations of render().
1058
1059 Must only be called from initialize() and render().
1060
1061 Available only when \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c
1062 true. Otherwise the returned value is \nullptr and it is up the
1063 reimplementation of initialize() to create and manage a depth-stencil
1064 buffer and a QRhiTextureRenderTarget.
1065
1066 When creating \l{QRhiGraphicsPipeline}{graphics pipelines}, a
1067 QRhiRenderPassDescriptor is needed. This can be queried from the returned
1068 QRhiTextureRenderTarget by calling
1069 \l{QRhiTextureRenderTarget::renderPassDescriptor()}{renderPassDescriptor()}.
1070
1071 \note The returned QRhiTextureRenderTarget always reports a
1072 \l{QRhiTextureRenderTarget::}{devicePixelRatio()} of \c 1.
1073 This is because only swapchains and the associated window have a concept of
1074 device pixel ratio, not textures, and the render target here always refers
1075 to a texture. If the on-screen scale factor is relevant for rendering,
1076 query and store it via the item's
1077 \c{window()->effectiveDevicePixelRatio()} in \l synchronize().
1078 When doing so, always prefer using \l{QQuickWindow::}{effectiveDevicePixelRatio()}
1079 over the base class' \l{QWindow::}{devicePixelRatio()}.
1080
1081 \sa colorTexture(), depthStencilBuffer(), QQuickWindow::effectiveDevicePixelRatio()
1082 */
1083QRhiRenderTarget *QQuickRhiItemRenderer::renderTarget() const
1084{
1085 return node ? node->m_renderTarget.get() : nullptr;
1086}
1087
1088/*!
1089 \fn QQuickRhiItemRenderer *QQuickRhiItem::createRenderer()
1090
1091 Reimplement this function to create and return a new instance of a
1092 QQuickRhiItemRenderer subclass.
1093
1094 This function will be called on the rendering thread while the GUI thread
1095 is blocked.
1096 */
1097
1098/*!
1099 \fn void QQuickRhiItemRenderer::initialize(QRhiCommandBuffer *cb)
1100
1101 Called when the item is initialized for the first time, when the
1102 associated texture's size, format, or sample count changes, or when the
1103 QRhi or texture change for any reason. The function is expected to
1104 maintain (create if not yet created, adjust and rebuild if the size has
1105 changed) the graphics resources used by the rendering code in render().
1106
1107 To query the QRhi, QRhiTexture, and other related objects, call rhi(),
1108 colorTexture(), depthStencilBuffer(), and renderTarget().
1109
1110 When the item size changes, the QRhi object, the color buffer texture,
1111 and the depth stencil buffer objects are all the same instances (so the
1112 getters return the same pointers) as before, but the color and
1113 depth/stencil buffers will likely have been rebuilt, meaning the
1114 \l{QRhiTexture::pixelSize()}{size} and the underlying native texture
1115 resource may be different than in the last invocation.
1116
1117 Reimplementations should also be prepared that the QRhi object and the
1118 color buffer texture may change between invocations of this function. For
1119 example, when the item is reparented so that it belongs to a new
1120 QQuickWindow, the the QRhi and all related resources managed by the
1121 QQuickRhiItem will be different instances than before in the subsequent
1122 call to this function. Is is then important that all existing QRhi
1123 resources previously created by the subclass are destroyed because they
1124 belong to the previous QRhi that should not be used anymore.
1125
1126 When \l {QQuickRhiItem::}{isAutoRenderTargetEnabled} is \c true, which is
1127 the default, a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
1128 associated with the colorTexture() (or msaaColorBuffer()) and the
1129 depth-stencil buffer are created and managed automatically.
1130 Reimplementations of initialize() and render() can query those objects via
1131 depthStencilBuffer() and renderTarget(). When \l
1132 {QQuickRhiItem::}{isAutoRenderTargetEnabled} is set to \c false, these
1133 objects are no longer created and managed automatically. Rather, it will be
1134 up the the initialize() implementation to create buffers and set up the
1135 render target as it sees fit. When manually managing additional color or
1136 depth-stencil attachments for the render target, their size and sample
1137 count must always follow the size and sample count of colorTexture() (or
1138 msaaColorBuffer()), otherwise rendering or 3D API validation errors may
1139 occur.
1140
1141 The subclass-created graphics resources are expected to be released in the
1142 destructor implementation of the subclass.
1143
1144 \a cb is the QRhiCommandBuffer for the current frame. The function is
1145 called with a frame being recorded, but without an active render pass. The
1146 command buffer is provided primarily to allow enqueuing
1147 \l{QRhiCommandBuffer::resourceUpdate()}{resource updates} without deferring
1148 to render().
1149
1150 This function is called on the render thread, if there is one.
1151
1152 \sa render()
1153 */
1154
1155/*!
1156 \fn void QQuickRhiItemRenderer::synchronize(QQuickRhiItem *item)
1157
1158 This function is called on the render thread, if there is one, while the
1159 main/GUI thread is blocked. It is called from
1160 \l{QQuickItem::updatePaintNode()}{the \a {item}'s synchronize step},
1161 and allows reading and writing data belonging to the main and render
1162 threads. Typically property values stored in the QQuickRhiItem are copied
1163 into the QQuickRhiItemRenderer, so that they can be safely read afterwards
1164 in render() when the render and main threads continue to work in parallel.
1165
1166 \sa initialize(), render()
1167 */
1168
1169/*!
1170 \fn void QQuickRhiItemRenderer::render(QRhiCommandBuffer *cb)
1171
1172 Called when the backing color buffer's contents needs updating.
1173
1174 There is always at least one call to initialize() before this function is
1175 called.
1176
1177 To request updates, call \l QQuickItem::update() when calling from QML or
1178 from C++ code on the main/GUI thread (e.g. when in a property setter), or
1179 \l update() when calling from within a QQuickRhiItemRenderer callback.
1180 Calling QQuickRhiItemRenderer's update() from within
1181 render() will lead to triggering updates continuously.
1182
1183 \a cb is the QRhiCommandBuffer for the current frame. The function is
1184 called with a frame being recorded, but without an active render pass.
1185
1186 This function is called on the render thread, if there is one.
1187
1188 \sa initialize(), synchronize()
1189 */
1190
1191QT_END_NAMESPACE
1192
1193#include "moc_qquickrhiitem.cpp"
1194#include "moc_qquickrhiitem_p.cpp"
QRhiTexture * m_colorTexture
QQuickRhiItem * m_item
QRhiCommandBuffer * queryCommandBuffer()
bool isValid() const
QRhiTexture * m_resolveTexture
\inmodule QtQuick
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:580
\inmodule QtGuiPrivate \inheaderfile rhi/qrhi.h
Definition qrhi.h:624
Combined button and popup list for selecting options.