diff options
Diffstat (limited to 'src/quick/items/qquickrendertarget.cpp')
-rw-r--r-- | src/quick/items/qquickrendertarget.cpp | 1777 |
1 files changed, 1777 insertions, 0 deletions
diff --git a/src/quick/items/qquickrendertarget.cpp b/src/quick/items/qquickrendertarget.cpp new file mode 100644 index 0000000000..55bbf22eee --- /dev/null +++ b/src/quick/items/qquickrendertarget.cpp @@ -0,0 +1,1777 @@ +// Copyright (C) 2020 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 "qquickrendertarget_p.h" +#include <rhi/qrhi.h> +#include <QtQuick/private/qquickitem_p.h> +#include <QtQuick/private/qquickwindow_p.h> +#include <QtQuick/private/qsgrhisupport_p.h> + +QT_BEGIN_NAMESPACE + +/*! + \class QQuickRenderTarget + \since 6.0 + \inmodule QtQuick + + \brief The QQuickRenderTarget class provides an opaque container for native + graphics resources specifying a render target, and associated metadata. + + \sa QQuickWindow::setRenderTarget(), QQuickGraphicsDevice +*/ + +QQuickRenderTargetPrivate::QQuickRenderTargetPrivate() + : ref(1) +{ +} + +QQuickRenderTargetPrivate::QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate &other) + : ref(1), + type(other.type), + pixelSize(other.pixelSize), + devicePixelRatio(other.devicePixelRatio), + sampleCount(other.sampleCount), + u(other.u), + customDepthTexture(other.customDepthTexture), + mirrorVertically(other.mirrorVertically), + multisampleResolve(other.multisampleResolve) +{ +} + +/*! + Constructs a default QQuickRenderTarget that does not reference any native + objects. + */ +QQuickRenderTarget::QQuickRenderTarget() + : d(new QQuickRenderTargetPrivate) +{ +} + +/*! + \internal + */ +void QQuickRenderTarget::detach() +{ + qAtomicDetach(d); +} + +/*! + \internal + */ +QQuickRenderTarget::QQuickRenderTarget(const QQuickRenderTarget &other) + : d(other.d) +{ + d->ref.ref(); +} + +/*! + \internal + */ +QQuickRenderTarget &QQuickRenderTarget::operator=(const QQuickRenderTarget &other) +{ + qAtomicAssign(d, other.d); + return *this; +} + +/*! + Destructor. + */ +QQuickRenderTarget::~QQuickRenderTarget() +{ + if (!d->ref.deref()) + delete d; +} + +/*! + \return true if this QQuickRenderTarget is default constructed, referencing + no native objects. + */ +bool QQuickRenderTarget::isNull() const +{ + return d->type == QQuickRenderTargetPrivate::Type::Null; +} + +/*! + \return the device pixel ratio for the render target. This is the ratio + between \e{device pixels} and \e{device independent pixels}. + + The default device pixel ratio is 1.0. + + \since 6.3 + + \sa setDevicePixelRatio() +*/ +qreal QQuickRenderTarget::devicePixelRatio() const +{ + return d->devicePixelRatio; +} + +/*! + Sets the device pixel ratio for this render target to \a ratio. This is + the ratio between \e{device pixels} and \e{device independent pixels}. + + Note that the specified device pixel ratio value will be ignored if + QQuickRenderControl::renderWindow() is re-implemented to return a valid + QWindow. + + \since 6.3 + + \sa devicePixelRatio() +*/ +void QQuickRenderTarget::setDevicePixelRatio(qreal ratio) +{ + if (d->devicePixelRatio == ratio) + return; + + detach(); + d->devicePixelRatio = ratio; +} + +/*! + \return Returns whether the render target is mirrored vertically. + + The default value is \c {false}. + + \since 6.4 + + \sa setMirrorVertically() +*/ +bool QQuickRenderTarget::mirrorVertically() const +{ + return d->mirrorVertically; +} + + +/*! + Sets the size of the render target contents should be mirrored vertically to + \a enable when drawing. This allows easy integration of third-party rendering + code that does not follow the standard expectations. + + \note This function should not be used when using the \c software backend. + + \since 6.4 + + \sa mirrorVertically() + */ +void QQuickRenderTarget::setMirrorVertically(bool enable) +{ + if (d->mirrorVertically == enable) + return; + + detach(); + d->mirrorVertically = enable; +} + +/*! + \return the currently set depth texture or, in most cases, \nullptr. + + The value is only non-null when setDepthTexture() was called. + + \since 6.8 + */ +QRhiTexture *QQuickRenderTarget::depthTexture() const +{ + return d->customDepthTexture; +} + +/*! + Requests using the given \a texture as the depth or depth-stencil buffer. + Ownership of \a texture is not taken. + + The request is only taken into account when relevant. For example, calling + this function has no effect with fromRhiRenderTarget(), fromPaintDevice(), + or fromOpenGLRenderBuffer(). + + Normally a depth-stencil buffer is created automatically, transparently to + the user of QQuickRenderTarget. Therefore, there is no need to call this + function in most cases when working with QQuickRenderTarget. In special + circumstances, it can however become essential to be able to provide a + texture to render depth (or depth and stencil) data into, instead of letting + Qt Quick create its own intermediate textures or buffers. An example of this + is \l{https://www.khronos.org/openxr/}{OpenXR} and its extensions such as + \l{https://registry.khronos.org/OpenXR/specs/1.0/html/xrspec.html#XR_KHR_composition_layer_depth}{XR_KHR_composition_layer_depth}. + In order to "submit the depth buffer" to the XR compositor, one has to, in + practice, retrieve an already created depth (depth-stencil) texture from + OpenXR (from the XrSwapchain) and use that texture as the render target for + depth data. That would not be possible without this function. + + \note The \a texture is always expected to be a non-multisample 2D texture + or texture array (for multiview). If MSAA is involved, the samples are + resolved into \a texture at the end of the render pass, regardless of having + the MultisampleResolve flag set or not. MSAA is only supported for depth + (depth-stencil) textures when the underlying 3D API supports this, and this + support is not universally available. See \l{QRhi::ResolveDepthStencil}{the + relevant QRhi feature flag} for details. When this is not supported and + multisampling is requested in combination with a custom depth texture, \a + texture is not going to be touched during rendering and a warning is + printed. + + \since 6.8 + + \note When it comes to OpenGL and OpenGL ES, using depth textures is not + functional on OpenGL ES 2.0 and requires at least OpenGL ES 3.0. Multisample + (MSAA) support is not available without at least OpenGL ES 3.1, or OpenGL + 3.0 on desktop. + */ +void QQuickRenderTarget::setDepthTexture(QRhiTexture *texture) +{ + if (d->customDepthTexture == texture) + return; + + detach(); + d->customDepthTexture = texture; +} + +/*! + \enum QQuickRenderTarget::Flag + Flags for the static QQuickRenderTarget constructor functions. + + \value MultisampleResolve Indicates that the \c sampleCount argument is not + the number of samples for the provided texture (and that the texture is + still a non-multisample texture), but rather the desired samples for + multisample antialiasing. Triggers automatically creating and managing an + intermediate multisample texture (or texture array) as the color buffer, + transparently to the application. The samples are resolved into the provided + texture at the end of the render pass automatically. When this flag is not + set, and the \c sampleCount argument is greater than 1, it implies the + provided texture is multisample. The flag has no effect is the + \c sampleCount is 1 (indicating that multisampling is not involved). + + \since 6.8 +*/ + +/*! + \return a new QQuickRenderTarget referencing an OpenGL texture object + specified by \a textureId. + + \a format specifies the native internal format of the + texture. Only texture formats that are supported by Qt's rendering + infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + The OpenGL object name \a textureId must be a valid name in the rendering + context used by the Qt Quick scenegraph. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.4 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +#if QT_CONFIG(opengl) || defined(Q_QDOC) +QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint format, + const QSize &pixelSize, int sampleCount) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!textureId) { + qWarning("QQuickRenderTarget: textureId is invalid"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromGL(format, &formatFlags); + d->u.nativeTexture = { textureId, 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + + return rt; +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing an OpenGL texture + object specified by \a textureId. The texture is assumed to have a + format of GL_RGBA (GL_RGBA8). + + \a pixelSize specifies the size of the image, in pixels. Currently + only 2D textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native + object is a multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + The OpenGL object name \a textureId must be a valid name in the rendering + context used by the Qt Quick scenegraph. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl +*/ +QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, const QSize &pixelSize, int sampleCount) +{ + return fromOpenGLTexture(textureId, 0, pixelSize, sampleCount); +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing an OpenGL 2D texture or texture + array object specified by \a textureId. + + \a format specifies the native internal format of the texture. Only texture + formats that are supported by Qt's rendering infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures and 2D texture arrays are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture, except when \a flags contains \l MultisampleResolve. In + that case, \a textureId is assumed to be a non-multisample 2D texture or 2D + texture array, and \a sampleCount defines the number of samples desired. The + resulting QQuickRenderTarget will use an intermediate, automatically created + multisample texture (or texture array) as its color attachment, and will + resolve the samples into \a textureId. This is the recommended approach to + perform MSAA when the native OpenGL texture is not already multisample. + + When \a arraySize is greater than 1, it implies multiview rendering + (\l{https://registry.khronos.org/OpenGL/extensions/OVR/OVR_multiview.txt}{GL_OVR_multiview}, + \l QRhiColorAttachment::setMultiViewCount()), which can be relevant with + VR/AR especially. In this case \a arraySize is the number of views, + typically \c 2. See \l QSGMaterial::viewCount() for details on enabling + multiview rendering within the Qt Quick scenegraph. + + A depth-stencil buffer, if applicable, is created and used automatically. + When the color buffer is multisample, the depth-stencil buffer will + automatically be multisample too. For multiview rendering, the depth-stencil + texture will automatically be made an array with a matching \a arraySize. + + The OpenGL object name \a textureId must be a valid 2D texture name in the + rendering context used by the Qt Quick scenegraph. When \a arraySize is + greater than 1, \a textureId must be a valid 2D texture array name. + + \note the resulting QQuickRenderTarget does not own any native resources, it + merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.8 + + \note The implementation of this overload is not compatible with OpenGL ES + 2.0 or 3.0, and requires OpenGL ES 3.1 at minimum. (or OpenGL 3.0 on + desktop) + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl, fromOpenGLTexture() + */ +QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint format, const QSize &pixelSize, int sampleCount, int arraySize, Flags flags) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!textureId) { + qWarning("QQuickRenderTarget: textureId is invalid"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromGL(format, &formatFlags); + + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve); + + if (arraySize <= 1) { + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->u.nativeTexture = { textureId, 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + } else { + d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray; + d->u.nativeTextureArray = { textureId, 0, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + } + + return rt; +} + +/*! + \return a new QQuickRenderTarget referencing an OpenGL renderbuffer object + specified by \a renderbufferId. + + The renderbuffer will be used as the color attachment for the internal + framebuffer object. This function is provided to allow targeting + renderbuffers that are created by the application with some external buffer + underneath, such as an EGLImageKHR. Once the application has called + \l{https://www.khronos.org/registry/OpenGL/extensions/OES/OES_EGL_image.txt}{glEGLImageTargetRenderbufferStorageOES}, + the renderbuffer can be passed to this function. + + \a pixelSize specifies the size of the image, in pixels. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample renderbuffer. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.2 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +QQuickRenderTarget QQuickRenderTarget::fromOpenGLRenderBuffer(uint renderbufferId, const QSize &pixelSize, int sampleCount) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!renderbufferId) { + qWarning("QQuickRenderTarget: renderbufferId is invalid"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::NativeRenderbuffer; + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + d->u.nativeRenderbufferObject = renderbufferId; + + return rt; +} +#endif + +/*! + \return a new QQuickRenderTarget referencing a D3D11 texture object + specified by \a texture. + + \a format specifies the DXGI_FORMAT of the texture. Only texture formats + that are supported by Qt's rendering infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.4 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +#if defined(Q_OS_WIN) || defined(Q_QDOC) +QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint format, + const QSize &pixelSize, int sampleCount) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!texture) { + qWarning("QQuickRenderTarget: texture is null"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(format, &formatFlags); + d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + + return rt; +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a D3D11 texture + object specified by \a texture. The texture is assumed to have a + format of DXGI_FORMAT_R8G8B8A8_UNORM. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl +*/ +QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSize &pixelSize, int sampleCount) +{ + return fromD3D11Texture(texture, 0 /* DXGI_FORMAT_UNKNOWN */, pixelSize, sampleCount); +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a D3D11 texture object + specified by \a texture. + + \a format specifies the DXGI_FORMAT of the texture. Only texture formats + that are supported by Qt's rendering infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture, except when \a flags contains \l MultisampleResolve. In + that case, \a texture is assumed to be a non-multisample 2D texture and \a + sampleCount defines the number of samples desired. The resulting + QQuickRenderTarget will use an intermediate, automatically created + multisample texture as its color attachment, and will resolve the samples + into \a texture. This is the recommended approach to perform MSAA when the + native texture is not already multisample. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. When the color buffer is multisample, the + depth-stencil buffer will automatically be multisample too. + + \note the resulting QQuickRenderTarget does not own any native resources, it + merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.8 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl, fromD3D11Texture() + */ +QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint format, const QSize &pixelSize, int sampleCount, Flags flags) +{ + QQuickRenderTarget rt = fromD3D11Texture(texture, format, pixelSize, sampleCount); + QQuickRenderTargetPrivate::get(&rt)->multisampleResolve = flags.testFlag(Flag::MultisampleResolve); + return rt; +} + +/*! + \return a new QQuickRenderTarget referencing a D3D12 texture object + specified by \a texture. + + \a resourceState must a valid bitmask with bits from D3D12_RESOURCE_STATES, + specifying the resource's current state. + + \a format specifies the DXGI_FORMAT of the texture. Only texture formats + that are supported by Qt's rendering infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.6 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +QQuickRenderTarget QQuickRenderTarget::fromD3D12Texture(void *texture, + int resourceState, + uint format, + const QSize &pixelSize, + int sampleCount) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!texture) { + qWarning("QQuickRenderTarget: texture is null"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(format, &formatFlags); + d->u.nativeTexture = { quint64(texture), resourceState, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + + return rt; +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a D3D12 2D texture or 2D + texture array object specified by \a texture. + + \a resourceState must a valid bitmask with bits from D3D12_RESOURCE_STATES, + specifying the resource's current state. + + \a format specifies the DXGI_FORMAT of the texture. Only texture formats + that are supported by Qt's rendering infrastructure should be used. + + \a viewFormat is the DXGI_FORMAT used for the render target view (RTV). + Often the same as \a format. Functional only when + \l{https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html}{relaxed + format casting} is supported by the driver, the argument is ignored otherwise. + In practice support is expected to be always available on Windows 10 1703 + and newer. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures and 2D texture arrays are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture, except when \a flags contains \l MultisampleResolve. In + that case, \a texture is assumed to be a non-multisample 2D texture or 2D + texture array, and \a sampleCount defines the number of samples desired. The + resulting QQuickRenderTarget will use an intermediate, automatically created + multisample texture (or texture array) as its color attachment, and will + resolve the samples into \a texture. This is the recommended approach to + perform MSAA when the native D3D12 texture is not already multisample. + + The number of array elements (layers) is given in \a arraySize. When greater + than 1, it implies multiview rendering + (\l{https://microsoft.github.io/DirectX-Specs/d3d/ViewInstancing.html}{view + instancing}), which can be relevant with VR/AR especially. \a arraySize is + the number of views, typically \c 2. See \l QSGMaterial::viewCount() for + details on enabling multiview rendering within the Qt Quick scenegraph. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. When the color buffer is multisample, the + depth-stencil buffer will automatically be multisample too. For multiview + rendering, the depth-stencil texture will automatically be made an array + with a matching \a arraySize. + + \note the resulting QQuickRenderTarget does not own any native resources, it + merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.8 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +QQuickRenderTarget QQuickRenderTarget::fromD3D12Texture(void *texture, + int resourceState, + uint format, + uint viewFormat, + const QSize &pixelSize, + int sampleCount, + int arraySize, + Flags flags) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!texture) { + qWarning("QQuickRenderTarget: texture is null"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(format, &formatFlags); + QRhiTexture::Flags viewFormatFlags; + QRhiTexture::Format rhiViewFormat = QSGRhiSupport::toRhiTextureFormatFromDXGI(viewFormat, &viewFormatFlags); + + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve); + + if (arraySize <= 1) { + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->u.nativeTexture = { quint64(texture), resourceState, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) }; + } else { + d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray; + d->u.nativeTextureArray = { quint64(texture), resourceState, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) }; + } + + return rt; +} + +#endif // Q_OS_WIN + +/*! + \return a new QQuickRenderTarget referencing a Metal texture object + specified by \a texture. + + \a format specifies the MTLPixelFormat of the texture. Only texture formats + that are supported by Qt's rendering infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.4 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +#if QT_CONFIG(metal) || defined(Q_QDOC) +QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uint format, + const QSize &pixelSize, int sampleCount) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!texture) { + qWarning("QQuickRenderTarget: texture is null"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(format, &formatFlags); + d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + + return rt; +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a Metal texture object + specified by \a texture. The texture is assumed to have a format of + MTLPixelFormatRGBA8Unorm. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl +*/ +QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, const QSize &pixelSize, int sampleCount) +{ + return fromMetalTexture(texture, 0 /* MTLPixelFormatInvalid */, pixelSize, sampleCount); +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a Metal 2D texture or 2D + texture array given in \a texture. + + \a format specifies the MTLPixelFormat of the texture. Only texture formats + that are supported by Qt's rendering infrastructure should be used. + + \a viewFormat is usually set to the same value as \a format. In some cases, + such as when rendering into a texture with a \c{_SRGB} format and the + implicit linear->sRGB conversion on shader writes is not wanted, the value + can be different. Note however that the value may be ignored by Qt, when at + run time QRhi reports that the \l{QRhi::TextureViewFormat} feature is + unsupported. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures and 2D texture arrays are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture, except when \a flags contains \l MultisampleResolve. In + that case, \a texture is assumed to be a non-multisample 2D texture or 2D + texture array, and \a sampleCount defines the number of samples desired. The + resulting QQuickRenderTarget will use an intermediate, automatically created + multisample texture (or texture array) as its color attachment, and will + resolve the samples into \a texture. This is the recommended approach to + perform MSAA when the native Metal texture is not already multisample. + + The number of array elements (layers) is given in \a arraySize. When greater + than 1, it implies multiview rendering, which can be relevant with VR/AR + especially. \a arraySize is the number of views, typically \c 2. See + \l QSGMaterial::viewCount() for details on enabling multiview rendering within + the Qt Quick scenegraph. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. When the color buffer is multisample, the + depth-stencil buffer will automatically be multisample too. For multiview + rendering, the depth-stencil texture will automatically be made an array + with a matching \a arraySize. + + \note the resulting QQuickRenderTarget does not own any native resources, it + merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.8 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uint format, uint viewFormat, + const QSize &pixelSize, int sampleCount, int arraySize, Flags flags) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!texture) { + qWarning("QQuickRenderTarget: texture is null"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(format, &formatFlags); + QRhiTexture::Flags viewFormatFlags; + QRhiTexture::Format rhiViewFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(viewFormat, &viewFormatFlags); + + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve); + + if (arraySize <= 1) { + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) }; + } else { + d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray; + d->u.nativeTextureArray = { quint64(texture), 0, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) }; + } + + return rt; +} + +#endif + +/*! + \return a new QQuickRenderTarget referencing a Vulkan image object + specified by \a image. The current \a layout of the image must be provided + as well. + + \a format specifies the VkFormat of the image. Only image formats that are + supported by Qt's rendering infrastructure should be used. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The image is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.4 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +#if QT_CONFIG(vulkan) || defined(Q_QDOC) +QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, const QSize &pixelSize, int sampleCount) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (image == VK_NULL_HANDLE) { + qWarning("QQuickRenderTarget: image is invalid"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(format, &formatFlags); + d->u.nativeTexture = { quint64(image), layout, uint(rhiFormat), uint(formatFlags), uint(rhiFormat), uint(formatFlags) }; + + return rt; +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a Vulkan image object specified + by \a image. The image is assumed to have a format of + VK_FORMAT_R8G8B8A8_UNORM. + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. + + \note the resulting QQuickRenderTarget does not own any native resources, + it merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl +*/ +QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, const QSize &pixelSize, int sampleCount) +{ + return fromVulkanImage(image, layout, VK_FORMAT_UNDEFINED, pixelSize, sampleCount); +} + +/*! + \overload + + \return a new QQuickRenderTarget referencing a Vulkan image object + specified by \a image. The current \a layout of the image must be provided + as well. The image must be either a 2D texture or 2D texture array. + + \a format specifies the VkFormat of the image. Only image formats that are + supported by Qt's rendering infrastructure should be used. + + \a viewFormat is usually set to the same value as \a format. In some cases, + such as when rendering into a texture with a \c{_SRGB} format and the + implicit linear->sRGB conversion on shader writes is not wanted, the value + can be different. (for example, a \a format of \c VK_FORMAT_R8G8B8A8_SRGB + and \a viewFormat of \c VK_FORMAT_R8G8B8A8_UNORM). + + \a pixelSize specifies the size of the image, in pixels. Currently only 2D + textures are supported. + + \a sampleCount specifies the number of samples. 0 or 1 means no + multisampling, while a value like 4 or 8 states that the native object is a + multisample texture, except when \a flags contains \l MultisampleResolve. In + that case, \a image is assumed to be a non-multisample 2D texture or 2D + texture array, and \a sampleCount defines the number of samples desired. The + resulting QQuickRenderTarget will use an intermediate, automatically created + multisample texture (or texture array) as its color attachment, and will + resolve the samples into \a image. This is the recommended approach to + perform MSAA when the native Vulkan image is not already multisample. + + The number of array elements (layers) is given in \a arraySize. When greater + than 1, it implies multiview rendering + (\l{https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VK_KHR_multiview.html}{VK_KHR_multiview}), + which can be relevant with VR/AR especially. \a arraySize is the number of + views, typically \c 2. See \l QSGMaterial::viewCount() for details on + enabling multiview rendering within the Qt Quick scenegraph. + + The texture is used as the first color attachment of the render target used + by the Qt Quick scenegraph. A depth-stencil buffer, if applicable, is + created and used automatically. When the color buffer is multisample, the + depth-stencil buffer will automatically be multisample too. For multiview + rendering, the depth-stencil texture will automatically be made an array + with a matching \a arraySize. + + \note the resulting QQuickRenderTarget does not own any native resources, it + merely contains references and the associated metadata of the size and + sample count. It is the caller's responsibility to ensure that the native + resource exists as long as necessary. + + \since 6.8 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, VkFormat viewFormat, + const QSize &pixelSize, int sampleCount, int arraySize, Flags flags) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (image == VK_NULL_HANDLE) { + qWarning("QQuickRenderTarget: image is invalid"); + return rt; + } + + if (pixelSize.isEmpty()) { + qWarning("QQuickRenderTarget: Cannot create with empty size"); + return rt; + } + + QRhiTexture::Flags formatFlags; + QRhiTexture::Format rhiFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(format, &formatFlags); + QRhiTexture::Flags viewFormatFlags; + QRhiTexture::Format rhiViewFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(viewFormat, &viewFormatFlags); + + d->pixelSize = pixelSize; + d->sampleCount = qMax(1, sampleCount); + d->multisampleResolve = flags.testFlag(Flag::MultisampleResolve); + + if (arraySize <= 1) { + d->type = QQuickRenderTargetPrivate::Type::NativeTexture; + d->u.nativeTexture = { quint64(image), layout, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) }; + } else { + d->type = QQuickRenderTargetPrivate::Type::NativeTextureArray; + d->u.nativeTextureArray = { quint64(image), layout, arraySize, uint(rhiFormat), uint(formatFlags), uint(rhiViewFormat), uint(viewFormatFlags) }; + } + + return rt; +} + +#endif // Vulkan + +/*! + \return a new QQuickRenderTarget referencing an existing \a renderTarget. + + \a renderTarget will in most cases be a QRhiTextureRenderTarget, which + allows directing the Qt Quick scene's rendering into a QRhiTexture. + + \note the resulting QQuickRenderTarget does not own \a renderTarget and any + underlying native resources, it merely contains references and the + associated metadata of the size and sample count. It is the caller's + responsibility to ensure that the referenced resources exists as long as + necessary. + + \since 6.6 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl +*/ +QQuickRenderTarget QQuickRenderTarget::fromRhiRenderTarget(QRhiRenderTarget *renderTarget) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + if (!renderTarget) { + qWarning("QQuickRenderTarget: Needs a valid QRhiRenderTarget"); + return rt; + } + + d->type = QQuickRenderTargetPrivate::Type::RhiRenderTarget; + d->pixelSize = renderTarget->pixelSize(); + d->sampleCount = renderTarget->sampleCount(); + d->u.rhiRt = renderTarget; + + return rt; +} + +/*! + \return a new QQuickRenderTarget referencing a paint device object + specified by \a device. + + This option of redirecting rendering to a QPaintDevice is available only + when running with the \c software backend of Qt Quick. + + \note The QQuickRenderTarget does not take ownship of \a device, it is the + caller's responsibility to ensure the object exists as long as necessary. + + \since 6.4 + + \sa QQuickWindow::setRenderTarget(), QQuickRenderControl + */ +QQuickRenderTarget QQuickRenderTarget::fromPaintDevice(QPaintDevice *device) +{ + QQuickRenderTarget rt; + QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); + + d->type = QQuickRenderTargetPrivate::Type::PaintDevice; + d->pixelSize = QSize(device->width(), device->height()); + d->u.paintDevice = device; + + return rt; +} + +/*! + \fn bool QQuickRenderTarget::operator==(const QQuickRenderTarget &a, const QQuickRenderTarget &b) noexcept + \return true if \a a and \a b refer to the same set of native objects and + have matching associated data (size, sample count). +*/ +/*! + \fn bool QQuickRenderTarget::operator!=(const QQuickRenderTarget &a, const QQuickRenderTarget &b) noexcept + + \return true if \a a and \a b refer to a different set of native objects, + or the associated data (size, sample count) does not match. +*/ + +/*! + \internal +*/ +bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept +{ + if (d->type != other.d->type + || d->pixelSize != other.d->pixelSize + || d->devicePixelRatio != other.d->devicePixelRatio + || d->sampleCount != other.d->sampleCount + || d->mirrorVertically != other.d->mirrorVertically + || d->multisampleResolve != other.d->multisampleResolve) + { + return false; + } + + switch (d->type) { + case QQuickRenderTargetPrivate::Type::Null: + break; + case QQuickRenderTargetPrivate::Type::NativeTexture: + if (d->u.nativeTexture.object != other.d->u.nativeTexture.object + || d->u.nativeTexture.layoutOrState != other.d->u.nativeTexture.layoutOrState + || d->u.nativeTexture.rhiFormat != other.d->u.nativeTexture.rhiFormat + || d->u.nativeTexture.rhiFormatFlags != other.d->u.nativeTexture.rhiFormatFlags + || d->u.nativeTexture.rhiViewFormat != other.d->u.nativeTexture.rhiViewFormat + || d->u.nativeTexture.rhiViewFormatFlags != other.d->u.nativeTexture.rhiViewFormatFlags) + return false; + break; + case QQuickRenderTargetPrivate::Type::NativeTextureArray: + if (d->u.nativeTextureArray.object != other.d->u.nativeTextureArray.object + || d->u.nativeTextureArray.layoutOrState != other.d->u.nativeTextureArray.layoutOrState + || d->u.nativeTextureArray.arraySize != other.d->u.nativeTextureArray.arraySize + || d->u.nativeTextureArray.rhiFormat != other.d->u.nativeTextureArray.rhiFormat + || d->u.nativeTextureArray.rhiFormatFlags != other.d->u.nativeTextureArray.rhiFormatFlags + || d->u.nativeTextureArray.rhiViewFormat != other.d->u.nativeTextureArray.rhiViewFormat + || d->u.nativeTextureArray.rhiViewFormatFlags != other.d->u.nativeTextureArray.rhiViewFormatFlags) + return false; + break; + case QQuickRenderTargetPrivate::Type::NativeRenderbuffer: + if (d->u.nativeRenderbufferObject != other.d->u.nativeRenderbufferObject) + return false; + break; + case QQuickRenderTargetPrivate::Type::RhiRenderTarget: + if (d->u.rhiRt != other.d->u.rhiRt) + return false; + break; + case QQuickRenderTargetPrivate::Type::PaintDevice: + if (d->u.paintDevice != other.d->u.paintDevice) + return false; + break; + default: + break; + } + + return true; +} + +static bool createRhiRenderTargetWithRenderBuffer(QRhiRenderBuffer *renderBuffer, + const QSize &pixelSize, + int sampleCount, + QRhi *rhi, + QQuickWindowRenderTarget *dst) +{ + sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi); + + std::unique_ptr<QRhiRenderBuffer> depthStencil; + if (dst->implicitBuffers.depthStencil) { + if (dst->implicitBuffers.depthStencil->pixelSize() == pixelSize + && dst->implicitBuffers.depthStencil->sampleCount() == sampleCount) + { + depthStencil.reset(dst->implicitBuffers.depthStencil); + dst->implicitBuffers.depthStencil = nullptr; + } + } + dst->implicitBuffers.reset(rhi); + + if (!depthStencil) { + depthStencil.reset(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); + depthStencil->setName(QByteArrayLiteral("Depth-stencil buffer for QQuickRenderTarget")); + if (!depthStencil->create()) { + qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget"); + return false; + } + } + + QRhiColorAttachment colorAttachment(renderBuffer); + QRhiTextureRenderTargetDescription rtDesc(colorAttachment); + rtDesc.setDepthStencilBuffer(depthStencil.get()); + std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + rt->setName(QByteArrayLiteral("RT for QQuickRenderTarget with renderbuffer")); + std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.get()); + + if (!rt->create()) { + qWarning("Failed to build renderbuffer-based render target for QQuickRenderTarget"); + return false; + } + + dst->rt.renderTarget = rt.release(); + dst->rt.owns = true; + dst->res.rpDesc = rp.release(); + dst->implicitBuffers.depthStencil = depthStencil.release(); + + return true; +} + +static bool createRhiRenderTarget(QRhiTexture *texture, + const QSize &pixelSize, + int sampleCount, + bool multisampleResolve, + QRhi *rhi, + QQuickWindowRenderTarget *dst) +{ + // Simple path: no user-supplied depth texture. So create our own + // depth-stencil buffer, using renderbuffers (so this is still GLES 2.0 + // compatible), with MSAA support being GLES 3.0 compatible. + + sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi); + if (sampleCount <= 1) + multisampleResolve = false; + + std::unique_ptr<QRhiRenderBuffer> depthStencil; + if (dst->implicitBuffers.depthStencil) { + if (dst->implicitBuffers.depthStencil->pixelSize() == pixelSize + && dst->implicitBuffers.depthStencil->sampleCount() == sampleCount) + { + depthStencil.reset(dst->implicitBuffers.depthStencil); + dst->implicitBuffers.depthStencil = nullptr; + } + } + + std::unique_ptr<QRhiTexture> colorBuffer; + QRhiTexture::Flags multisampleTextureFlags; + QRhiTexture::Format multisampleTextureFormat = texture->format(); + if (multisampleResolve) { + multisampleTextureFlags = QRhiTexture::RenderTarget; + if (texture->flags().testFlag(QRhiTexture::sRGB)) + multisampleTextureFlags |= QRhiTexture::sRGB; + + if (dst->implicitBuffers.multisampleTexture) { + if (dst->implicitBuffers.multisampleTexture->pixelSize() == pixelSize + && dst->implicitBuffers.multisampleTexture->format() == multisampleTextureFormat + && dst->implicitBuffers.multisampleTexture->sampleCount() == sampleCount + && dst->implicitBuffers.multisampleTexture->flags().testFlags(multisampleTextureFlags)) + { + colorBuffer.reset(dst->implicitBuffers.multisampleTexture); + dst->implicitBuffers.multisampleTexture = nullptr; + } + } + } + + dst->implicitBuffers.reset(rhi); + + if (!depthStencil) { + depthStencil.reset(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); + depthStencil->setName(QByteArrayLiteral("Depth-stencil buffer for QQuickRenderTarget")); + if (!depthStencil->create()) { + qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget"); + return false; + } + } + + if (multisampleResolve && !colorBuffer) { + colorBuffer.reset(rhi->newTexture(multisampleTextureFormat, pixelSize, sampleCount, multisampleTextureFlags)); + colorBuffer->setName(QByteArrayLiteral("Multisample color buffer for QQuickRenderTarget")); + colorBuffer->setWriteViewFormat(texture->writeViewFormat()); + if (!colorBuffer->create()) { + qWarning("Failed to build multisample color buffer for QQuickRenderTarget"); + return false; + } + } + + QRhiColorAttachment colorAttachment; + if (multisampleResolve) { + colorAttachment.setTexture(colorBuffer.get()); + colorAttachment.setResolveTexture(texture); + } else { + colorAttachment.setTexture(texture); + } + QRhiTextureRenderTargetDescription rtDesc(colorAttachment); + rtDesc.setDepthStencilBuffer(depthStencil.get()); + std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + rt->setName(QByteArrayLiteral("RT for QQuickRenderTarget")); + std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.get()); + + if (!rt->create()) { + qWarning("Failed to build texture render target for QQuickRenderTarget"); + return false; + } + + dst->rt.renderTarget = rt.release(); + dst->rt.owns = true; + dst->res.rpDesc = rp.release(); + dst->implicitBuffers.depthStencil = depthStencil.release(); + if (multisampleResolve) + dst->implicitBuffers.multisampleTexture = colorBuffer.release(); + + return true; +} + +static bool createRhiRenderTargetWithDepthTexture(QRhiTexture *texture, + QRhiTexture *depthTexture, + const QSize &pixelSize, + int sampleCount, + bool multisampleResolve, + QRhi *rhi, + QQuickWindowRenderTarget *dst) +{ + // This version takes a user-supplied depthTexture. That texture is always + // non-multisample. If sample count is > 1, we still need our own + // multisample depth-stencil buffer, and the depth(stencil) data is expected + // to be resolved (and written out) to depthTexture, _if_ the underlying API + // supports it (see QRhi's ResolveDepthStencil feature). The intermediate, + // multisample depth-stencil buffer must be a texture here (not + // renderbuffer), specifically for OpenGL ES and its related multisample + // extensions. + + sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi); + if (sampleCount <= 1) + multisampleResolve = false; + + std::unique_ptr<QRhiTexture> depthStencil; + if (dst->implicitBuffers.depthStencilTexture) { + if (dst->implicitBuffers.depthStencilTexture->pixelSize() == pixelSize + && dst->implicitBuffers.depthStencilTexture->sampleCount() == sampleCount) + { + depthStencil.reset(dst->implicitBuffers.depthStencilTexture); + dst->implicitBuffers.depthStencilTexture = nullptr; + } + } + + std::unique_ptr<QRhiTexture> colorBuffer; + QRhiTexture::Flags multisampleTextureFlags; + QRhiTexture::Format multisampleTextureFormat = texture->format(); + if (multisampleResolve) { + multisampleTextureFlags = QRhiTexture::RenderTarget; + if (texture->flags().testFlag(QRhiTexture::sRGB)) + multisampleTextureFlags |= QRhiTexture::sRGB; + + if (dst->implicitBuffers.multisampleTexture) { + if (dst->implicitBuffers.multisampleTexture->pixelSize() == pixelSize + && dst->implicitBuffers.multisampleTexture->format() == multisampleTextureFormat + && dst->implicitBuffers.multisampleTexture->sampleCount() == sampleCount + && dst->implicitBuffers.multisampleTexture->flags().testFlags(multisampleTextureFlags)) + { + colorBuffer.reset(dst->implicitBuffers.multisampleTexture); + dst->implicitBuffers.multisampleTexture = nullptr; + } + } + } + + dst->implicitBuffers.reset(rhi); + + bool needsDepthStencilBuffer = true; + if (sampleCount <= 1) { + depthStencil.reset(); + needsDepthStencilBuffer = false; + } + if (depthTexture->pixelSize() != pixelSize) { + qWarning("Custom depth texture size (%dx%d) does not match the QQuickRenderTarget (%dx%d)", + depthTexture->pixelSize().width(), + depthTexture->pixelSize().height(), + pixelSize.width(), + pixelSize.height()); + return false; + } + if (depthTexture->sampleCount() > 1) { + qWarning("Custom depth texture cannot be multisample"); + return false; + } + if (needsDepthStencilBuffer && !depthStencil) { + QRhiTexture::Format multisampleDepthTextureFormat = depthTexture->format(); + depthStencil.reset(rhi->newTexture(multisampleDepthTextureFormat, pixelSize, sampleCount, QRhiTexture::RenderTarget)); + depthStencil->setName(QByteArrayLiteral("Depth-stencil texture for QQuickRenderTarget")); + if (!depthStencil->create()) { + qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget"); + return false; + } + } + + if (multisampleResolve && !colorBuffer) { + colorBuffer.reset(rhi->newTexture(multisampleTextureFormat, pixelSize, sampleCount, multisampleTextureFlags)); + colorBuffer->setName(QByteArrayLiteral("Multisample color buffer for QQuickRenderTarget")); + colorBuffer->setWriteViewFormat(texture->writeViewFormat()); + if (!colorBuffer->create()) { + qWarning("Failed to build multisample color buffer for QQuickRenderTarget"); + return false; + } + } + + QRhiColorAttachment colorAttachment; + if (multisampleResolve) { + colorAttachment.setTexture(colorBuffer.get()); + colorAttachment.setResolveTexture(texture); + } else { + colorAttachment.setTexture(texture); + } + + QRhiTextureRenderTargetDescription rtDesc(colorAttachment); + if (sampleCount > 1) { + rtDesc.setDepthTexture(depthStencil.get()); + if (rhi->isFeatureSupported(QRhi::ResolveDepthStencil)) + rtDesc.setDepthResolveTexture(depthTexture); + else + qWarning("Depth-stencil resolve is not supported by the underlying 3D API, depth contents will not be resolved"); + } else { + rtDesc.setDepthTexture(depthTexture); + } + + std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc)); + rt->setName(QByteArrayLiteral("RT for QQuickRenderTarget")); + std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.get()); + + if (!rt->create()) { + qWarning("Failed to build texture render target for QQuickRenderTarget"); + return false; + } + + dst->rt.renderTarget = rt.release(); + dst->rt.owns = true; + dst->res.rpDesc = rp.release(); + if (depthStencil) + dst->implicitBuffers.depthStencilTexture = depthStencil.release(); + if (multisampleResolve) + dst->implicitBuffers.multisampleTexture = colorBuffer.release(); + + return true; +} + +static bool createRhiRenderTargetMultiView(QRhiTexture *texture, + QRhiTexture *maybeCustomDepthTexture, + const QSize &pixelSize, + int arraySize, + int sampleCount, + bool multisampleResolve, + QRhi *rhi, + QQuickWindowRenderTarget *dst) +{ + // Multiview path, working with texture arrays. Optionally with a + // user-supplied, non-multisample depth texture (array). (same semantics + // then as with createRhiRenderTargetWithDepthTexture, but everything is a + // 2D texture array here) + + sampleCount = QSGRhiSupport::chooseSampleCount(sampleCount, rhi); + if (sampleCount <= 1) + multisampleResolve = false; + + std::unique_ptr<QRhiTexture> depthStencil; + if (dst->implicitBuffers.depthStencilTexture) { + if (dst->implicitBuffers.depthStencilTexture->pixelSize() == pixelSize + && dst->implicitBuffers.depthStencilTexture->sampleCount() == sampleCount + && dst->implicitBuffers.depthStencilTexture->arraySize() == arraySize) + { + depthStencil.reset(dst->implicitBuffers.depthStencilTexture); + dst->implicitBuffers.depthStencilTexture = nullptr; + } + } + + std::unique_ptr<QRhiTexture> colorBuffer; + QRhiTexture::Flags multisampleTextureFlags; + QRhiTexture::Format multisampleTextureFormat = texture->format(); + if (multisampleResolve) { + multisampleTextureFlags = QRhiTexture::RenderTarget; + if (texture->flags().testFlag(QRhiTexture::sRGB)) + multisampleTextureFlags |= QRhiTexture::sRGB; + + if (dst->implicitBuffers.multisampleTexture) { + if (dst->implicitBuffers.multisampleTexture->pixelSize() == pixelSize + && dst->implicitBuffers.multisampleTexture->format() == multisampleTextureFormat + && dst->implicitBuffers.multisampleTexture->sampleCount() == sampleCount + && dst->implicitBuffers.multisampleTexture->arraySize() == arraySize + && dst->implicitBuffers.multisampleTexture->flags().testFlags(multisampleTextureFlags)) + { + colorBuffer.reset(dst->implicitBuffers.multisampleTexture); + dst->implicitBuffers.multisampleTexture = nullptr; + } + } + } + + dst->implicitBuffers.reset(rhi); + + bool needsDepthStencilBuffer = true; + if (maybeCustomDepthTexture) { + if (sampleCount <= 1) { + depthStencil.reset(); + needsDepthStencilBuffer = false; + } + if (maybeCustomDepthTexture->arraySize() != arraySize) { + qWarning("Custom depth texture array size (%d) does not match QQuickRenderTarget (%d)", + maybeCustomDepthTexture->arraySize(), arraySize); + return false; + } + if (maybeCustomDepthTexture->pixelSize() != pixelSize) { + qWarning("Custom depth texture size (%dx%d) does not match the QQuickRenderTarget (%dx%d)", + maybeCustomDepthTexture->pixelSize().width(), + maybeCustomDepthTexture->pixelSize().height(), + pixelSize.width(), + pixelSize.height()); + return false; + } + if (maybeCustomDepthTexture->sampleCount() > 1) { + qWarning("Custom depth texture cannot be multisample"); + return false; + } + } + if (needsDepthStencilBuffer && !depthStencil) { + depthStencil.reset(rhi->newTextureArray(QRhiTexture::D24S8, arraySize, pixelSize, sampleCount, QRhiTexture::RenderTarget)); + depthStencil->setName(QByteArrayLiteral("Depth-stencil buffer (multiview) for QQuickRenderTarget")); + if (!depthStencil->create()) { + qWarning("Failed to build depth-stencil texture array for QQuickRenderTarget"); + return false; + } + } + + if (multisampleResolve && !colorBuffer) { + colorBuffer.reset(rhi->newTextureArray(multisampleTextureFormat, arraySize, pixelSize, sampleCount, multisampleTextureFlags)); + colorBuffer->setName(QByteArrayLiteral("Multisample color buffer (multiview) for QQuickRenderTarget")); + colorBuffer->setWriteViewFormat(texture->writeViewFormat()); + if (!colorBuffer->create()) { + qWarning("Failed to build multisample texture array for QQuickRenderTarget"); + return false; + } + } + + QRhiColorAttachment colorAttachment; + colorAttachment.setMultiViewCount(arraySize); + if (multisampleResolve) { + colorAttachment.setTexture(colorBuffer.get()); + colorAttachment.setResolveTexture(texture); + } else { + colorAttachment.setTexture(texture); + } + + QRhiTextureRenderTargetDescription rtDesc(colorAttachment); + if (sampleCount > 1) { + rtDesc.setDepthTexture(depthStencil.get()); + if (maybeCustomDepthTexture) { + if (rhi->isFeatureSupported(QRhi::ResolveDepthStencil)) + rtDesc.setDepthResolveTexture(maybeCustomDepthTexture); + else + qWarning("Depth-stencil resolve is not supported by the underlying 3D API, depth contents will not be resolved"); + } + } else { + if (depthStencil) + rtDesc.setDepthTexture(depthStencil.get()); + else if (maybeCustomDepthTexture) + rtDesc.setDepthTexture(maybeCustomDepthTexture); + } + + QRhiTextureRenderTarget::Flags rtFlags; + if (!maybeCustomDepthTexture) + rtFlags |= QRhiTextureRenderTarget::DoNotStoreDepthStencilContents; + + std::unique_ptr<QRhiTextureRenderTarget> rt(rhi->newTextureRenderTarget(rtDesc, rtFlags)); + rt->setName(QByteArrayLiteral("RT for multiview QQuickRenderTarget")); + std::unique_ptr<QRhiRenderPassDescriptor> rp(rt->newCompatibleRenderPassDescriptor()); + rt->setRenderPassDescriptor(rp.get()); + + if (!rt->create()) { + qWarning("Failed to build multiview texture render target for QQuickRenderTarget"); + return false; + } + + dst->rt.renderTarget = rt.release(); + dst->rt.owns = true; + dst->res.rpDesc = rp.release(); + if (depthStencil) + dst->implicitBuffers.depthStencilTexture = depthStencil.release(); + if (multisampleResolve) + dst->implicitBuffers.multisampleTexture = colorBuffer.release(); + + dst->rt.multiViewCount = arraySize; + + return true; +} + +bool QQuickRenderTargetPrivate::resolve(QRhi *rhi, QQuickWindowRenderTarget *dst) +{ + // dst->implicitBuffers may contain valid objects. If so, and their + // properties are suitable, they are expected to be reused. Once taken what + // we can reuse, it needs to be reset(). + + switch (type) { + case Type::Null: + dst->implicitBuffers.reset(rhi); + return true; + + case Type::NativeTexture: + { + QRhiTexture::Format format = u.nativeTexture.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 + : QRhiTexture::Format(u.nativeTexture.rhiFormat); + QRhiTexture::Format viewFormat = u.nativeTexture.rhiViewFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 + : QRhiTexture::Format(u.nativeTexture.rhiViewFormat); + const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags(u.nativeTexture.rhiFormatFlags); + std::unique_ptr<QRhiTexture> texture(rhi->newTexture(format, pixelSize, multisampleResolve ? 1 : sampleCount, flags)); + const bool textureIsSrgb = flags.testFlag(QRhiTexture::sRGB); + const bool viewIsSrgb = QRhiTexture::Flags(u.nativeTexture.rhiViewFormatFlags).testFlag(QRhiTexture::sRGB); + if (viewFormat != format || viewIsSrgb != textureIsSrgb) + texture->setWriteViewFormat({ viewFormat, viewIsSrgb }); + if (!texture->createFrom({ u.nativeTexture.object, u.nativeTexture.layoutOrState })) { + qWarning("Failed to build wrapper texture for QQuickRenderTarget"); + return false; + } + if (customDepthTexture) { + if (!createRhiRenderTargetWithDepthTexture(texture.get(), customDepthTexture, pixelSize, sampleCount, multisampleResolve, rhi, dst)) + return false; + } else { + if (!createRhiRenderTarget(texture.get(), pixelSize, sampleCount, multisampleResolve, rhi, dst)) + return false; + } + dst->res.texture = texture.release(); + } + return true; + + case Type::NativeTextureArray: + { + QRhiTexture::Format format = u.nativeTextureArray.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 + : QRhiTexture::Format(u.nativeTextureArray.rhiFormat); + QRhiTexture::Format viewFormat = u.nativeTextureArray.rhiViewFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 + : QRhiTexture::Format(u.nativeTextureArray.rhiViewFormat); + const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags(u.nativeTextureArray.rhiFormatFlags); + const int arraySize = u.nativeTextureArray.arraySize; + std::unique_ptr<QRhiTexture> texture(rhi->newTextureArray(format, arraySize, pixelSize, multisampleResolve ? 1 : sampleCount, flags)); + const bool textureIsSrgb = flags.testFlag(QRhiTexture::sRGB); + const bool viewIsSrgb = QRhiTexture::Flags(u.nativeTextureArray.rhiViewFormatFlags).testFlag(QRhiTexture::sRGB); + if (viewFormat != format || viewIsSrgb != textureIsSrgb) + texture->setWriteViewFormat({ viewFormat, viewIsSrgb }); + if (!texture->createFrom({ u.nativeTextureArray.object, u.nativeTextureArray.layoutOrState })) { + qWarning("Failed to build wrapper texture array for QQuickRenderTarget"); + return false; + } + if (!createRhiRenderTargetMultiView(texture.get(), customDepthTexture, pixelSize, arraySize, sampleCount, multisampleResolve, rhi, dst)) + return false; + dst->res.texture = texture.release(); + } + return true; + + case Type::NativeRenderbuffer: + { + std::unique_ptr<QRhiRenderBuffer> renderbuffer(rhi->newRenderBuffer(QRhiRenderBuffer::Color, pixelSize, sampleCount)); + if (!renderbuffer->createFrom({ u.nativeRenderbufferObject })) { + qWarning("Failed to build wrapper renderbuffer for QQuickRenderTarget"); + return false; + } + if (customDepthTexture) + qWarning("Custom depth texture is not supported with renderbuffers in QQuickRenderTarget"); + if (!createRhiRenderTargetWithRenderBuffer(renderbuffer.get(), pixelSize, sampleCount, rhi, dst)) + return false; + dst->res.renderBuffer = renderbuffer.release(); + } + return true; + + case Type::RhiRenderTarget: + dst->implicitBuffers.reset(rhi); + dst->rt.renderTarget = u.rhiRt; + dst->rt.owns = false; + if (dst->rt.renderTarget->resourceType() == QRhiResource::TextureRenderTarget) { + auto texRt = static_cast<QRhiTextureRenderTarget *>(dst->rt.renderTarget); + const QRhiTextureRenderTargetDescription desc = texRt->description(); + bool first = true; + for (auto it = desc.cbeginColorAttachments(), end = desc.cendColorAttachments(); it != end; ++it) { + if (it->multiViewCount() <= 1) + continue; + if (first || dst->rt.multiViewCount == it->multiViewCount()) { + first = false; + if (it->texture() && it->texture()->flags().testFlag(QRhiTexture::TextureArray)) { + if (it->texture()->arraySize() >= it->layer() + it->multiViewCount()) { + dst->rt.multiViewCount = it->multiViewCount(); + } else { + qWarning("Invalid QQuickRenderTarget; needs at least %d elements in texture array, got %d", + it->layer() + it->multiViewCount(), + it->texture()->arraySize()); + return false; + } + } else { + qWarning("Invalid QQuickRenderTarget; multiview requires a texture array"); + return false; + } + } else { + qWarning("Inconsistent multiViewCount in QQuickRenderTarget (was %d, now found an attachment with %d)", + dst->rt.multiViewCount, it->multiViewCount()); + return false; + } + } + } + if (customDepthTexture) + qWarning("Custom depth texture is not supported with QRhiRenderTarget in QQuickRenderTarget"); + return true; + + case Type::PaintDevice: + dst->implicitBuffers.reset(rhi); + dst->sw.paintDevice = u.paintDevice; + dst->sw.owns = false; + return true; + } + + Q_UNREACHABLE_RETURN(false); +} + +QT_END_NAMESPACE |