diff options
Diffstat (limited to 'src/quick/items/qquickrendertarget.cpp')
-rw-r--r-- | src/quick/items/qquickrendertarget.cpp | 1164 |
1 files changed, 1094 insertions, 70 deletions
diff --git a/src/quick/items/qquickrendertarget.cpp b/src/quick/items/qquickrendertarget.cpp index 7242a55d88..bce5b358ff 100644 --- a/src/quick/items/qquickrendertarget.cpp +++ b/src/quick/items/qquickrendertarget.cpp @@ -2,7 +2,7 @@ // 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 <QtGui/private/qrhi_p.h> +#include <rhi/qrhi.h> #include <QtQuick/private/qquickitem_p.h> #include <QtQuick/private/qquickwindow_p.h> #include <QtQuick/private/qsgrhisupport_p.h> @@ -25,14 +25,16 @@ QQuickRenderTargetPrivate::QQuickRenderTargetPrivate() { } -QQuickRenderTargetPrivate::QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate *other) +QQuickRenderTargetPrivate::QQuickRenderTargetPrivate(const QQuickRenderTargetPrivate &other) : ref(1), - type(other->type), - pixelSize(other->pixelSize), - devicePixelRatio(other->devicePixelRatio), - sampleCount(other->sampleCount), - u(other->u), - mirrorVertically(other->mirrorVertically) + type(other.type), + pixelSize(other.pixelSize), + devicePixelRatio(other.devicePixelRatio), + sampleCount(other.sampleCount), + u(other.u), + customDepthTexture(other.customDepthTexture), + mirrorVertically(other.mirrorVertically), + multisampleResolve(other.multisampleResolve) { } @@ -161,6 +163,84 @@ void QQuickRenderTarget::setMirrorVertically(bool 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 the 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. @@ -171,7 +251,7 @@ void QQuickRenderTarget::setMirrorVertically(bool enable) \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -212,8 +292,9 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint fo d->pixelSize = pixelSize; d->sampleCount = qMax(1, sampleCount); - auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromGL(format); - d->u.nativeTexture = { textureId, 0, uint(rhiFormat), 0 }; + 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; } @@ -228,7 +309,7 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, uint fo \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -252,6 +333,90 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, const Q } /*! + \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. @@ -264,7 +429,7 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLTexture(uint textureId, const Q \a pixelSize specifies the size of the image, in pixels. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -311,7 +476,7 @@ QQuickRenderTarget QQuickRenderTarget::fromOpenGLRenderBuffer(uint renderbufferI \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -349,9 +514,9 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint form d->pixelSize = pixelSize; d->sampleCount = qMax(1, sampleCount); - QRhiTexture::Flags flags; - auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromD3D11(format, &flags); - d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(flags) }; + 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; } @@ -366,7 +531,7 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, uint form \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -385,7 +550,208 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSi { return fromD3D11Texture(texture, 0 /* DXGI_FORMAT_UNKNOWN */, pixelSize, sampleCount); } -#endif + +/*! + \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 @@ -397,7 +763,7 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSi \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -414,7 +780,7 @@ QQuickRenderTarget QQuickRenderTarget::fromD3D11Texture(void *texture, const QSi \sa QQuickWindow::setRenderTarget(), QQuickRenderControl */ -#if defined(Q_OS_MACOS) || defined(Q_OS_IOS) || defined(Q_QDOC) +#if QT_CONFIG(metal) || defined(Q_QDOC) QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uint format, const QSize &pixelSize, int sampleCount) { @@ -435,9 +801,9 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uin d->pixelSize = pixelSize; d->sampleCount = qMax(1, sampleCount); - QRhiTexture::Flags flags; - auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromMetal(format, &flags); - d->u.nativeTexture = { quint64(texture), 0, uint(rhiFormat), uint(flags) }; + 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; } @@ -452,7 +818,7 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, uin \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -471,6 +837,94 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, con { 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 /*! @@ -484,7 +938,7 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, con \a pixelSize specifies the size of the image, in pixels. Currently only 2D textures are supported. - \a sampleCount specific the number of samples. 0 or 1 means no + \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. @@ -502,8 +956,7 @@ QQuickRenderTarget QQuickRenderTarget::fromMetalTexture(MTLTexture *texture, con \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 QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLayout layout, VkFormat format, const QSize &pixelSize, int sampleCount) { QQuickRenderTarget rt; QQuickRenderTargetPrivate *d = QQuickRenderTargetPrivate::get(&rt); @@ -522,9 +975,9 @@ QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLay d->pixelSize = pixelSize; d->sampleCount = qMax(1, sampleCount); - QRhiTexture::Flags flags; - auto rhiFormat = QSGRhiSupport::toRhiTextureFormatFromVulkan(format, &flags); - d->u.nativeTexture = { quint64(image), layout, uint(rhiFormat), uint(flags) }; + 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; } @@ -532,14 +985,14 @@ QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLay /*! \overload - \return a new QQuickRenderTarget referencing n Vulkan image object specified + \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 specific the number of samples. 0 or 1 means no + \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. @@ -558,11 +1011,113 @@ QQuickRenderTarget QQuickRenderTarget::fromVulkanImage(VkImage image, VkImageLay { return fromVulkanImage(image, layout, VK_FORMAT_UNDEFINED, pixelSize, sampleCount); } -#endif /*! - \internal + \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; @@ -628,7 +1183,8 @@ bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept || d->pixelSize != other.d->pixelSize || d->devicePixelRatio != other.d->devicePixelRatio || d->sampleCount != other.d->sampleCount - || d->mirrorVertically != other.d->mirrorVertically) + || d->mirrorVertically != other.d->mirrorVertically + || d->multisampleResolve != other.d->multisampleResolve) { return false; } @@ -638,9 +1194,21 @@ bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept break; case QQuickRenderTargetPrivate::Type::NativeTexture: if (d->u.nativeTexture.object != other.d->u.nativeTexture.object - || d->u.nativeTexture.layout != other.d->u.nativeTexture.layout + || d->u.nativeTexture.layoutOrState != other.d->u.nativeTexture.layoutOrState || d->u.nativeTexture.rhiFormat != other.d->u.nativeTexture.rhiFormat - || d->u.nativeTexture.rhiFlags != other.d->u.nativeTexture.rhiFlags) + || 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: @@ -662,21 +1230,260 @@ bool QQuickRenderTarget::isEqual(const QQuickRenderTarget &other) const noexcept return true; } -static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, +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) { - std::unique_ptr<QRhiRenderBuffer> depthStencil(rhi->newRenderBuffer(QRhiRenderBuffer::DepthStencil, pixelSize, sampleCount)); - if (!depthStencil->create()) { - qWarning("Failed to build depth-stencil buffer for QQuickRenderTarget"); - return false; + // 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) { + depthStencil.reset(rhi->newTexture(QRhiTexture::D24S8, 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()); @@ -685,37 +1492,222 @@ static bool createRhiRenderTarget(const QRhiColorAttachment &colorAttachment, return false; } - dst->renderTarget = rt.release(); - dst->rpDesc = rp.release(); - dst->depthStencil = depthStencil.release(); - dst->owns = true; // ownership of the native resource itself is not transferred but the QRhi objects are on us now + 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->renderTarget = nullptr; - dst->paintDevice = nullptr; - dst->owns = false; + dst->implicitBuffers.reset(rhi); return true; case Type::NativeTexture: { - const auto format = u.nativeTexture.rhiFormat == QRhiTexture::UnknownFormat ? QRhiTexture::RGBA8 - : QRhiTexture::Format(u.nativeTexture.rhiFormat); - const auto flags = QRhiTexture::RenderTarget | QRhiTexture::Flags(u.nativeTexture.rhiFlags); - std::unique_ptr<QRhiTexture> texture(rhi->newTexture(format, pixelSize, sampleCount, flags)); - if (!texture->createFrom({ u.nativeTexture.object, u.nativeTexture.layout })) { + 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; } - QRhiColorAttachment att(texture.get()); - if (!createRhiRenderTarget(att, pixelSize, sampleCount, rhi, dst)) + 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; - dst->texture = texture.release(); + } + if (!createRhiRenderTargetMultiView(texture.get(), customDepthTexture, pixelSize, arraySize, sampleCount, multisampleResolve, rhi, dst)) + return false; + dst->res.texture = texture.release(); } return true; @@ -726,27 +1718,59 @@ bool QQuickRenderTargetPrivate::resolve(QRhi *rhi, QQuickWindowRenderTarget *dst qWarning("Failed to build wrapper renderbuffer for QQuickRenderTarget"); return false; } - QRhiColorAttachment att(renderbuffer.get()); - if (!createRhiRenderTarget(att, pixelSize, sampleCount, rhi, dst)) + if (customDepthTexture) + qWarning("Custom depth texture is not supported with renderbuffers in QQuickRenderTarget"); + if (!createRhiRenderTargetWithRenderBuffer(renderbuffer.get(), pixelSize, sampleCount, rhi, dst)) return false; - dst->renderBuffer = renderbuffer.release(); + dst->res.renderBuffer = renderbuffer.release(); } return true; case Type::RhiRenderTarget: - dst->renderTarget = u.rhiRt; - dst->rpDesc = u.rhiRt->renderPassDescriptor(); // just for QQuickWindowRenderTarget::reset() - dst->owns = false; + 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->paintDevice = u.paintDevice; - dst->owns = false; + dst->implicitBuffers.reset(rhi); + dst->sw.paintDevice = u.paintDevice; + dst->sw.owns = false; return true; - - default: - break; } - return false; + + Q_UNREACHABLE_RETURN(false); } QT_END_NAMESPACE |