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

source code of qtdeclarative/src/quick/items/qquickrhiitem.cpp