Source code

Revision control

Copy as Markdown

// 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
// Qt-Security score:significant reason:default
#include "qquickrhiitem_p.h"
#include <QtQuick/private/qsgrhisupport_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.
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.
\section1 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 well. These are provided as Vulkan-style GLSL source code and
must be processed first by the Qt shader infrastructure first. This is
achieved either by running the \c qsb command-line tool manually, or by
using the \l qt_add_shaders() function in CMake.
The QQuickRhiItem loads these pre-processed \c{.qsb}
files that are shipped with the application. See \l{Qt Shader Tools} for
more information about Qt's shader translation infrastructure.
\c{color.vert}
\snippet qquickrhiitem/qquickrhiitem_intro.vert 0
\c{color.frag}
\snippet qquickrhiitem/qquickrhiitem_intro.frag 0
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 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 = (QSizeF(m_item->width(), m_item->height()) * m_dpr).toSize();
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,
QSGRhiSupport::depthStencilBufferFlags()));
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);
}
/*!
* \internal
*/
QQuickRhiItem::QQuickRhiItem(QQuickRhiItemPrivate &dd, QQuickItem *parent)
: QQuickItem(dd, 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(qreal(0), width()), qMax(qreal(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();
}
/*!
\enum QQuickRhiItem::TextureFormat
Specifies the format of the backing texture to which the QQuickRhiItem renders.
\value RGBA8 See QRhiTexture::RGBA8. This is the default.
\value RGBA16F See QRhiTexture::RGBA16F.
\value RGBA32F See QRhiTexture::RGBA32F.
\value RGB10A2 See QRhiTexture::RGB10A2.
\sa QRhiTexture
*/
/*!
\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). On the other hand, setting a size that is half of the
item's pixel size effectively achieves rendering at half resolution and then
upscaling the results.
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}).
\note The device pixel ratio (the system compositor's scale factor) can have
a big impact on performance, since a scale factor of 2 (200%) means
rendering at twice the resolution, so twice of what the developer and UI
designer perceives as the item's size, and then effectively downscaling the
content, similarly to what happens when setting this property to twice the
item's pixel size on a system where the device pixel ratio is 1. Therefore,
this property is expected to be rarely used with sizes bigger than the
item's pixel size, since many modern desktop systems have neither the need
nor have the performance budget for it, when a larger than 1 device pixel
ratio is used anyway by the system. Instead, the main use case for this
property is to set a smaller size, in order to render at a reasonable
smaller resolution instead of blindly following the item (and perhaps
window) geometry, however big that may be.
*/
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.
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}).
See \l fixedColorBufferWidth for more information on the use cases for
setting a fixed width and height.
*/
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::}{isAutoRenderTargetEnabled}.
\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::}{isAutoRenderTargetEnabled} 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::}{isAutoRenderTargetEnabled} 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::}{isAutoRenderTargetEnabled} 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::}{isAutoRenderTargetEnabled} 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::}{isAutoRenderTargetEnabled} 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::}{isAutoRenderTargetEnabled} 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
#include "moc_qquickrhiitem.cpp"
#include "moc_qquickrhiitem_p.cpp"