aboutsummaryrefslogtreecommitdiffstats
path: root/src/quick/items/qquickrhiitem.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/quick/items/qquickrhiitem.cpp')
-rw-r--r--src/quick/items/qquickrhiitem.cpp1144
1 files changed, 1144 insertions, 0 deletions
diff --git a/src/quick/items/qquickrhiitem.cpp b/src/quick/items/qquickrhiitem.cpp
new file mode 100644
index 0000000000..69b6bb15ed
--- /dev/null
+++ b/src/quick/items/qquickrhiitem.cpp
@@ -0,0 +1,1144 @@
+// Copyright (C) 2023 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
+
+#include "qquickrhiitem_p.h"
+
+QT_BEGIN_NAMESPACE
+
+/*!
+ \class QQuickRhiItem
+ \inmodule QtQuick
+ \since 6.7
+
+ \brief The QQuickRhiItem class is a portable alternative to
+ QQuickFramebufferObject that is not tied to OpenGL, but rather allows
+ integrating rendering with the QRhi APIs with Qt Quick.
+
+ \preliminary
+
+ \note QQuickRhiItem is in tech preview in Qt 6.7. \b {The API is under
+ development and subject to change.}
+
+ QQuickRhiItem is effectively the counterpart of \l QRhiWidget in the world of
+ Qt Quick. Both of these are meant to be subclassed, and they both enable
+ recording QRhi-based rendering that targets an offscreen color buffer. The
+ resulting 2D image is then composited with the rest of the Qt Quick scene.
+
+ \note While QQuickRhiItem is a public Qt API, the QRhi family of classes in
+ the Qt Gui module, including QShader and QShaderDescription, offer limited
+ compatibility guarantees. There are no source or binary compatibility
+ guarantees for these classes, meaning the API is only guaranteed to work
+ with the Qt version the application was developed against. Source
+ incompatible changes are however aimed to be kept at a minimum and will
+ only be made in minor releases (6.7, 6.8, and so on). \c{qquickrhiitem.h}
+ does not directly include any QRhi-related headers. To use those classes
+ when implementing a QQuickRhiItem subclass, link to
+ \c{Qt::GuiPrivate} (if using CMake), and include the appropriate headers
+ with the \c rhi prefix, for example \c{#include <rhi/qrhi.h>}.
+
+ QQuickRhiItem is a replacement for the legacy \l QQuickFramebufferObject
+ class. The latter is inherently tied to OpenGL / OpenGL ES, whereas
+ QQuickRhiItem works with the QRhi classes, allowing to run the same
+ rendering code with Vulkan, Metal, Direct 3D 11/12, and OpenGL / OpenGL ES.
+ Conceptually and functionally they are very close, and migrating from
+ QQuickFramebufferObject to QQuickRhiItem is straightforward.
+ QQuickFramebufferObject continues to be available to ensure compatibility
+ for existing application code that works directly with the OpenGL API.
+
+ \note QQuickRhiItem will not be functional when using the \c software
+ adaptation of the Qt Quick scene graph.
+
+ On most platforms, the scene graph rendering, and thus the rendering
+ performed by the QQuickRhiItem will occur on a \l {Scene Graph and
+ Rendering}{dedicated thread}. For this reason, the QQuickRhiItem class
+ enforces a strict separation between the item implementation (the
+ QQuickItem subclass) and the actual rendering logic. All item logic, such
+ as properties and UI-related helper functions exposed to QML must be
+ located in the QQuickRhiItem subclass. Everything that relates to rendering
+ must be located in the QQuickRhiItemRenderer class. To avoid race
+ conditions and read/write issues from two threads it is important that the
+ renderer and the item never read or write shared variables. Communication
+ between the item and the renderer should primarily happen via the
+ QQuickRhiItem::synchronize() function. This function will be called on the
+ render thread while the GUI thread is blocked. Using queued connections or
+ events for communication between item and renderer is also possible.
+
+ Applications must subclass both QQuickRhiItem and QQuickRhiItemRenderer.
+ The pure virtual createRenderer() function must be reimplemented to return
+ a new instance of the QQuickRhiItemRenderer subclass.
+
+ As with QRhiWidget, QQuickRhiItem automatically managed the color buffer,
+ which is a 2D texture (QRhiTexture) normally, or a QRhiRenderBuffer when
+ multisampling is in use. (some 3D APIs differentiate between textures and
+ renderbuffers, while with some others the underlying native resource is the
+ same; renderbuffers are used mainly to allow multisampling with OpenGL ES
+ 3.0)
+
+ The size of the texture will by default adapt to the size of the item (with
+ the \l{QQuickWindow::effectiveDevicePixelRatio()}{device pixel ratio} taken
+ into account). If the item size changes, the texture is recreated with the
+ correct size. If a fixed size is preferred, set \l fixedColorBufferWidth and
+ \l fixedColorBufferHeight to non-zero values.
+
+ QQuickRhiItem is a \l{QSGTextureProvider}{texture provider} and can be used
+ directly in \l {ShaderEffect}{ShaderEffects} and other classes that consume
+ texture providers.
+
+ While not a primary use case, QQuickRhiItem also allows incorporating
+ rendering code that directly uses a 3D graphics API such as Vulkan, Metal,
+ Direct 3D, or OpenGL. See \l QRhiCommandBuffer::beginExternal() for details
+ on recording native commands within a QRhi render pass, as well as
+ \l QRhiTexture::createFrom() for a way to wrap an existing native texture and
+ then use it with QRhi in a subsequent render pass. See also
+ \l QQuickGraphicsConfiguration regarding configuring the native 3D API
+ environment (e.g. device extensions) and note that the \l QQuickWindow can be
+ associated with a custom \l QVulkanInstance by calling
+ \l QWindow::setVulkanInstance() early enough.
+
+ \note QQuickRhiItem always uses the same QRhi instance the QQuickWindow
+ uses (and by extension, the same OpenGL context, Vulkan device, etc.). To
+ choose which underlying 3D graphics API is used, call
+ \l{QQuickWindow::setGraphicsApi()}{setGraphicsApi()} on the QQuickWindow
+ early enough. Changing it is not possible once the scene graph has
+ initialized, and all QQuickRhiItem instances in the scene will render using
+ the same 3D API.
+
+ \section2 A simple example
+
+ Take the following subclass of QQuickRhiItem. It is shown here in complete
+ form. It renders a single triangle with a perspective projection, where the
+ triangle is rotated based on the \c angle property of the custom item.
+ (meaning it can be driven for example with animations such as
+ \l NumberAnimation from QML)
+
+ \snippet qquickrhiitem/qquickrhiitem_intro.cpp 0
+
+ It is notable that this simple class is almost exactly the same as the code
+ shown in the \l QRhiWidget introduction. The vertex and fragment shaders
+ are the same as shown there.
+
+ Once exposed to QML (note the \c QML_NAMED_ELEMENT), our custom item can be
+ instantiated in any scene. (after importing the appropriate \c URI specified
+ for \l{qt6_add_qml_module}{qt_add_qml_module} in the CMake project)
+
+ \code
+ ExampleRhiItem {
+ anchors.fill: parent
+ anchors.margins: 10
+ NumberAnimation on angle { from: 0; to: 360: duration: 5000; loops: Animation.Infinite }
+ }
+ \endcode
+
+ See \l{Scene Graph - RHI Texture Item} for a more complex example.
+
+ \sa QQuickRhiItemRenderer, {Scene Graph - RHI Texture Item}, QRhi, {Scene Graph and Rendering}
+ */
+
+/*!
+ \class QQuickRhiItemRenderer
+ \inmodule QtQuick
+ \since 6.7
+
+ \brief A QQuickRhiItemRenderer implements the rendering logic of a
+ QQuickRhiItem.
+
+ \preliminary
+
+ \note QQuickRhiItem and QQuickRhiItemRenderer are in tech preview in Qt
+ 6.7. \b {The API is under development and subject to change.}
+
+ \sa QQuickRhiItem, QRhi
+ */
+
+QQuickRhiItemNode::QQuickRhiItemNode(QQuickRhiItem *item)
+ : m_item(item)
+{
+ m_window = m_item->window();
+ connect(m_window, &QQuickWindow::beforeRendering, this, &QQuickRhiItemNode::render,
+ Qt::DirectConnection);
+ connect(m_window, &QQuickWindow::screenChanged, this, [this]() {
+ if (m_window->effectiveDevicePixelRatio() != m_dpr)
+ m_item->update();
+ }, Qt::DirectConnection);
+}
+
+QSGTexture *QQuickRhiItemNode::texture() const
+{
+ return m_sgTexture.get();
+}
+
+void QQuickRhiItemNode::resetColorBufferObjects()
+{
+ // owns either m_colorTexture or m_resolveTexture
+ m_sgTexture.reset();
+
+ m_colorTexture = nullptr;
+ m_resolveTexture = nullptr;
+
+ m_msaaColorBuffer.reset();
+}
+
+void QQuickRhiItemNode::resetRenderTargetObjects()
+{
+ m_renderTarget.reset();
+ m_renderPassDescriptor.reset();
+ m_depthStencilBuffer.reset();
+}
+
+void QQuickRhiItemNode::sync()
+{
+ if (!m_rhi) {
+ m_rhi = m_window->rhi();
+ if (!m_rhi) {
+ qWarning("No QRhi found for window %p, QQuickRhiItem will not be functional", m_window);
+ return;
+ }
+ }
+
+ m_dpr = m_window->effectiveDevicePixelRatio();
+ const int minTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMin);
+ const int maxTexSize = m_rhi->resourceLimit(QRhi::TextureSizeMax);
+
+ QQuickRhiItemPrivate *itemD = m_item->d_func();
+ QSize newSize = QSize(itemD->fixedTextureWidth, itemD->fixedTextureHeight);
+ if (newSize.isEmpty())
+ newSize = QSize(int(m_item->width()), int(m_item->height())) * m_dpr;
+
+ newSize.setWidth(qMin(maxTexSize, qMax(minTexSize, newSize.width())));
+ newSize.setHeight(qMin(maxTexSize, qMax(minTexSize, newSize.height())));
+
+ if (m_colorTexture) {
+ if (m_colorTexture->format() != itemD->rhiTextureFormat
+ || m_colorTexture->sampleCount() != itemD->samples)
+ {
+ resetColorBufferObjects();
+ resetRenderTargetObjects();
+ }
+ }
+
+ if (m_msaaColorBuffer) {
+ if (m_msaaColorBuffer->backingFormat() != itemD->rhiTextureFormat
+ || m_msaaColorBuffer->sampleCount() != itemD->samples)
+ {
+ resetColorBufferObjects();
+ resetRenderTargetObjects();
+ }
+ }
+
+ if (m_sgTexture && m_sgTexture->hasAlphaChannel() != itemD->blend) {
+ resetColorBufferObjects();
+ resetRenderTargetObjects();
+ }
+
+ if (!m_colorTexture && itemD->samples <= 1) {
+ if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) {
+ qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the "
+ "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat));
+ }
+ m_colorTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, itemD->samples,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!m_colorTexture->create()) {
+ qWarning("Failed to create backing texture for QQuickRhiItem");
+ delete m_colorTexture;
+ m_colorTexture = nullptr;
+ return;
+ }
+ }
+
+ if (itemD->samples > 1) {
+ if (!m_msaaColorBuffer) {
+ if (!m_rhi->isFeatureSupported(QRhi::MultisampleRenderBuffer)) {
+ qWarning("QQuickRhiItem: Multisample renderbuffers are reported as unsupported; "
+ "sample count %d will not work as expected", itemD->samples);
+ }
+ if (!m_rhi->isTextureFormatSupported(itemD->rhiTextureFormat)) {
+ qWarning("QQuickRhiItem: The requested texture format (%d) is not supported by the "
+ "underlying 3D graphics API implementation", int(itemD->rhiTextureFormat));
+ }
+ m_msaaColorBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::Color, newSize, itemD->samples,
+ {}, itemD->rhiTextureFormat));
+ if (!m_msaaColorBuffer->create()) {
+ qWarning("Failed to create multisample color buffer for QQuickRhiItem");
+ m_msaaColorBuffer.reset();
+ return;
+ }
+ }
+ if (!m_resolveTexture) {
+ m_resolveTexture = m_rhi->newTexture(itemD->rhiTextureFormat, newSize, 1,
+ QRhiTexture::RenderTarget | QRhiTexture::UsedAsTransferSource);
+ if (!m_resolveTexture->create()) {
+ qWarning("Failed to create resolve texture for QQuickRhiItem");
+ delete m_resolveTexture;
+ m_resolveTexture = nullptr;
+ return;
+ }
+ }
+ } else if (m_resolveTexture) {
+ m_resolveTexture->deleteLater();
+ m_resolveTexture = nullptr;
+ }
+
+ if (m_colorTexture && m_colorTexture->pixelSize() != newSize) {
+ m_colorTexture->setPixelSize(newSize);
+ if (!m_colorTexture->create())
+ qWarning("Failed to rebuild texture for QQuickRhiItem after resizing");
+ }
+
+ if (m_msaaColorBuffer && m_msaaColorBuffer->pixelSize() != newSize) {
+ m_msaaColorBuffer->setPixelSize(newSize);
+ if (!m_msaaColorBuffer->create())
+ qWarning("Failed to rebuild multisample color buffer for QQuickRhiitem after resizing");
+ }
+
+ if (m_resolveTexture && m_resolveTexture->pixelSize() != newSize) {
+ m_resolveTexture->setPixelSize(newSize);
+ if (!m_resolveTexture->create())
+ qWarning("Failed to rebuild resolve texture for QQuickRhiItem after resizing");
+ }
+
+ if (!m_sgTexture) {
+ QQuickWindow::CreateTextureOptions options;
+ if (itemD->blend)
+ options |= QQuickWindow::TextureHasAlphaChannel;
+ // the QSGTexture takes ownership of the QRhiTexture
+ m_sgTexture.reset(m_window->createTextureFromRhiTexture(m_colorTexture ? m_colorTexture : m_resolveTexture,
+ options));
+ setTexture(m_sgTexture.get());
+ }
+
+ if (itemD->autoRenderTarget) {
+ const QSize pixelSize = m_colorTexture ? m_colorTexture->pixelSize()
+ : m_msaaColorBuffer->pixelSize();
+ if (!m_depthStencilBuffer) {
+ m_depthStencilBuffer.reset(m_rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, itemD->samples));
+ if (!m_depthStencilBuffer->create()) {
+ qWarning("Failed to create depth-stencil buffer for QQuickRhiItem");
+ resetRenderTargetObjects();
+ return;
+ }
+ } else if (m_depthStencilBuffer->pixelSize() != pixelSize) {
+ m_depthStencilBuffer->setPixelSize(pixelSize);
+ if (!m_depthStencilBuffer->create()) {
+ qWarning("Failed to rebuild depth-stencil buffer for QQuickRhiItem with new size");
+ return;
+ }
+ }
+ if (!m_renderTarget) {
+ QRhiColorAttachment color0;
+ if (m_colorTexture)
+ color0.setTexture(m_colorTexture);
+ else
+ color0.setRenderBuffer(m_msaaColorBuffer.get());
+ if (itemD->samples > 1)
+ color0.setResolveTexture(m_resolveTexture);
+ QRhiTextureRenderTargetDescription rtDesc(color0, m_depthStencilBuffer.get());
+ m_renderTarget.reset(m_rhi->newTextureRenderTarget(rtDesc));
+ m_renderPassDescriptor.reset(m_renderTarget->newCompatibleRenderPassDescriptor());
+ m_renderTarget->setRenderPassDescriptor(m_renderPassDescriptor.get());
+ if (!m_renderTarget->create()) {
+ qWarning("Failed to create render target for QQuickRhiitem");
+ resetRenderTargetObjects();
+ return;
+ }
+ }
+ } else {
+ resetRenderTargetObjects();
+ }
+
+ if (newSize != itemD->effectiveTextureSize) {
+ itemD->effectiveTextureSize = newSize;
+ emit m_item->effectiveColorBufferSizeChanged();
+ }
+
+ QRhiCommandBuffer *cb = queryCommandBuffer();
+ if (cb)
+ m_renderer->initialize(cb);
+
+ m_renderer->synchronize(m_item);
+}
+
+QRhiCommandBuffer *QQuickRhiItemNode::queryCommandBuffer()
+{
+ QRhiSwapChain *swapchain = m_window->swapChain();
+ QSGRendererInterface *rif = m_window->rendererInterface();
+
+ // Handle both cases: on-screen QQuickWindow vs. off-screen QQuickWindow
+ // e.g. by using QQuickRenderControl to redirect into a texture.
+ QRhiCommandBuffer *cb = swapchain ? swapchain->currentFrameCommandBuffer()
+ : static_cast<QRhiCommandBuffer *>(
+ rif->getResource(m_window, QSGRendererInterface::RhiRedirectCommandBuffer));
+
+ if (!cb) {
+ qWarning("QQuickRhiItem: Neither swapchain nor redirected command buffer are available.");
+ return nullptr;
+ }
+
+ return cb;
+}
+
+void QQuickRhiItemNode::render()
+{
+ // called before Qt Quick starts recording its main render pass
+
+ if (!isValid() || !m_renderPending)
+ return;
+
+ QRhiCommandBuffer *cb = queryCommandBuffer();
+ if (!cb)
+ return;
+
+ m_renderPending = false;
+ m_renderer->render(cb);
+
+ markDirty(QSGNode::DirtyMaterial);
+ emit textureChanged();
+}
+
+/*!
+ Constructs a new QQuickRhiItem with the given \a parent.
+ */
+QQuickRhiItem::QQuickRhiItem(QQuickItem *parent)
+ : QQuickItem(*new QQuickRhiItemPrivate, parent)
+{
+ setFlag(ItemHasContents);
+}
+
+/*!
+ Destructor.
+*/
+QQuickRhiItem::~QQuickRhiItem()
+{
+}
+
+/*!
+ \internal
+ */
+QSGNode *QQuickRhiItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
+{
+ // Changing to an empty size should not involve destroying and then later
+ // recreating the node, because we do not know how expensive the user's
+ // renderer setup is. Rather, keep the node if it already exist, and clamp
+ // all accesses to width and height. Hence the unusual !oldNode condition here.
+ if (!oldNode && (width() <= 0 || height() <= 0))
+ return nullptr;
+
+ Q_D(QQuickRhiItem);
+ QQuickRhiItemNode *n = static_cast<QQuickRhiItemNode *>(oldNode);
+ if (!n) {
+ if (!d->node)
+ d->node = new QQuickRhiItemNode(this);
+ if (!d->node->hasRenderer()) {
+ QQuickRhiItemRenderer *r = createRenderer();
+ if (r) {
+ r->node = d->node;
+ d->node->setRenderer(r);
+ } else {
+ qWarning("No QQuickRhiItemRenderer was created; the item will not render");
+ delete d->node;
+ d->node = nullptr;
+ return nullptr;
+ }
+ }
+ n = d->node;
+ }
+
+ n->sync();
+
+ if (!n->isValid()) {
+ delete n;
+ d->node = nullptr;
+ return nullptr;
+ }
+
+ if (window()->rhi()->isYUpInFramebuffer()) {
+ n->setTextureCoordinatesTransform(d->mirrorVertically
+ ? QSGSimpleTextureNode::NoTransform
+ : QSGSimpleTextureNode::MirrorVertically);
+ } else {
+ n->setTextureCoordinatesTransform(d->mirrorVertically
+ ? QSGSimpleTextureNode::MirrorVertically
+ : QSGSimpleTextureNode::NoTransform);
+ }
+ n->setFiltering(d->smooth ? QSGTexture::Linear : QSGTexture::Nearest);
+ n->setRect(0, 0, qMax<int>(0, width()), qMax<int>(0, height()));
+
+ n->scheduleUpdate();
+
+ return n;
+}
+
+/*!
+ \reimp
+ */
+bool QQuickRhiItem::event(QEvent *e)
+{
+ return QQuickItem::event(e);
+}
+
+/*!
+ \reimp
+ */
+void QQuickRhiItem::geometryChange(const QRectF &newGeometry, const QRectF &oldGeometry)
+{
+ QQuickItem::geometryChange(newGeometry, oldGeometry);
+ if (newGeometry.size() != oldGeometry.size())
+ update();
+}
+
+/*!
+ \reimp
+ */
+void QQuickRhiItem::releaseResources()
+{
+ // called on the gui thread if the item is removed from scene
+
+ Q_D(QQuickRhiItem);
+ d->node = nullptr;
+}
+
+void QQuickRhiItem::invalidateSceneGraph()
+{
+ // called on the render thread when the scenegraph is invalidated
+
+ Q_D(QQuickRhiItem);
+ d->node = nullptr;
+}
+
+/*!
+ \reimp
+ */
+bool QQuickRhiItem::isTextureProvider() const
+{
+ return true;
+}
+
+/*!
+ \reimp
+ */
+QSGTextureProvider *QQuickRhiItem::textureProvider() const
+{
+ if (QQuickItem::isTextureProvider()) // e.g. if Item::layer::enabled == true
+ return QQuickItem::textureProvider();
+
+ Q_D(const QQuickRhiItem);
+ if (!d->node) // create a node to have a provider, the texture will be null but that's ok
+ d->node = new QQuickRhiItemNode(const_cast<QQuickRhiItem *>(this));
+
+ return d->node;
+}
+
+/*!
+ \property QQuickRhiItem::sampleCount
+
+ This property controls for sample count for multisample antialiasing.
+ By default the value is \c 1 which means MSAA is disabled.
+
+ Valid values are 1, 4, 8, and sometimes 16 and 32.
+ \l QRhi::supportedSampleCounts() can be used to query the supported sample
+ counts at run time, but typically applications should request 1 (no MSAA),
+ 4x (normal MSAA) or 8x (high MSAA).
+
+ \note Setting a new value implies that all QRhiGraphicsPipeline objects
+ created by the renderer must use the same sample count from then on.
+ Existing QRhiGraphicsPipeline objects created with a different sample count
+ must not be used anymore. When the value changes, all color and
+ depth-stencil buffers are destroyed and recreated automatically, and
+ \l {QQuickRhiItemRenderer::}{initialize()} is invoked again. However, when
+ isAutoRenderTargetEnabled() is \c false, it will be up to the application to
+ manage this with regards to the depth-stencil buffer or additional color
+ buffers.
+
+ Changing the sample count from the default 1 to a higher value implies that
+ \l {QQuickRhiItemRenderer::}{colorTexture()} becomes \nullptr and
+ \l {QQuickRhiItemRenderer::}{msaaColorBuffer()} starts returning a
+ valid object. Switching back to 1 (or 0), implies the opposite: in the next
+ call to initialize() msaaColorBuffer() is going to return \nullptr, whereas
+ colorTexture() becomes once again valid. In addition,
+ \l {QQuickRhiItemRenderer::}{resolveTexture()}
+ returns a valid (non-multisample) QRhiTexture whenever the sample count is
+ greater than 1 (i.e., MSAA is in use).
+
+ \sa QQuickRhiItemRenderer::msaaColorBuffer(),
+ QQuickRhiItemRenderer::resolveTexture()
+ */
+
+int QQuickRhiItem::sampleCount() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->samples;
+}
+
+void QQuickRhiItem::setSampleCount(int samples)
+{
+ Q_D(QQuickRhiItem);
+ if (d->samples == samples)
+ return;
+
+ d->samples = samples;
+ emit sampleCountChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::colorBufferFormat
+
+ This property controls the texture format for the texture used as the color
+ buffer. The default value is TextureFormat::RGBA8. QQuickRhiItem supports
+ rendering to a subset of the formats supported by \l QRhiTexture. Only
+ formats that are reported as supported from
+ \l QRhi::isTextureFormatSupported() should be specified, rendering will not be
+ functional otherwise.
+
+ \note Setting a new format when the item and its renderer are already
+ initialized and have rendered implies that all QRhiGraphicsPipeline objects
+ created by the renderer may become unusable, if the associated
+ QRhiRenderPassDescriptor is now incompatible due to the different texture
+ format. Similarly to changing
+ \l sampleCount dynamically, this means that initialize() or render()
+ implementations must then take care of releasing the existing pipelines and
+ creating new ones.
+ */
+
+QQuickRhiItem::TextureFormat QQuickRhiItem::colorBufferFormat() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->itemTextureFormat;
+}
+
+void QQuickRhiItem::setColorBufferFormat(TextureFormat format)
+{
+ Q_D(QQuickRhiItem);
+ if (d->itemTextureFormat == format)
+ return;
+
+ d->itemTextureFormat = format;
+ switch (format) {
+ case TextureFormat::RGBA8:
+ d->rhiTextureFormat = QRhiTexture::RGBA8;
+ break;
+ case TextureFormat::RGBA16F:
+ d->rhiTextureFormat = QRhiTexture::RGBA16F;
+ break;
+ case TextureFormat::RGBA32F:
+ d->rhiTextureFormat = QRhiTexture::RGBA32F;
+ break;
+ case TextureFormat::RGB10A2:
+ d->rhiTextureFormat = QRhiTexture::RGB10A2;
+ break;
+ }
+ emit colorBufferFormatChanged();
+ update();
+}
+
+/*!
+ \return the current automatic depth-stencil buffer and render target management setting.
+
+ By default this value is \c true.
+
+ \sa setAutoRenderTarget()
+ */
+bool QQuickRhiItem::isAutoRenderTargetEnabled() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->autoRenderTarget;
+}
+
+/*!
+ Controls if a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
+ is created and maintained automatically by the item. The default value is
+ \c true. Call this function early on, for example from the derived class'
+ constructor, with \a enabled set to \c false to disable this.
+
+ In automatic mode, the size and sample count of the depth-stencil buffer
+ follows the color buffer texture's settings. In non-automatic mode,
+ renderTarget() and depthStencilBuffer() always return \nullptr and it is
+ then up to the application's implementation of initialize() to take care of
+ setting up and managing these objects.
+ */
+void QQuickRhiItem::setAutoRenderTarget(bool enabled)
+{
+ Q_D(QQuickRhiItem);
+ if (d->autoRenderTarget == enabled)
+ return;
+
+ d->autoRenderTarget = enabled;
+ emit autoRenderTargetChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::mirrorVertically
+
+ This property controls if texture UVs are flipped when drawing the textured
+ quad. It has no effect on the contents of the offscreen color buffer and
+ the rendering implemented by the QQuickRhiItemRenderer.
+
+ The default value is \c false.
+ */
+
+bool QQuickRhiItem::isMirrorVerticallyEnabled() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->mirrorVertically;
+}
+
+void QQuickRhiItem::setMirrorVertically(bool enable)
+{
+ Q_D(QQuickRhiItem);
+ if (d->mirrorVertically == enable)
+ return;
+
+ d->mirrorVertically = enable;
+ emit mirrorVerticallyChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::fixedColorBufferWidth
+
+ The fixed width, in pixels, of the item's associated texture or
+ renderbuffer. Relevant when a fixed color buffer size is desired that does
+ not depend on the item's size. This size has no effect on the geometry of
+ the item (its size and placement within the scene), which means the
+ texture's content will appear stretched (scaled up) or scaled down onto the
+ item's area.
+
+ For example, setting a size that is exactly twice the item's (pixel) size
+ effectively performs 2x supersampling (rendering at twice the resolution
+ and then implicitly scaling down when texturing the quad corresponding to
+ the item in the scene).
+
+ By default the value is \c 0. A value of 0 means that texture's size
+ follows the item's size. (\c{texture size} = \c{item size} * \c{device
+ pixel ratio}).
+ */
+int QQuickRhiItem::fixedColorBufferWidth() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->fixedTextureWidth;
+}
+
+void QQuickRhiItem::setFixedColorBufferWidth(int width)
+{
+ Q_D(QQuickRhiItem);
+ if (d->fixedTextureWidth == width)
+ return;
+
+ d->fixedTextureWidth = width;
+ emit fixedColorBufferWidthChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::fixedColorBufferHeight
+
+ The fixed height, in pixels, of the item's associated texture. Relevant when
+ a fixed texture size is desired that does not depend on the item's size.
+ This size has no effect on the geometry of the item (its size and placement
+ within the scene), which means the texture's content will appear stretched
+ (scaled up) or scaled down onto the item's area.
+
+ For example, setting a size that is exactly twice the item's (pixel) size
+ effectively performs 2x supersampling (rendering at twice the resolution
+ and then implicitly scaling down when texturing the quad corresponding to
+ the item in the scene).
+
+ By default the value is \c 0. A value of 0 means that texture's size
+ follows the item's size. (\c{texture size} = \c{item size} * \c{device
+ pixel ratio}).
+ */
+
+int QQuickRhiItem::fixedColorBufferHeight() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->fixedTextureHeight;
+}
+
+void QQuickRhiItem::setFixedColorBufferHeight(int height)
+{
+ Q_D(QQuickRhiItem);
+ if (d->fixedTextureHeight == height)
+ return;
+
+ d->fixedTextureHeight = height;
+ emit fixedColorBufferHeightChanged();
+ update();
+}
+
+/*!
+ \property QQuickRhiItem::effectiveColorBufferSize
+
+ This property exposes the size, in pixels, of the underlying color buffer
+ (the QRhiTexture or QRhiRenderBuffer). It is provided for use on the GUI
+ (main) thread, in QML bindings or JavaScript.
+
+ \note QQuickRhiItemRenderer implementations, operating on the scene graph
+ render thread, should not use this property. Those should rather query the
+ size from the
+ \l{QQuickRhiItemRenderer::renderTarget()}{render target}.
+
+ \note The value becomes available asynchronously from the main thread's
+ perspective in the sense that the value changes when rendering happens on
+ the render thread. This means that this property is useful mainly in QML
+ bindings. Application code must not assume that the value is up to date
+ already when the QQuickRhiItem object is constructed.
+
+ This is a read-only property.
+ */
+
+QSize QQuickRhiItem::effectiveColorBufferSize() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->effectiveTextureSize;
+}
+
+/*!
+ \property QQuickRhiItem::alphaBlending
+
+ Controls if blending is always enabled when drawing the quad textured with
+ the content generated by the QQuickRhiItem and its renderer.
+
+ The default value is \c false. This is for performance reasons: if
+ semi-transparency is not involved, because the QQuickRhiItemRenderer clears
+ to an opaque color and never renders fragments with alpha smaller than 1,
+ then there is no point in enabling blending.
+
+ If the QQuickRhiItemRenderer subclass renders with semi-transparency involved,
+ set this property to true.
+
+ \note Under certain conditions blending is still going to happen regardless
+ of the value of this property. For example, if the item's
+ \l{QQuickItem::opacity}{opacity} (more precisely, the combined opacity
+ inherited from the parent chain) is smaller than 1, blending will be
+ automatically enabled even when this property is set to false.
+
+ \note The Qt Quick scene graph relies on and expect pre-multiplied alpha.
+ For example, if the intention is to clear the background in the renderer to
+ an alpha value of 0.5, then make sure to multiply the red, green, and blue
+ clear color values with 0.5 as well. Otherwise the blending results will be
+ incorrect.
+ */
+
+bool QQuickRhiItem::alphaBlending() const
+{
+ Q_D(const QQuickRhiItem);
+ return d->blend;
+}
+
+void QQuickRhiItem::setAlphaBlending(bool enable)
+{
+ Q_D(QQuickRhiItem);
+ if (d->blend == enable)
+ return;
+
+ d->blend = enable;
+ emit alphaBlendingChanged();
+ update();
+}
+
+/*!
+ Constructs a new renderer.
+
+ This function is called on the rendering thread during the scene graph sync
+ phase when the GUI thread is blocked.
+
+ \sa QQuickRhiItem::createRenderer()
+ */
+QQuickRhiItemRenderer::QQuickRhiItemRenderer()
+{
+}
+
+/*!
+ The Renderer is automatically deleted when the scene graph resources for
+ the QQuickRhiItem item are cleaned up.
+
+ This function is called on the rendering thread.
+
+ Under certain conditions it is normal and expected that the renderer object
+ is destroyed and then recreated. This is because the renderer's lifetime
+ effectively follows the underlying scene graph node. For example, when
+ changing the parent of a QQuickRhiItem object so that it then belongs to a
+ different \l QQuickWindow, the scene graph nodes are all dropped and
+ recreated due to the window change. This will also involve dropping and
+ creating a new QQuickRhiItemRenderer.
+
+ Unlike \l QRhiWidget, QQuickRhiItemRenderer has no need to implement
+ additional code paths for releasing (or early-relasing) graphics resources
+ created via QRhi. It is sufficient to release everything in the destructor,
+ or rely on smart pointers.
+ */
+QQuickRhiItemRenderer::~QQuickRhiItemRenderer()
+{
+}
+
+/*!
+ Call this function when the content of the offscreen color buffer should be
+ updated. (i.e. to request that render() is called again; the call will
+ happen at a later point, and note that updates are typically throttled to
+ the presentation rate)
+
+ This function can be called from render() to schedule an update.
+
+ \note This function should be used from inside the renderer. To update
+ the item on the GUI thread, use QQuickRhiItem::update().
+ */
+void QQuickRhiItemRenderer::update()
+{
+ if (node)
+ node->scheduleUpdate();
+}
+
+/*!
+ \return the current QRhi object.
+
+ Must only be called from initialize() and render().
+ */
+QRhi *QQuickRhiItemRenderer::rhi() const
+{
+ return node ? node->m_rhi : nullptr;
+}
+
+/*!
+ \return the texture serving as the color buffer for the item.
+
+ Must only be called from initialize() and render().
+
+ Unlike the depth-stencil buffer and the QRhiRenderTarget, this texture is
+ always available and is managed by the QQuickRhiItem, independent of the
+ value of \l {QQuickRhiItem::}{autoRenderTarget}.
+
+ \note When \l {QQuickRhiItem::}{sampleCount} is larger than 1, and so
+ multisample antialiasing is enabled, the return value is \nullptr. Instead,
+ query the \l QRhiRenderBuffer by calling msaaColorBuffer().
+
+ \note The backing texture size and sample count can also be queried via the
+ QRhiRenderTarget returned from renderTarget(). This can be more convenient
+ and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
+ it works regardless of multisampling is in use or not.
+
+ \sa msaaColorBuffer(), depthStencilBuffer(), renderTarget(), resolveTexture()
+ */
+QRhiTexture *QQuickRhiItemRenderer::colorTexture() const
+{
+ return node ? node->m_colorTexture : nullptr;
+}
+
+/*!
+ \return the renderbuffer serving as the multisample color buffer for the item.
+
+ Must only be called from initialize() and render().
+
+ When \l {QQuickRhiItem::}{sampleCount} is larger than 1, and so multisample
+ antialising is enabled, the returned QRhiRenderBuffer has a matching sample
+ count and serves as the color buffer. Graphics pipelines used to render into
+ this buffer must be created with the same sample count, and the
+ depth-stencil buffer's sample count must match as well. The multisample
+ content is expected to be resolved into the texture returned from
+ resolveTexture(). When \l {QQuickRhiItem::}{autoRenderTarget} is \c true,
+ renderTarget() is set up automatically to do this, by setting up
+ msaaColorBuffer() as the
+ \l{QRhiColorAttachment::renderBuffer()}{renderbuffer} of color attachment 0
+ and resolveTexture() as its
+ \l{QRhiColorAttachment::resolveTexture()}{resolveTexture}.
+
+ When MSAA is not in use, the return value is \nullptr. Use colorTexture()
+ instead then.
+
+ Depending on the underlying 3D graphics API, there may be no practical
+ difference between multisample textures and color renderbuffers with a
+ sample count larger than 1 (QRhi may just map both to the same native
+ resource type). Some older APIs however may differentiate between textures
+ and renderbuffers. In order to support OpenGL ES 3.0, where multisample
+ renderbuffers are available, but multisample textures are not, QQuickRhiItem
+ always performs MSAA by using a multisample QRhiRenderBuffer as the color
+ attachment (and never a multisample QRhiTexture).
+
+ \note The backing texture size and sample count can also be queried via the
+ QRhiRenderTarget returned from renderTarget(). This can be more convenient
+ and compact than querying from the QRhiTexture or QRhiRenderBuffer, because
+ it works regardless of multisampling is in use or not.
+
+ \sa colorTexture(), depthStencilBuffer(), renderTarget(), resolveTexture()
+ */
+QRhiRenderBuffer *QQuickRhiItemRenderer::msaaColorBuffer() const
+{
+ return node ? node->m_msaaColorBuffer.get() : nullptr;
+}
+
+/*!
+ \return the non-multisample texture to which the multisample content is resolved.
+
+ The result is \nullptr when multisample antialiasing is not enabled.
+
+ Must only be called from initialize() and render().
+
+ With MSAA enabled, this is the texture that gets used by the item's
+ underlying scene graph node when texturing a quad in the main render pass
+ of Qt Quick. However, the QQuickRhiItemRenderer's rendering must target the
+ (multisample) QRhiRenderBuffer returned from msaaColorBuffer(). When
+ \l {QQuickRhiItem::}{autoRenderTarget} is \c true, this is taken care of by
+ the QRhiRenderTarget returned from renderTarget(). Otherwise, it is up to
+ the subclass code to correctly configure a render target object with both
+ the color buffer and resolve textures.
+
+ \sa colorTexture()
+ */
+QRhiTexture *QQuickRhiItemRenderer::resolveTexture() const
+{
+ return node ? node->m_resolveTexture : nullptr;
+}
+
+/*!
+ \return the depth-stencil buffer used by the item's rendering.
+
+ Must only be called from initialize() and render().
+
+ Available only when \l {QQuickRhiItem::}{autoRenderTarget} is \c true.
+ Otherwise the returned value is \nullptr and it is up the reimplementation
+ of initialize() to create and manage a depth-stencil buffer and a
+ QRhiTextureRenderTarget.
+
+ \sa colorTexture(), renderTarget()
+ */
+QRhiRenderBuffer *QQuickRhiItemRenderer::depthStencilBuffer() const
+{
+ return node ? node->m_depthStencilBuffer.get() : nullptr;
+}
+
+/*!
+ \return the render target object that must be used with
+ \l QRhiCommandBuffer::beginPass() in reimplementations of render().
+
+ Must only be called from initialize() and render().
+
+ Available only when \l {QQuickRhiItem::}{autoRenderTarget} is \c true.
+ Otherwise the returned value is \nullptr and it is up the reimplementation
+ of initialize() to create and manage a depth-stencil buffer and a
+ QRhiTextureRenderTarget.
+
+ When creating \l{QRhiGraphicsPipeline}{graphics pipelines}, a
+ QRhiRenderPassDescriptor is needed. This can be queried from the returned
+ QRhiTextureRenderTarget by calling
+ \l{QRhiTextureRenderTarget::renderPassDescriptor()}{renderPassDescriptor()}.
+
+ \note The returned QRhiTextureRenderTarget always reports a
+ \l{QRhiTextureRenderTarget::}{devicePixelRatio()} of \c 1.
+ This is because only swapchains and the associated window have a concept of
+ device pixel ratio, not textures, and the render target here always refers
+ to a texture. If the on-screen scale factor is relevant for rendering,
+ query and store it via the item's
+ \c{window()->effectiveDevicePixelRatio()} in \l synchronize().
+ When doing so, always prefer using \l{QQuickWindow::}{effectiveDevicePixelRatio()}
+ over the base class' \l{QWindow::}{devicePixelRatio()}.
+
+ \sa colorTexture(), depthStencilBuffer(), QQuickWindow::effectiveDevicePixelRatio()
+ */
+QRhiRenderTarget *QQuickRhiItemRenderer::renderTarget() const
+{
+ return node ? node->m_renderTarget.get() : nullptr;
+}
+
+/*!
+ \fn QQuickRhiItemRenderer *QQuickRhiItem::createRenderer()
+
+ Reimplement this function to create and return a new instance of a
+ QQuickRhiItemRenderer subclass.
+
+ This function will be called on the rendering thread while the GUI thread
+ is blocked.
+ */
+
+/*!
+ \fn void QQuickRhiItemRenderer::initialize(QRhiCommandBuffer *cb)
+
+ Called when the item is initialized for the first time, when the
+ associated texture's size, format, or sample count changes, or when the
+ QRhi or texture change for any reason. The function is expected to
+ maintain (create if not yet created, adjust and rebuild if the size has
+ changed) the graphics resources used by the rendering code in render().
+
+ To query the QRhi, QRhiTexture, and other related objects, call rhi(),
+ colorTexture(), depthStencilBuffer(), and renderTarget().
+
+ When the item size changes, the QRhi object, the color buffer texture,
+ and the depth stencil buffer objects are all the same instances (so the
+ getters return the same pointers) as before, but the color and
+ depth/stencil buffers will likely have been rebuilt, meaning the
+ \l{QRhiTexture::pixelSize()}{size} and the underlying native texture
+ resource may be different than in the last invocation.
+
+ Reimplementations should also be prepared that the QRhi object and the
+ color buffer texture may change between invocations of this function. For
+ example, when the item is reparented so that it belongs to a new
+ QQuickWindow, the the QRhi and all related resources managed by the
+ QQuickRhiItem will be different instances than before in the subsequent
+ call to this function. Is is then important that all existing QRhi
+ resources previously created by the subclass are destroyed because they
+ belong to the previous QRhi that should not be used anymore.
+
+ When \l {QQuickRhiItem::}{autoRenderTarget} is \c true, which is the
+ default, a depth-stencil QRhiRenderBuffer and a QRhiTextureRenderTarget
+ associated with the colorTexture() (or msaaColorBuffer()) and the
+ depth-stencil buffer are created and managed automatically.
+ Reimplementations of initialize() and render() can query those objects via
+ depthStencilBuffer() and renderTarget(). When
+ \l {QQuickRhiItem::}{autoRenderTarget} is set to \c false, these objects are
+ no longer created and managed automatically. Rather, it will be
+ up the the initialize() implementation to create buffers and set up the
+ render target as it sees fit. When manually managing additional color or
+ depth-stencil attachments for the render target, their size and sample
+ count must always follow the size and sample count of colorTexture() (or
+ msaaColorBuffer()), otherwise rendering or 3D API validation errors may
+ occur.
+
+ The subclass-created graphics resources are expected to be released in the
+ destructor implementation of the subclass.
+
+ \a cb is the QRhiCommandBuffer for the current frame. The function is
+ called with a frame being recorded, but without an active render pass. The
+ command buffer is provided primarily to allow enqueuing
+ \l{QRhiCommandBuffer::resourceUpdate()}{resource updates} without deferring
+ to render().
+
+ This function is called on the render thread, if there is one.
+
+ \sa render()
+ */
+
+/*!
+ \fn void QQuickRhiItemRenderer::synchronize(QQuickRhiItem *item)
+
+ This function is called on the render thread, if there is one, while the
+ main/GUI thread is blocked. It is called from
+ \l{QQuickItem::updatePaintNode()}{the \a {item}'s synchronize step},
+ and allows reading and writing data belonging to the main and render
+ threads. Typically property values stored in the QQuickRhiItem are copied
+ into the QQuickRhiItemRenderer, so that they can be safely read afterwards
+ in render() when the render and main threads continue to work in parallel.
+
+ \sa initialize(), render()
+ */
+
+/*!
+ \fn void QQuickRhiItemRenderer::render(QRhiCommandBuffer *cb)
+
+ Called when the backing color buffer's contents needs updating.
+
+ There is always at least one call to initialize() before this function is
+ called.
+
+ To request updates, call \l QQuickItem::update() when calling from QML or
+ from C++ code on the main/GUI thread (e.g. when in a property setter), or
+ \l update() when calling from within a QQuickRhiItemRenderer callback.
+ Calling QQuickRhiItemRenderer's update() from within
+ render() will lead to triggering updates continuously.
+
+ \a cb is the QRhiCommandBuffer for the current frame. The function is
+ called with a frame being recorded, but without an active render pass.
+
+ This function is called on the render thread, if there is one.
+
+ \sa initialize(), synchronize()
+ */
+
+QT_END_NAMESPACE