summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLaszlo Agocs <laszlo.agocs@qt.io>2024-03-20 13:57:18 +0100
committerLaszlo Agocs <laszlo.agocs@qt.io>2024-04-03 22:14:21 +0200
commite36c1a8d840ee47be0d4fbf8069fda536042a53c (patch)
tree70d13c05607710b56fadb4f853412b1e51436ade
parentfaaee821297ad801f24cab0cdddd0d068595686d (diff)
rhi: Add support for resolving depth-stencil
Add setDepthResolveTexture(). Should work similarly to the color attachments' resolveTexture, but for depth or depth-stencil. However, this is another fragmented feature. - D3D11/12: Not supported. AFAICS multisample resolve (ResolveSubresource) is just not supported for depth or depth-stencil formats. - Vulkan: Not supported with Vulkan 1.0. Supported with Vulkan 1.1 and the two extensions. (VK_KHR_depth_stencil_resolve which in turn requires VK_KHR_create_renderpass2 since the 1.0 structs are not extensible, so now need to use VkRenderPassCreateInfo2 and all the '2' structs) In Vulkan 1.2 the above are in core, without the KHR suffix, but we cannot just use that because our main target, the Quest 3 (Android) is Vulkan 1.1. So 1.2 and up is ignored for now and we always look for the 1.1 KHR extensions. The depth resolve filter is forced for SAMPLE_0. AVG seems to be supported on desktop (NVIDIA) at least, but that's not guaranteed, so would need physical device support checks. On the Quest 3 it does not seem to be supported. And in any case, other APIs such as Metal do not have an AVG filter mode at all, so just use SAMPLE_0 always. - OpenGL (not ES): Should work, both when the multisample data is a renderbuffer and a texture. Relies on glBlitFramebuffer with filter NEAREST. What it does internally, with regards to the depth/stencil resolve mode, is not under our control. - OpenGL ES: Should work when the multisample buffer is a texture. But it will not work when a multisample renderbuffer (setDepthStencilBuffer, not setDepthTexture) is used because the GLES-only multisample extensions (GL_EXT_multisampled_render_to_texture, GL_OVR_multiview_multisampled_render_to_texture, which we prefer over the explicit resolve-based approach) work with textures only. - Metal: Should work. Task-number: QTBUG-122292 Change-Id: Ifa7ca5e1be78227bd6bd7546dde3a62f7fdbc95e Reviewed-by: Andy Nichols <andy.nichols@qt.io>
-rw-r--r--src/gui/rhi/qrhi.cpp52
-rw-r--r--src/gui/rhi/qrhi.h7
-rw-r--r--src/gui/rhi/qrhid3d11.cpp4
-rw-r--r--src/gui/rhi/qrhid3d12.cpp7
-rw-r--r--src/gui/rhi/qrhigles2.cpp246
-rw-r--r--src/gui/rhi/qrhigles2_p.h2
-rw-r--r--src/gui/rhi/qrhimetal.mm21
-rw-r--r--src/gui/rhi/qrhivulkan.cpp307
-rw-r--r--src/gui/rhi/qrhivulkan_p.h14
-rw-r--r--tests/auto/gui/rhi/qrhi/tst_qrhi.cpp3
-rw-r--r--tests/manual/rhi/CMakeLists.txt1
-rw-r--r--tests/manual/rhi/msaatextureresolve/CMakeLists.txt41
-rw-r--r--tests/manual/rhi/msaatextureresolve/msaatextureresolve.cpp235
13 files changed, 865 insertions, 75 deletions
diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp
index 25df9c6003..a39709c726 100644
--- a/src/gui/rhi/qrhi.cpp
+++ b/src/gui/rhi/qrhi.cpp
@@ -1022,6 +1022,20 @@ Q_LOGGING_CATEGORY(QRHI_LOG_INFO, "qt.rhi.general")
\l{https://microsoft.github.io/DirectX-Specs/d3d/RelaxedCasting.html} (and
note that QRhi always uses fully typed formats for textures.) This enum
value has been introduced in Qt 6.8.
+
+ \value ResolveDepthStencil Indicates that resolving a multisample depth or
+ depth-stencil texture is supported. Otherwise,
+ \l{QRhiTextureRenderTargetDescription::setDepthResolveTexture()}{setting a
+ depth resolve texture} is not functional and must be avoided. Direct 3D 11
+ and 12 have no support for resolving depth/depth-stencil formats, and
+ therefore this feature will never be supported with those. Vulkan 1.0 has no
+ API to request resolving a depth-stencil attachment. Therefore, with Vulkan
+ this feature will only be supported with Vulkan 1.2 and up, and on 1.1
+ implementations with the appropriate extensions present. This feature is
+ provided for the rare case when resolving into a non-multisample depth
+ texture becomes necessary, for example when rendering into an
+ OpenXR-provided depth texture (XR_KHR_composition_layer_depth). This enum
+ value has been introduced in Qt 6.8.
*/
/*!
@@ -2693,6 +2707,39 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh
*/
/*!
+ \fn QRhiTexture *QRhiTextureRenderTargetDescription::depthResolveTexture() const
+
+ \return the texture to which a multisample depth (or depth-stencil) texture
+ (or texture array) is resolved to. \nullptr if there is none, which is the
+ most common case.
+
+ \since 6.8
+ \sa QRhiColorAttachment::resolveTexture(), depthTexture()
+ */
+
+/*!
+ \fn void QRhiTextureRenderTargetDescription::setDepthResolveTexture(QRhiTexture *tex)
+
+ Sets the depth (or depth-stencil) resolve texture \a tex.
+
+ \a tex is expected to be a 2D texture or a 2D texture array with a format
+ matching the texture set via setDepthTexture().
+
+ \note Resolving depth (or depth-stencil) data is only functional when the
+ \l ResolveDepthStencil feature is reported as supported at run time. Support
+ for depth-stencil resolve is not universally available among the graphics
+ APIs. Designs assuming unconditional availability of depth-stencil resolve
+ are therefore non-portable, and should be avoided.
+
+ \note As an additional limitation for OpenGL ES in particular, setting a
+ depth resolve texture may only be functional in combination with
+ setDepthTexture(), not with setDepthStencilBuffer().
+
+ \since 6.8
+ \sa QRhiColorAttachment::setResolveTexture(), setDepthTexture()
+ */
+
+/*!
\class QRhiTextureSubresourceUploadDescription
\inmodule QtGui
\since 6.6
@@ -5088,8 +5135,9 @@ QRhiResource::Type QRhiSwapChainRenderTarget::resourceType() const
\value DoNotStoreDepthStencilContents Indicates that the contents of the
depth texture does not need to be written out. Relevant only when a
QRhiTexture, not QRhiRenderBuffer, is used as the depth-stencil buffer,
- because for QRhiRenderBuffer this is implicit. This enum value is introduced
- in Qt 6.8.
+ because for QRhiRenderBuffer this is implicit. When a depthResolveTexture is
+ set, the flag is not relevant, because the behavior is then as if the flag
+ was set. This enum value is introduced in Qt 6.8.
*/
/*!
diff --git a/src/gui/rhi/qrhi.h b/src/gui/rhi/qrhi.h
index a9836bf892..d20b7e00d1 100644
--- a/src/gui/rhi/qrhi.h
+++ b/src/gui/rhi/qrhi.h
@@ -642,10 +642,14 @@ public:
QRhiTexture *depthTexture() const { return m_depthTexture; }
void setDepthTexture(QRhiTexture *texture) { m_depthTexture = texture; }
+ QRhiTexture *depthResolveTexture() const { return m_depthResolveTexture; }
+ void setDepthResolveTexture(QRhiTexture *tex) { m_depthResolveTexture = tex; }
+
private:
QVarLengthArray<QRhiColorAttachment, 8> m_colorAttachments;
QRhiRenderBuffer *m_depthStencilBuffer = nullptr;
QRhiTexture *m_depthTexture = nullptr;
+ QRhiTexture *m_depthResolveTexture = nullptr;
};
class Q_GUI_EXPORT QRhiTextureSubresourceUploadDescription
@@ -1866,7 +1870,8 @@ public:
RenderToOneDimensionalTexture,
ThreeDimensionalTextureMipmaps,
MultiView,
- TextureViewFormat
+ TextureViewFormat,
+ ResolveDepthStencil
};
enum BeginFrameFlag {
diff --git a/src/gui/rhi/qrhid3d11.cpp b/src/gui/rhi/qrhid3d11.cpp
index 26e1f45dc7..ba2b783b0e 100644
--- a/src/gui/rhi/qrhid3d11.cpp
+++ b/src/gui/rhi/qrhid3d11.cpp
@@ -600,6 +600,8 @@ bool QRhiD3D11::isFeatureSupported(QRhi::Feature feature) const
return false;
case QRhi::TextureViewFormat:
return false; // because we use fully typed formats for textures and relaxed casting is a D3D12 thing
+ case QRhi::ResolveDepthStencil:
+ return false;
default:
Q_UNREACHABLE();
return false;
@@ -2119,6 +2121,8 @@ void QRhiD3D11::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
cmd.args.resolveSubRes.srcSubRes = D3D11CalcSubresource(0, UINT(colorAtt.layer()), 1);
cmd.args.resolveSubRes.format = dstTexD->dxgiFormat;
}
+ if (rtTex->m_desc.depthResolveTexture())
+ qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
}
cbD->recordingPass = QD3D11CommandBuffer::NoPass;
diff --git a/src/gui/rhi/qrhid3d12.cpp b/src/gui/rhi/qrhid3d12.cpp
index 08224984d2..d77e2db091 100644
--- a/src/gui/rhi/qrhid3d12.cpp
+++ b/src/gui/rhi/qrhid3d12.cpp
@@ -712,6 +712,10 @@ bool QRhiD3D12::isFeatureSupported(QRhi::Feature feature) const
return caps.multiView;
case QRhi::TextureViewFormat:
return caps.textureViewFormat;
+ case QRhi::ResolveDepthStencil:
+ // there is no Multisample Resolve support for depth/stencil formats
+ // https://learn.microsoft.com/en-us/windows/win32/direct3ddxgi/hardware-support-for-direct3d-12-1-formats
+ return false;
}
return false;
}
@@ -1960,7 +1964,8 @@ void QRhiD3D12::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
dstTexD->dxgiFormat);
}
}
-
+ if (rtTex->m_desc.depthResolveTexture())
+ qWarning("Resolving multisample depth-stencil buffers is not supported with D3D");
}
cbD->recordingPass = QD3D12CommandBuffer::NoPass;
diff --git a/src/gui/rhi/qrhigles2.cpp b/src/gui/rhi/qrhigles2.cpp
index d86b5368c4..62830c291d 100644
--- a/src/gui/rhi/qrhigles2.cpp
+++ b/src/gui/rhi/qrhigles2.cpp
@@ -1457,6 +1457,8 @@ bool QRhiGles2::isFeatureSupported(QRhi::Feature feature) const
return caps.multiView && caps.maxTextureArraySize > 0;
case QRhi::TextureViewFormat:
return false;
+ case QRhi::ResolveDepthStencil:
+ return true;
default:
Q_UNREACHABLE_RETURN(false);
}
@@ -3580,21 +3582,47 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
GLuint fbo[2];
f->glGenFramebuffers(2, fbo);
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
- f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ const bool ds = cmd.args.blitFromRenderbuffer.isDepthStencil;
+ if (ds) {
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ } else {
+ f->glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ GL_RENDERBUFFER, cmd.args.blitFromRenderbuffer.renderbuffer);
+ }
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
if (cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_3D || cmd.args.blitFromRenderbuffer.target == GL_TEXTURE_2D_ARRAY) {
- f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- cmd.args.blitFromRenderbuffer.dstTexture,
- cmd.args.blitFromRenderbuffer.dstLevel,
- cmd.args.blitFromRenderbuffer.dstLayer);
+ if (ds) {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ cmd.args.blitFromRenderbuffer.dstTexture,
+ cmd.args.blitFromRenderbuffer.dstLevel,
+ cmd.args.blitFromRenderbuffer.dstLayer);
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ cmd.args.blitFromRenderbuffer.dstTexture,
+ cmd.args.blitFromRenderbuffer.dstLevel,
+ cmd.args.blitFromRenderbuffer.dstLayer);
+ } else {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.blitFromRenderbuffer.dstTexture,
+ cmd.args.blitFromRenderbuffer.dstLevel,
+ cmd.args.blitFromRenderbuffer.dstLayer);
+ }
} else {
- f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRenderbuffer.target,
- cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ if (ds) {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromRenderbuffer.target,
+ cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromRenderbuffer.target,
+ cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ } else {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromRenderbuffer.target,
+ cmd.args.blitFromRenderbuffer.dstTexture, cmd.args.blitFromRenderbuffer.dstLevel);
+ }
}
f->glBlitFramebuffer(0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h,
0, 0, cmd.args.blitFromRenderbuffer.w, cmd.args.blitFromRenderbuffer.h,
- GL_COLOR_BUFFER_BIT,
+ ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT,
GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
f->glDeleteFramebuffers(2, fbo);
@@ -3609,28 +3637,65 @@ void QRhiGles2::executeCommandBuffer(QRhiCommandBuffer *cb)
GLuint fbo[2];
f->glGenFramebuffers(2, fbo);
f->glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]);
+ const bool ds = cmd.args.blitFromTexture.isDepthStencil;
if (cmd.args.blitFromTexture.srcTarget == GL_TEXTURE_2D_MULTISAMPLE_ARRAY) {
- f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- cmd.args.blitFromTexture.srcTexture,
- cmd.args.blitFromTexture.srcLevel,
- cmd.args.blitFromTexture.srcLayer);
+ if (ds) {
+ f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ cmd.args.blitFromTexture.srcTexture,
+ cmd.args.blitFromTexture.srcLevel,
+ cmd.args.blitFromTexture.srcLayer);
+ f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ cmd.args.blitFromTexture.srcTexture,
+ cmd.args.blitFromTexture.srcLevel,
+ cmd.args.blitFromTexture.srcLayer);
+ } else {
+ f->glFramebufferTextureLayer(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.blitFromTexture.srcTexture,
+ cmd.args.blitFromTexture.srcLevel,
+ cmd.args.blitFromTexture.srcLayer);
+ }
} else {
- f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.srcTarget,
- cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ if (ds) {
+ f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromTexture.srcTarget,
+ cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromTexture.srcTarget,
+ cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ } else {
+ f->glFramebufferTexture2D(GL_READ_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.srcTarget,
+ cmd.args.blitFromTexture.srcTexture, cmd.args.blitFromTexture.srcLevel);
+ }
}
f->glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]);
if (cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_3D || cmd.args.blitFromTexture.dstTarget == GL_TEXTURE_2D_ARRAY) {
- f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
- cmd.args.blitFromTexture.dstTexture,
- cmd.args.blitFromTexture.dstLevel,
- cmd.args.blitFromTexture.dstLayer);
+ if (ds) {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT,
+ cmd.args.blitFromTexture.dstTexture,
+ cmd.args.blitFromTexture.dstLevel,
+ cmd.args.blitFromTexture.dstLayer);
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT,
+ cmd.args.blitFromTexture.dstTexture,
+ cmd.args.blitFromTexture.dstLevel,
+ cmd.args.blitFromTexture.dstLayer);
+ } else {
+ f->glFramebufferTextureLayer(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0,
+ cmd.args.blitFromTexture.dstTexture,
+ cmd.args.blitFromTexture.dstLevel,
+ cmd.args.blitFromTexture.dstLayer);
+ }
} else {
- f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.dstTarget,
- cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel);
+ if (ds) {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, cmd.args.blitFromTexture.dstTarget,
+ cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel);
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, cmd.args.blitFromTexture.dstTarget,
+ cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel);
+ } else {
+ f->glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, cmd.args.blitFromTexture.dstTarget,
+ cmd.args.blitFromTexture.dstTexture, cmd.args.blitFromTexture.dstLevel);
+ }
}
f->glBlitFramebuffer(0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h,
0, 0, cmd.args.blitFromTexture.w, cmd.args.blitFromTexture.h,
- GL_COLOR_BUFFER_BIT,
+ ds ? GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT : GL_COLOR_BUFFER_BIT,
GL_NEAREST); // Qt 5 used Nearest when resolving samples, stick to that
f->glBindFramebuffer(GL_FRAMEBUFFER, ctx->defaultFramebufferObject());
f->glDeleteFramebuffers(2, fbo);
@@ -4514,6 +4579,7 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
const bool hasZ = resolveTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional)
|| resolveTexD->m_flags.testFlag(QRhiTexture::TextureArray);
cmd.args.blitFromRenderbuffer.dstLayer = hasZ ? colorAtt.resolveLayer() : 0;
+ cmd.args.blitFromRenderbuffer.isDepthStencil = false;
} else if (caps.glesMultisampleRenderToTexture) {
// Nothing to do, resolving into colorAtt.resolveTexture() is automatic,
// colorAtt.texture() is in fact not used for anything.
@@ -4550,12 +4616,53 @@ void QRhiGles2::endPass(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resource
cmd.args.blitFromTexture.dstLayer = 0;
if (resolveTexD->m_flags.testFlag(QRhiTexture::ThreeDimensional) || resolveTexD->m_flags.testFlag(QRhiTexture::TextureArray))
cmd.args.blitFromTexture.dstLayer = dstLayer;
+ cmd.args.blitFromTexture.isDepthStencil = false;
+ }
+ }
+ }
+
+ if (rtTex->m_desc.depthResolveTexture()) {
+ QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthResolveTexture());
+ const QSize size = depthResolveTexD->pixelSize();
+ if (rtTex->m_desc.depthStencilBuffer()) {
+ QGles2RenderBuffer *rbD = QRHI_RES(QGles2RenderBuffer, rtTex->m_desc.depthStencilBuffer());
+ QGles2CommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromRenderbuffer;
+ cmd.args.blitFromRenderbuffer.renderbuffer = rbD->renderbuffer;
+ cmd.args.blitFromRenderbuffer.w = size.width();
+ cmd.args.blitFromRenderbuffer.h = size.height();
+ cmd.args.blitFromRenderbuffer.target = depthResolveTexD->target;
+ cmd.args.blitFromRenderbuffer.dstTexture = depthResolveTexD->texture;
+ cmd.args.blitFromRenderbuffer.dstLevel = 0;
+ cmd.args.blitFromRenderbuffer.dstLayer = 0;
+ cmd.args.blitFromRenderbuffer.isDepthStencil = true;
+ } else if (caps.glesMultisampleRenderToTexture) {
+ // Nothing to do, resolving into depthResolveTexture() is automatic.
+ } else {
+ QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, rtTex->m_desc.depthTexture());
+ const int resolveCount = depthTexD->arraySize() >= 2 ? depthTexD->arraySize() : 1;
+ for (int resolveIdx = 0; resolveIdx < resolveCount; ++resolveIdx) {
+ QGles2CommandBuffer::Command &cmd(cbD->commands.get());
+ cmd.cmd = QGles2CommandBuffer::Command::BlitFromTexture;
+ cmd.args.blitFromTexture.srcTarget = depthTexD->target;
+ cmd.args.blitFromTexture.srcTexture = depthTexD->texture;
+ cmd.args.blitFromTexture.srcLevel = 0;
+ cmd.args.blitFromTexture.srcLayer = resolveIdx;
+ cmd.args.blitFromTexture.w = size.width();
+ cmd.args.blitFromTexture.h = size.height();
+ cmd.args.blitFromTexture.dstTarget = depthResolveTexD->target;
+ cmd.args.blitFromTexture.dstTexture = depthResolveTexD->texture;
+ cmd.args.blitFromTexture.dstLevel = 0;
+ cmd.args.blitFromTexture.dstLayer = resolveIdx;
+ cmd.args.blitFromTexture.isDepthStencil = true;
}
}
}
- const bool mayDiscardDepthStencil = rtTex->m_desc.depthStencilBuffer()
- || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::DoNotStoreDepthStencilContents));
+ const bool mayDiscardDepthStencil =
+ (rtTex->m_desc.depthStencilBuffer()
+ || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::DoNotStoreDepthStencilContents)))
+ && !rtTex->m_desc.depthResolveTexture();
if (mayDiscardDepthStencil) {
QGles2CommandBuffer::Command &cmd(cbD->commands.get());
cmd.cmd = QGles2CommandBuffer::Command::InvalidateFramebuffer;
@@ -5923,11 +6030,24 @@ bool QGles2TextureRenderTarget::create()
} else {
QGles2Texture *depthTexD = QRHI_RES(QGles2Texture, m_desc.depthTexture());
if (multiViewCount < 2) {
- rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target,
- depthTexD->texture, 0);
- if (rhiD->isStencilSupportingFormat(depthTexD->format())) {
- rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->target,
+ if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultisampleRenderToTexture && m_desc.depthResolveTexture()) {
+ // Special path for GLES and
+ // GL_EXT_multisampled_render_to_texture, for depth-stencil.
+ // Relevant only when depthResolveTexture is set.
+ QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture());
+ rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthResolveTexD->target,
+ depthResolveTexD->texture, 0, depthTexD->sampleCount());
+ if (rhiD->isStencilSupportingFormat(depthResolveTexD->format())) {
+ rhiD->glFramebufferTexture2DMultisampleEXT(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthResolveTexD->target,
+ depthResolveTexD->texture, 0, depthTexD->sampleCount());
+ }
+ } else {
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, depthTexD->target,
depthTexD->texture, 0);
+ if (rhiD->isStencilSupportingFormat(depthTexD->format())) {
+ rhiD->f->glFramebufferTexture2D(GL_FRAMEBUFFER, GL_STENCIL_ATTACHMENT, depthTexD->target,
+ depthTexD->texture, 0);
+ }
}
} else {
if (depthTexD->sampleCount() > 1 && rhiD->caps.glesMultiviewMultisampleRenderToTexture) {
@@ -5944,37 +6064,55 @@ bool QGles2TextureRenderTarget::create()
// multisample-multiview-auto-resolving version (which in
// turn is not supported on desktop GL e.g. by NVIDIA), too
// bad we have a multisample depth texture array here as
- // every other API out there requires that. So create a
- // temporary one ignoring what the user has already created.
+ // every other API out there requires that. So, in absence
+ // of a depthResolveTexture, create a temporary one ignoring
+ // what the user has already created.
//
- // (also, why on Earth would we want to waste time on
- // resolving the throwaway depth-stencil buffer? Hopefully
- // the invalidation at the end of the pass avoids that...)
- if (!m_flags.testFlag(DoNotStoreDepthStencilContents)) {
+ if (!m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture()) {
qWarning("Attempted to create a multiview+multisample QRhiTextureRenderTarget, but DoNotStoreDepthStencilContents was not set."
" This path has no choice but to behave as if DoNotStoreDepthStencilContents was set, because QRhi is forced to create"
- " a throwaway non-multisample depth texture here. Set the flag to silence this warning.");
+ " a throwaway non-multisample depth texture here. Set the flag to silence this warning, or set a depthResolveTexture.");
}
- if (!nonMsaaThrowawayDepthTexture) {
- rhiD->f->glGenTextures(1, &nonMsaaThrowawayDepthTexture);
- rhiD->f->glBindTexture(GL_TEXTURE_2D_ARRAY, nonMsaaThrowawayDepthTexture);
- rhiD->f->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH24_STENCIL8,
- depthTexD->pixelSize().width(), depthTexD->pixelSize().height(), multiViewCount);
+ if (m_desc.depthResolveTexture()) {
+ QGles2Texture *depthResolveTexD = QRHI_RES(QGles2Texture, m_desc.depthResolveTexture());
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_DEPTH_ATTACHMENT,
+ depthResolveTexD->texture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ if (rhiD->isStencilSupportingFormat(depthResolveTexD->format())) {
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_STENCIL_ATTACHMENT,
+ depthResolveTexD->texture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ }
+ } else {
+ if (!nonMsaaThrowawayDepthTexture) {
+ rhiD->f->glGenTextures(1, &nonMsaaThrowawayDepthTexture);
+ rhiD->f->glBindTexture(GL_TEXTURE_2D_ARRAY, nonMsaaThrowawayDepthTexture);
+ rhiD->f->glTexStorage3D(GL_TEXTURE_2D_ARRAY, 1, GL_DEPTH24_STENCIL8,
+ depthTexD->pixelSize().width(), depthTexD->pixelSize().height(), multiViewCount);
+ }
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_DEPTH_ATTACHMENT,
+ nonMsaaThrowawayDepthTexture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
+ rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
+ GL_STENCIL_ATTACHMENT,
+ nonMsaaThrowawayDepthTexture,
+ 0,
+ depthTexD->sampleCount(),
+ 0,
+ multiViewCount);
}
- rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
- GL_DEPTH_ATTACHMENT,
- nonMsaaThrowawayDepthTexture,
- 0,
- depthTexD->sampleCount(),
- 0,
- multiViewCount);
- rhiD->glFramebufferTextureMultisampleMultiviewOVR(GL_FRAMEBUFFER,
- GL_STENCIL_ATTACHMENT,
- nonMsaaThrowawayDepthTexture,
- 0,
- depthTexD->sampleCount(),
- 0,
- multiViewCount);
} else {
// The depth texture here must be an array with at least
// multiViewCount elements, and the format should be D24 or D32F
diff --git a/src/gui/rhi/qrhigles2_p.h b/src/gui/rhi/qrhigles2_p.h
index 1365d439c2..4305186c02 100644
--- a/src/gui/rhi/qrhigles2_p.h
+++ b/src/gui/rhi/qrhigles2_p.h
@@ -507,6 +507,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
GLuint dstTexture;
int dstLevel;
int dstLayer;
+ bool isDepthStencil;
} blitFromRenderbuffer;
struct {
GLenum srcTarget;
@@ -519,6 +520,7 @@ struct QGles2CommandBuffer : public QRhiCommandBuffer
GLuint dstTexture;
int dstLevel;
int dstLayer;
+ bool isDepthStencil;
} blitFromTexture;
struct {
GLenum target;
diff --git a/src/gui/rhi/qrhimetal.mm b/src/gui/rhi/qrhimetal.mm
index 2cdac0531a..b69bc17f51 100644
--- a/src/gui/rhi/qrhimetal.mm
+++ b/src/gui/rhi/qrhimetal.mm
@@ -363,6 +363,7 @@ struct QMetalRenderTargetData
struct {
ColorAtt colorAtt[QMetalRenderPassDescriptor::MAX_COLOR_ATTACHMENTS];
id<MTLTexture> dsTex = nil;
+ id<MTLTexture> dsResolveTex = nil;
bool hasStencil = false;
bool depthNeedsStore = false;
bool preserveColor = false;
@@ -854,6 +855,8 @@ bool QRhiMetal::isFeatureSupported(QRhi::Feature feature) const
return caps.multiView;
case QRhi::TextureViewFormat:
return false;
+ case QRhi::ResolveDepthStencil:
+ return true;
default:
Q_UNREACHABLE();
return false;
@@ -2368,6 +2371,7 @@ QRhi::FrameOpResult QRhiMetal::beginFrame(QRhiSwapChain *swapChain, QRhi::BeginF
swapChainD->rtWrapper.d->fb.colorAtt[0] = colorAtt;
swapChainD->rtWrapper.d->fb.dsTex = swapChainD->ds ? swapChainD->ds->d->tex : nil;
+ swapChainD->rtWrapper.d->fb.dsResolveTex = nil;
swapChainD->rtWrapper.d->fb.hasStencil = swapChainD->ds ? true : false;
swapChainD->rtWrapper.d->fb.depthNeedsStore = false;
@@ -2979,6 +2983,8 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
QRHI_RES(QMetalRenderBuffer, rtTex->m_desc.depthStencilBuffer())->lastActiveFrameSlot = currentFrameSlot;
if (rtTex->m_desc.depthTexture())
QRHI_RES(QMetalTexture, rtTex->m_desc.depthTexture())->lastActiveFrameSlot = currentFrameSlot;
+ if (rtTex->m_desc.depthResolveTexture())
+ QRHI_RES(QMetalTexture, rtTex->m_desc.depthResolveTexture())->lastActiveFrameSlot = currentFrameSlot;
}
break;
default:
@@ -3006,6 +3012,15 @@ void QRhiMetal::beginPass(QRhiCommandBuffer *cb,
cbD->d->currentPassRpDesc.stencilAttachment.texture = rtD->fb.hasStencil ? rtD->fb.dsTex : nil;
if (rtD->fb.depthNeedsStore) // Depth/Stencil is set to DontCare by default, override if needed
cbD->d->currentPassRpDesc.depthAttachment.storeAction = MTLStoreActionStore;
+ if (rtD->fb.dsResolveTex) {
+ cbD->d->currentPassRpDesc.depthAttachment.storeAction = rtD->fb.depthNeedsStore ? MTLStoreActionStoreAndMultisampleResolve
+ : MTLStoreActionMultisampleResolve;
+ cbD->d->currentPassRpDesc.depthAttachment.resolveTexture = rtD->fb.dsResolveTex;
+ if (rtD->fb.hasStencil) {
+ cbD->d->currentPassRpDesc.stencilAttachment.resolveTexture = rtD->fb.dsResolveTex;
+ cbD->d->currentPassRpDesc.stencilAttachment.storeAction = cbD->d->currentPassRpDesc.depthAttachment.storeAction;
+ }
+ }
}
cbD->d->currentRenderPassEncoder = [cbD->d->cb renderCommandEncoderWithDescriptor: cbD->d->currentPassRpDesc];
@@ -4229,7 +4244,7 @@ bool QMetalTextureRenderTarget::create()
QMetalTexture *depthTexD = QRHI_RES(QMetalTexture, m_desc.depthTexture());
d->fb.dsTex = depthTexD->d->tex;
d->fb.hasStencil = rhiD->isStencilSupportingFormat(depthTexD->format());
- d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents);
+ d->fb.depthNeedsStore = !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture();
d->fb.preserveDs = m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents);
if (d->colorAttCount == 0) {
d->pixelSize = depthTexD->pixelSize();
@@ -4246,6 +4261,10 @@ bool QMetalTextureRenderTarget::create()
d->sampleCount = depthRbD->samples;
}
}
+ if (m_desc.depthResolveTexture()) {
+ QMetalTexture *depthResolveTexD = QRHI_RES(QMetalTexture, m_desc.depthResolveTexture());
+ d->fb.dsResolveTex = depthResolveTexD->d->tex;
+ }
d->dsAttCount = 1;
} else {
d->dsAttCount = 0;
diff --git a/src/gui/rhi/qrhivulkan.cpp b/src/gui/rhi/qrhivulkan.cpp
index da8c8fc52a..f0b51146cc 100644
--- a/src/gui/rhi/qrhivulkan.cpp
+++ b/src/gui/rhi/qrhivulkan.cpp
@@ -334,7 +334,9 @@ QByteArrayList QRhiVulkanInitParams::preferredExtensionsForImportedDevice()
{
return {
QByteArrayLiteral("VK_KHR_swapchain"),
- QByteArrayLiteral("VK_EXT_vertex_attribute_divisor")
+ QByteArrayLiteral("VK_EXT_vertex_attribute_divisor"),
+ QByteArrayLiteral("VK_KHR_create_renderpass2"),
+ QByteArrayLiteral("VK_KHR_depth_stencil_resolve")
};
}
@@ -428,6 +430,8 @@ bool QRhiVulkan::create(QRhi::Flags flags)
for (const char *ext : inst->extensions())
qCDebug(QRHI_LOG_INFO, " %s", ext);
}
+
+ caps = {};
caps.debugUtils = inst->extensions().contains(QByteArrayLiteral("VK_EXT_debug_utils"));
QList<VkQueueFamilyProperties> queueFamilyProps;
@@ -659,7 +663,6 @@ bool QRhiVulkan::create(QRhi::Flags flags)
}
}
- caps.vertexAttribDivisor = false;
#ifdef VK_EXT_vertex_attribute_divisor
if (devExts.contains(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME)) {
if (hasPhysDevProp2) {
@@ -669,6 +672,20 @@ bool QRhiVulkan::create(QRhi::Flags flags)
}
#endif
+#ifdef VK_KHR_create_renderpass2
+ if (devExts.contains(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME)) {
+ requestedDevExts.append(VK_KHR_CREATE_RENDERPASS_2_EXTENSION_NAME);
+ caps.renderPass2KHR = true;
+ }
+#endif
+
+#ifdef VK_KHR_depth_stencil_resolve
+ if (devExts.contains(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME)) {
+ requestedDevExts.append(VK_KHR_DEPTH_STENCIL_RESOLVE_EXTENSION_NAME);
+ caps.depthStencilResolveKHR = true;
+ }
+#endif
+
for (const QByteArray &ext : requestedDeviceExtensions) {
if (!ext.isEmpty() && !requestedDevExts.contains(ext)) {
if (devExts.contains(ext)) {
@@ -750,6 +767,13 @@ bool QRhiVulkan::create(QRhi::Flags flags)
}
} else {
qCDebug(QRHI_LOG_INFO, "Using imported device %p", dev);
+
+ // Here we have no way to tell if the extensions got enabled or not.
+ // Pretend it's all there and supported. If getProcAddress fails, we'll
+ // handle that gracefully.
+ caps.vertexAttribDivisor = true;
+ caps.renderPass2KHR = true;
+ caps.depthStencilResolveKHR = true;
}
vkGetPhysicalDeviceSurfaceCapabilitiesKHR = reinterpret_cast<PFN_vkGetPhysicalDeviceSurfaceCapabilitiesKHR>(
@@ -807,6 +831,18 @@ bool QRhiVulkan::create(QRhi::Flags flags)
caps.multiView = multiviewFeaturesIfApi11.multiview;
#endif
+ // With Vulkan 1.2 renderpass2 and depth_stencil_resolve are core, but we
+ // have to support the case of 1.1 + extensions, in particular for the Quest
+ // 3 (Android, Vulkan 1.1 at the time of writing). Therefore, always rely on
+ // the KHR extension for now.
+#ifdef VK_KHR_create_renderpass2
+ if (caps.renderPass2KHR) {
+ vkCreateRenderPass2KHR = reinterpret_cast<PFN_vkCreateRenderPass2KHR>(f->vkGetDeviceProcAddr(dev, "vkCreateRenderPass2KHR"));
+ if (!vkCreateRenderPass2KHR) // handle it gracefully, the caps flag may be incorrect when using an imported VkDevice
+ caps.renderPass2KHR = false;
+ }
+#endif
+
if (!importedAllocator) {
VmaVulkanFunctions funcs = {};
funcs.vkGetInstanceProcAddr = wrap_vkGetInstanceProcAddr;
@@ -1331,6 +1367,7 @@ bool QRhiVulkan::createDefaultRenderPass(QVkRenderPassDescriptor *rpD, bool hasD
rpD->colorRefs.append({ 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL });
rpD->hasDepthStencil = hasDepthStencil;
+ rpD->hasDepthStencilResolve = false;
rpD->multiViewCount = 0;
if (hasDepthStencil) {
@@ -1434,6 +1471,132 @@ struct MultiViewRenderPassSetupHelper
#endif
};
+#ifdef VK_KHR_create_renderpass2
+// Effectively converts a VkRenderPassCreateInfo into a VkRenderPassCreateInfo2,
+// adding depth-stencil resolve support. Assumes a single subpass and no subpass
+// dependencies.
+struct RenderPass2SetupHelper
+{
+ bool prepare(VkRenderPassCreateInfo2 *rpInfo2, const VkRenderPassCreateInfo *rpInfo, const QVkRenderPassDescriptor *rpD, int multiViewCount) {
+ *rpInfo2 = {};
+
+ viewMask = 0;
+ if (multiViewCount >= 2) {
+ for (uint32_t i = 0; i < uint32_t(multiViewCount); ++i)
+ viewMask |= (1 << i);
+ }
+
+ attDescs2.resize(rpInfo->attachmentCount);
+ for (qsizetype i = 0; i < attDescs2.count(); ++i) {
+ VkAttachmentDescription2KHR &att2(attDescs2[i]);
+ const VkAttachmentDescription &att(rpInfo->pAttachments[i]);
+ att2 = {};
+ att2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_DESCRIPTION_2;
+ att2.flags = att.flags;
+ att2.format = att.format;
+ att2.samples = att.samples;
+ att2.loadOp = att.loadOp;
+ att2.storeOp = att.storeOp;
+ att2.stencilLoadOp = att.stencilLoadOp;
+ att2.stencilStoreOp = att.stencilStoreOp;
+ att2.initialLayout = att.initialLayout;
+ att2.finalLayout = att.finalLayout;
+ }
+
+ attRefs2.clear();
+ subpass2 = {};
+ subpass2.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_2_KHR;
+ const VkSubpassDescription &subpassDesc(rpInfo->pSubpasses[0]);
+ subpass2.flags = subpassDesc.flags;
+ subpass2.pipelineBindPoint = subpassDesc.pipelineBindPoint;
+ if (multiViewCount >= 2)
+ subpass2.viewMask = viewMask;
+
+ // color attachment refs
+ qsizetype startIndex = attRefs2.count();
+ for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) {
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ const VkAttachmentReference &attref(subpassDesc.pColorAttachments[j]);
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = attref.attachment;
+ attref2.layout = attref.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ }
+ subpass2.colorAttachmentCount = subpassDesc.colorAttachmentCount;
+ subpass2.pColorAttachments = attRefs2.constData() + startIndex;
+
+ // color resolve refs
+ if (subpassDesc.pResolveAttachments) {
+ startIndex = attRefs2.count();
+ for (uint32_t j = 0; j < subpassDesc.colorAttachmentCount; ++j) {
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ const VkAttachmentReference &attref(subpassDesc.pResolveAttachments[j]);
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = attref.attachment;
+ attref2.layout = attref.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ }
+ subpass2.pResolveAttachments = attRefs2.constData() + startIndex;
+ }
+
+ // depth-stencil ref
+ if (subpassDesc.pDepthStencilAttachment) {
+ startIndex = attRefs2.count();
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ const VkAttachmentReference &attref(*subpassDesc.pDepthStencilAttachment);
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = attref.attachment;
+ attref2.layout = attref.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ subpass2.pDepthStencilAttachment = attRefs2.constData() + startIndex;
+ }
+
+ // depth-stencil resolve ref
+#ifdef VK_KHR_depth_stencil_resolve
+ dsResolveDesc = {};
+ if (rpD->hasDepthStencilResolve) {
+ startIndex = attRefs2.count();
+ attRefs2.append({});
+ VkAttachmentReference2KHR &attref2(attRefs2.last());
+ attref2.sType = VK_STRUCTURE_TYPE_ATTACHMENT_REFERENCE_2_KHR;
+ attref2.attachment = rpD->dsResolveRef.attachment;
+ attref2.layout = rpD->dsResolveRef.layout;
+ attref2.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ dsResolveDesc.sType = VK_STRUCTURE_TYPE_SUBPASS_DESCRIPTION_DEPTH_STENCIL_RESOLVE_KHR;
+ dsResolveDesc.depthResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
+ dsResolveDesc.stencilResolveMode = VK_RESOLVE_MODE_SAMPLE_ZERO_BIT;
+ dsResolveDesc.pDepthStencilResolveAttachment = attRefs2.constData() + startIndex;
+ subpass2.pNext = &dsResolveDesc;
+ }
+#endif
+
+ rpInfo2->sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO_2_KHR;
+ rpInfo2->pNext = nullptr; // the 1.1 VkRenderPassMultiviewCreateInfo is part of the '2' structs
+ rpInfo2->flags = rpInfo->flags;
+ rpInfo2->attachmentCount = rpInfo->attachmentCount;
+ rpInfo2->pAttachments = attDescs2.constData();
+ rpInfo2->subpassCount = 1;
+ rpInfo2->pSubpasses = &subpass2;
+ if (multiViewCount >= 2) {
+ rpInfo2->correlatedViewMaskCount = 1;
+ rpInfo2->pCorrelatedViewMasks = &viewMask;
+ }
+ return true;
+ }
+
+ QVarLengthArray<VkAttachmentDescription2KHR, 8> attDescs2;
+ QVarLengthArray<VkAttachmentReference2KHR, 8> attRefs2;
+ VkSubpassDescription2KHR subpass2;
+#ifdef VK_KHR_depth_stencil_resolve
+ VkSubpassDescriptionDepthStencilResolveKHR dsResolveDesc;
+#endif
+ uint32_t viewMask;
+};
+#endif // VK_KHR_create_renderpass2
+
bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
const QRhiColorAttachment *firstColorAttachment,
const QRhiColorAttachment *lastColorAttachment,
@@ -1441,9 +1604,10 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
bool preserveDs,
bool storeDs,
QRhiRenderBuffer *depthStencilBuffer,
- QRhiTexture *depthTexture)
+ QRhiTexture *depthTexture,
+ QRhiTexture *depthResolveTexture)
{
- // attachment list layout is color (0-8), ds (0-1), resolve (0-8)
+ // attachment list layout is color (0-8), ds (0-1), resolve (0-8), ds resolve (0-1)
int multiViewCount = 0;
for (auto it = firstColorAttachment; it != lastColorAttachment; ++it) {
@@ -1539,6 +1703,31 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
}
Q_ASSERT(rpD->colorRefs.size() == rpD->resolveRefs.size());
+ rpD->hasDepthStencilResolve = rpD->hasDepthStencil && depthResolveTexture;
+ if (rpD->hasDepthStencilResolve) {
+ QVkTexture *rtexD = QRHI_RES(QVkTexture, depthResolveTexture);
+ if (rtexD->samples > VK_SAMPLE_COUNT_1_BIT)
+ qWarning("Resolving into a multisample depth texture is not supported");
+
+ QVkTexture *texD = QRHI_RES(QVkTexture, depthResolveTexture);
+ if (texD->vkformat != rtexD->vkformat) {
+ qWarning("Multisample resolve between different depth-stencil formats (%d and %d) is not supported.",
+ int(texD->vkformat), int(rtexD->vkformat));
+ }
+
+ VkAttachmentDescription attDesc = {};
+ attDesc.format = rtexD->viewFormat;
+ attDesc.samples = VK_SAMPLE_COUNT_1_BIT;
+ attDesc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; // ignored
+ attDesc.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ attDesc.stencilLoadOp = attDesc.loadOp;
+ attDesc.stencilStoreOp = attDesc.storeOp;
+ attDesc.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ attDesc.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+ rpD->attDescs.append(attDesc);
+ }
+ rpD->dsResolveRef = { uint32_t(rpD->attDescs.size() - 1), VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
+
// rpD->subpassDeps stays empty: don't yet know the correct initial/final
// access and stage stuff for the implicit deps at this point, so leave it
// to the resource tracking and activateTextureRenderTarget() to generate
@@ -1552,10 +1741,31 @@ bool QRhiVulkan::createOffscreenRenderPass(QVkRenderPassDescriptor *rpD,
if (!multiViewHelper.prepare(&rpInfo, multiViewCount, caps.multiView))
return false;
- VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
- if (err != VK_SUCCESS) {
- qWarning("Failed to create renderpass: %d", err);
- return false;
+#ifdef VK_KHR_create_renderpass2
+ if (rpD->hasDepthStencilResolve && caps.renderPass2KHR) {
+ // Use the KHR extension, not the 1.2 core API, in order to support Vulkan 1.1.
+ VkRenderPassCreateInfo2KHR rpInfo2;
+ RenderPass2SetupHelper rp2Helper;
+ if (!rp2Helper.prepare(&rpInfo2, &rpInfo, rpD, multiViewCount))
+ return false;
+
+ VkResult err = vkCreateRenderPass2KHR(dev, &rpInfo2, nullptr, &rpD->rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass (using VkRenderPassCreateInfo2KHR): %d", err);
+ return false;
+ }
+ } else
+#endif
+ {
+ if (rpD->hasDepthStencilResolve) {
+ qWarning("Resolving multisample depth-stencil buffers is not supported without "
+ "VK_KHR_depth_stencil_resolve and VK_KHR_create_renderpass2");
+ }
+ VkResult err = df->vkCreateRenderPass(dev, &rpInfo, nullptr, &rpD->rp);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create renderpass: %d", err);
+ return false;
+ }
}
return true;
@@ -2487,6 +2697,13 @@ void QRhiVulkan::activateTextureRenderTarget(QVkCommandBuffer *cbD, QVkTextureRe
QRhiPassResourceTracker::TexDepthOutputStage);
depthTexD->lastActiveFrameSlot = currentFrameSlot;
}
+ if (rtD->m_desc.depthResolveTexture()) {
+ QVkTexture *depthResolveTexD = QRHI_RES(QVkTexture, rtD->m_desc.depthResolveTexture());
+ trackedRegisterTexture(&passResTracker, depthResolveTexD,
+ QRhiPassResourceTracker::TexDepthOutput,
+ QRhiPassResourceTracker::TexDepthOutputStage);
+ depthResolveTexD->lastActiveFrameSlot = currentFrameSlot;
+ }
}
void QRhiVulkan::resourceUpdate(QRhiCommandBuffer *cb, QRhiResourceUpdateBatch *resourceUpdates)
@@ -2628,6 +2845,11 @@ void QRhiVulkan::beginPass(QRhiCommandBuffer *cb,
float(colorClearValue.alphaF()) } };
cvs.append(cv);
}
+ for (int i = 0; i < rtD->dsResolveAttCount; ++i) {
+ VkClearValue cv;
+ cv.depthStencil = { depthStencilClearValue.depthClearValue(), depthStencilClearValue.stencilClearValue() };
+ cvs.append(cv);
+ }
rpBeginInfo.clearValueCount = uint32_t(cvs.size());
QVkCommandBuffer::Command &cmd(cbD->commands.get());
@@ -3931,6 +4153,7 @@ void QRhiVulkan::executeDeferredReleases(bool forced)
df->vkDestroyImageView(dev, e.textureRenderTarget.resrtv[att], nullptr);
}
df->vkDestroyImageView(dev, e.textureRenderTarget.dsv, nullptr);
+ df->vkDestroyImageView(dev, e.textureRenderTarget.resdsv, nullptr);
break;
case QRhiVulkan::DeferredReleaseEntry::RenderPass:
df->vkDestroyRenderPass(dev, e.renderPass.rp, nullptr);
@@ -4592,6 +4815,8 @@ bool QRhiVulkan::isFeatureSupported(QRhi::Feature feature) const
return caps.multiView;
case QRhi::TextureViewFormat:
return true;
+ case QRhi::ResolveDepthStencil:
+ return caps.renderPass2KHR && caps.depthStencilResolveKHR;
default:
Q_UNREACHABLE_RETURN(false);
}
@@ -6590,7 +6815,7 @@ bool QVkSampler::create()
QVkRenderPassDescriptor::QVkRenderPassDescriptor(QRhiImplementation *rhi)
: QRhiRenderPassDescriptor(rhi)
{
- serializedFormatData.reserve(32);
+ serializedFormatData.reserve(64);
}
QVkRenderPassDescriptor::~QVkRenderPassDescriptor()
@@ -6653,6 +6878,8 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other
return false;
if (hasDepthStencil != o->hasDepthStencil)
return false;
+ if (hasDepthStencilResolve != o->hasDepthStencilResolve)
+ return false;
if (multiViewCount != o->multiViewCount)
return false;
@@ -6680,6 +6907,14 @@ bool QVkRenderPassDescriptor::isCompatible(const QRhiRenderPassDescriptor *other
return false;
}
+ if (hasDepthStencilResolve) {
+ const uint32_t attIdx = dsResolveRef.attachment;
+ if (attIdx != o->dsResolveRef.attachment)
+ return false;
+ if (attIdx != VK_ATTACHMENT_UNUSED && !attachmentDescriptionEquals(attDescs[attIdx], o->attDescs[attIdx]))
+ return false;
+ }
+
// subpassDeps is not included
return true;
@@ -6694,6 +6929,7 @@ void QVkRenderPassDescriptor::updateSerializedFormat()
*p++ = colorRefs.size();
*p++ = resolveRefs.size();
*p++ = hasDepthStencil;
+ *p++ = hasDepthStencilResolve;
*p++ = multiViewCount;
auto serializeAttachmentData = [this, &p](uint32_t attIdx) {
@@ -6726,6 +6962,12 @@ void QVkRenderPassDescriptor::updateSerializedFormat()
*p++ = attIdx;
serializeAttachmentData(attIdx);
}
+
+ if (hasDepthStencilResolve) {
+ const uint32_t attIdx = dsResolveRef.attachment;
+ *p++ = attIdx;
+ serializeAttachmentData(attIdx);
+ }
}
QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescriptor() const
@@ -6738,8 +6980,10 @@ QRhiRenderPassDescriptor *QVkRenderPassDescriptor::newCompatibleRenderPassDescri
rpD->resolveRefs = resolveRefs;
rpD->subpassDeps = subpassDeps;
rpD->hasDepthStencil = hasDepthStencil;
+ rpD->hasDepthStencilResolve = hasDepthStencilResolve;
rpD->multiViewCount = multiViewCount;
rpD->dsRef = dsRef;
+ rpD->dsResolveRef = dsResolveRef;
VkRenderPassCreateInfo rpInfo;
VkSubpassDescription subpassDesc;
@@ -6842,6 +7086,8 @@ void QVkTextureRenderTarget::destroy()
e.textureRenderTarget.dsv = dsv;
dsv = VK_NULL_HANDLE;
+ e.textureRenderTarget.resdsv = resdsv;
+ resdsv = VK_NULL_HANDLE;
QRHI_RES_RHI(QRhiVulkan);
if (rhiD) {
@@ -6861,9 +7107,10 @@ QRhiRenderPassDescriptor *QVkTextureRenderTarget::newCompatibleRenderPassDescrip
m_desc.cendColorAttachments(),
m_flags.testFlag(QRhiTextureRenderTarget::PreserveColorContents),
m_flags.testFlag(QRhiTextureRenderTarget::PreserveDepthStencilContents),
- m_desc.depthTexture() && !m_flags.testFlag(DoNotStoreDepthStencilContents),
+ m_desc.depthTexture() && !m_flags.testFlag(DoNotStoreDepthStencilContents) && !m_desc.depthResolveTexture(),
m_desc.depthStencilBuffer(),
- m_desc.depthTexture()))
+ m_desc.depthTexture(),
+ m_desc.depthResolveTexture()))
{
delete rp;
return nullptr;
@@ -7010,6 +7257,36 @@ bool QVkTextureRenderTarget::create()
}
}
+ if (m_desc.depthResolveTexture()) {
+ QVkTexture *resTexD = QRHI_RES(QVkTexture, m_desc.depthResolveTexture());
+ Q_ASSERT(resTexD->flags().testFlag(QRhiTexture::RenderTarget));
+
+ VkImageViewCreateInfo viewInfo = {};
+ viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ viewInfo.image = resTexD->image;
+ viewInfo.viewType = d.multiViewCount ? VK_IMAGE_VIEW_TYPE_2D_ARRAY
+ : VK_IMAGE_VIEW_TYPE_2D;
+ viewInfo.format = resTexD->viewFormat;
+ viewInfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ viewInfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ viewInfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ viewInfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
+ viewInfo.subresourceRange.baseMipLevel = 0;
+ viewInfo.subresourceRange.levelCount = 1;
+ viewInfo.subresourceRange.baseArrayLayer = 0;
+ viewInfo.subresourceRange.layerCount = qMax<uint32_t>(1, d.multiViewCount);
+ VkResult err = rhiD->df->vkCreateImageView(rhiD->dev, &viewInfo, nullptr, &resdsv);
+ if (err != VK_SUCCESS) {
+ qWarning("Failed to create render target depth resolve image view: %d", err);
+ return false;
+ }
+ views.append(resdsv);
+ d.dsResolveAttCount = 1;
+ } else {
+ d.dsResolveAttCount = 0;
+ }
+
if (!m_renderPassDesc)
qWarning("QVkTextureRenderTarget: No renderpass descriptor set. See newCompatibleRenderPassDescriptor() and setRenderPassDescriptor().");
@@ -7019,7 +7296,7 @@ bool QVkTextureRenderTarget::create()
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = d.rp->rp;
- fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount);
+ fbInfo.attachmentCount = uint32_t(d.colorAttCount + d.dsAttCount + d.resolveAttCount + d.dsResolveAttCount);
fbInfo.pAttachments = views.constData();
fbInfo.width = uint32_t(d.pixelSize.width());
fbInfo.height = uint32_t(d.pixelSize.height());
@@ -7870,6 +8147,7 @@ bool QVkSwapChain::createOrResize()
rtWrapper.d.dsAttCount = 0;
ds = nullptr;
}
+ rtWrapper.d.dsResolveAttCount = 0;
if (samples > VK_SAMPLE_COUNT_1_BIT)
rtWrapper.d.resolveAttCount = 1;
else
@@ -7886,7 +8164,7 @@ bool QVkSwapChain::createOrResize()
VkFramebufferCreateInfo fbInfo = {};
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = rtWrapper.d.rp->rp;
- fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount);
+ fbInfo.attachmentCount = uint32_t(rtWrapper.d.colorAttCount + rtWrapper.d.dsAttCount + rtWrapper.d.resolveAttCount + rtWrapper.d.dsResolveAttCount);
fbInfo.pAttachments = views;
fbInfo.width = uint32_t(pixelSize.width());
fbInfo.height = uint32_t(pixelSize.height());
@@ -7916,6 +8194,7 @@ bool QVkSwapChain::createOrResize()
rtWrapperRight.d.dsAttCount = 0;
ds = nullptr;
}
+ rtWrapperRight.d.dsResolveAttCount = 0;
if (samples > VK_SAMPLE_COUNT_1_BIT)
rtWrapperRight.d.resolveAttCount = 1;
else
@@ -7934,7 +8213,7 @@ bool QVkSwapChain::createOrResize()
fbInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
fbInfo.renderPass = rtWrapperRight.d.rp->rp;
fbInfo.attachmentCount = uint32_t(rtWrapperRight.d.colorAttCount + rtWrapperRight.d.dsAttCount
- + rtWrapperRight.d.resolveAttCount);
+ + rtWrapperRight.d.resolveAttCount + rtWrapperRight.d.dsResolveAttCount);
fbInfo.pAttachments = views;
fbInfo.width = uint32_t(pixelSize.width());
fbInfo.height = uint32_t(pixelSize.height());
diff --git a/src/gui/rhi/qrhivulkan_p.h b/src/gui/rhi/qrhivulkan_p.h
index bdd65e177b..ad8687de5d 100644
--- a/src/gui/rhi/qrhivulkan_p.h
+++ b/src/gui/rhi/qrhivulkan_p.h
@@ -164,8 +164,10 @@ struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor
QVarLengthArray<VkAttachmentReference, 8> resolveRefs;
QVarLengthArray<VkSubpassDependency, 2> subpassDeps;
bool hasDepthStencil = false;
+ bool hasDepthStencilResolve = false;
uint32_t multiViewCount = 0;
VkAttachmentReference dsRef;
+ VkAttachmentReference dsResolveRef;
QVector<quint32> serializedFormatData;
QRhiVulkanRenderPassNativeHandles nativeHandlesStruct;
int lastActiveFrameSlot = -1;
@@ -181,6 +183,7 @@ struct QVkRenderTargetData
int colorAttCount = 0;
int dsAttCount = 0;
int resolveAttCount = 0;
+ int dsResolveAttCount = 0;
int multiViewCount = 0;
QRhiRenderTargetAttachmentTracker::ResIdList currentResIdList;
static const int MAX_COLOR_ATTACHMENTS = 8;
@@ -216,6 +219,7 @@ struct QVkTextureRenderTarget : public QRhiTextureRenderTarget
VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
VkImageView dsv = VK_NULL_HANDLE;
VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
+ VkImageView resdsv = VK_NULL_HANDLE;
int lastActiveFrameSlot = -1;
friend class QRhiVulkan;
};
@@ -775,7 +779,8 @@ public:
bool preserveDs,
bool storeDs,
QRhiRenderBuffer *depthStencilBuffer,
- QRhiTexture *depthTexture);
+ QRhiTexture *depthTexture,
+ QRhiTexture *depthResolveTexture);
bool ensurePipelineCache(const void *initialData = nullptr, size_t initialDataSize = 0);
VkShaderModule createShader(const QByteArray &spirv);
@@ -876,6 +881,10 @@ public:
PFN_vkGetPhysicalDeviceSurfaceFormatsKHR vkGetPhysicalDeviceSurfaceFormatsKHR;
PFN_vkGetPhysicalDeviceSurfacePresentModesKHR vkGetPhysicalDeviceSurfacePresentModesKHR;
+#ifdef VK_KHR_create_renderpass2
+ PFN_vkCreateRenderPass2KHR vkCreateRenderPass2KHR = nullptr;
+#endif
+
struct {
bool compute = false;
bool wideLines = false;
@@ -886,6 +895,8 @@ public:
bool geometryShader = false;
bool nonFillPolygonMode = false;
bool multiView = false;
+ bool renderPass2KHR = false;
+ bool depthStencilResolveKHR = false;
QVersionNumber apiVersion;
} caps;
@@ -1001,6 +1012,7 @@ public:
VkImageView rtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
VkImageView resrtv[QVkRenderTargetData::MAX_COLOR_ATTACHMENTS];
VkImageView dsv;
+ VkImageView resdsv;
} textureRenderTarget;
struct {
VkRenderPass rp;
diff --git a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
index cdbf247fce..7187b6858f 100644
--- a/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
+++ b/tests/auto/gui/rhi/qrhi/tst_qrhi.cpp
@@ -425,7 +425,8 @@ void tst_QRhi::create()
QRhi::RenderToOneDimensionalTexture,
QRhi::ThreeDimensionalTextureMipmaps,
QRhi::MultiView,
- QRhi::TextureViewFormat
+ QRhi::TextureViewFormat,
+ QRhi::ResolveDepthStencil
};
for (size_t i = 0; i <sizeof(features) / sizeof(QRhi::Feature); ++i)
rhi->isFeatureSupported(features[i]);
diff --git a/tests/manual/rhi/CMakeLists.txt b/tests/manual/rhi/CMakeLists.txt
index b0637b208c..8f48bf219d 100644
--- a/tests/manual/rhi/CMakeLists.txt
+++ b/tests/manual/rhi/CMakeLists.txt
@@ -33,6 +33,7 @@ add_subdirectory(tex1d)
add_subdirectory(displacement)
add_subdirectory(imguirenderer)
add_subdirectory(multiview)
+add_subdirectory(msaatextureresolve)
if(QT_FEATURE_widgets)
add_subdirectory(rhiwidgetproto)
endif()
diff --git a/tests/manual/rhi/msaatextureresolve/CMakeLists.txt b/tests/manual/rhi/msaatextureresolve/CMakeLists.txt
new file mode 100644
index 0000000000..fb14b119de
--- /dev/null
+++ b/tests/manual/rhi/msaatextureresolve/CMakeLists.txt
@@ -0,0 +1,41 @@
+# Copyright (C) 2024 The Qt Company Ltd.
+# SPDX-License-Identifier: BSD-3-Clause
+
+if(NOT QT_BUILD_STANDALONE_TESTS AND NOT QT_BUILDING_QT)
+ cmake_minimum_required(VERSION 3.16)
+ project(msaatextureresolve LANGUAGES CXX)
+ find_package(Qt6BuildInternals REQUIRED COMPONENTS STANDALONE_TEST)
+endif()
+
+qt_internal_add_manual_test(msaatextureresolve
+ GUI
+ SOURCES
+ msaatextureresolve.cpp
+ LIBRARIES
+ Qt::Gui
+ Qt::GuiPrivate
+)
+
+# Resources:
+set_source_files_properties("../shared/color.frag.qsb"
+ PROPERTIES QT_RESOURCE_ALIAS "color.frag.qsb"
+)
+set_source_files_properties("../shared/color.vert.qsb"
+ PROPERTIES QT_RESOURCE_ALIAS "color.vert.qsb"
+)
+set_source_files_properties("../shared/texture.frag.qsb"
+ PROPERTIES QT_RESOURCE_ALIAS "texture.frag.qsb"
+)
+set_source_files_properties("../shared/texture.vert.qsb"
+ PROPERTIES QT_RESOURCE_ALIAS "texture.vert.qsb"
+)
+
+qt_internal_add_resource(msaatextureresolve "msaatextureresolve"
+ PREFIX
+ "/"
+ FILES
+ "../shared/color.frag.qsb"
+ "../shared/color.vert.qsb"
+ "../shared/texture.frag.qsb"
+ "../shared/texture.vert.qsb"
+)
diff --git a/tests/manual/rhi/msaatextureresolve/msaatextureresolve.cpp b/tests/manual/rhi/msaatextureresolve/msaatextureresolve.cpp
new file mode 100644
index 0000000000..128cecf707
--- /dev/null
+++ b/tests/manual/rhi/msaatextureresolve/msaatextureresolve.cpp
@@ -0,0 +1,235 @@
+// Copyright (C) 2024 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include "../shared/examplefw.h"
+
+// Uses a multisample texture both for color and depth-stencil, renders into
+// those, and then resolves into non-multisample textures. Also the
+// depth-stencil, in order to exercise that rarely used path. If that is
+// functional, will not be visible on-screen. Frame captures with tools such as
+// RenderDoc can be used to verify that there is indeed a non-multisample depth
+// texture generated.
+
+static float vertexData[] =
+{ // Y up, CCW
+ -0.5f, 0.5f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f,
+ 0.5f, -0.5f, 1.0f, 1.0f,
+ 0.5f, 0.5f, 1.0f, 0.0f
+};
+
+static quint16 indexData[] =
+{
+ 0, 1, 2, 0, 2, 3
+};
+
+static float triangleData[] =
+{ // Y up, CCW
+ 0.0f, 0.5f, 1.0f, 0.0f, 0.0f,
+ -0.5f, -0.5f, 0.0f, 1.0f, 0.0f,
+ 0.5f, -0.5f, 0.0f, 0.0f, 1.0f,
+};
+
+struct {
+ QList<QRhiResource *> releasePool;
+ QRhiBuffer *vbuf = nullptr;
+ QRhiBuffer *ibuf = nullptr;
+ QRhiBuffer *ubuf = nullptr;
+ QRhiTexture *msaaColorTexture = nullptr;
+ QRhiTexture *msaaDepthTexture = nullptr;
+ QRhiTextureRenderTarget *rt = nullptr;
+ QRhiRenderPassDescriptor *rtRp = nullptr;
+ QRhiTexture *tex = nullptr;
+ QRhiTexture *depthTex = nullptr;
+ QRhiSampler *sampler = nullptr;
+ QRhiBuffer *triUbuf = nullptr;
+ QRhiShaderResourceBindings *triSrb = nullptr;
+ QRhiGraphicsPipeline *triPs = nullptr;
+ QRhiShaderResourceBindings *srb = nullptr;
+ QRhiGraphicsPipeline *ps = nullptr;
+ QRhiResourceUpdateBatch *initialUpdates = nullptr;
+ QMatrix4x4 triBaseMvp;
+ float triRot = 0;
+ QMatrix4x4 winProj;
+} d;
+
+void Window::customInit()
+{
+ if (!m_r->isFeatureSupported(QRhi::MultisampleTexture))
+ qFatal("Multisample textures not supported by this backend");
+
+ // Skip the check for ResolveDepthStencil, and let it run regardless. When
+ // not supported, it won't be functional, but should be handled somewhat
+ // gracefully.
+
+ d.vbuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::VertexBuffer, sizeof(vertexData) + sizeof(triangleData));
+ d.vbuf->create();
+ d.releasePool << d.vbuf;
+
+ d.ibuf = m_r->newBuffer(QRhiBuffer::Immutable, QRhiBuffer::IndexBuffer, sizeof(indexData));
+ d.ibuf->create();
+ d.releasePool << d.ibuf;
+
+ d.ubuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
+ d.ubuf->create();
+ d.releasePool << d.ubuf;
+
+ d.msaaColorTexture = m_r->newTexture(QRhiTexture::RGBA8, QSize(512, 512), 4, QRhiTexture::RenderTarget); // 4x MSAA
+ d.msaaColorTexture->create();
+ d.releasePool << d.msaaColorTexture;
+
+ d.msaaDepthTexture = m_r->newTexture(QRhiTexture::D24S8, QSize(512, 512), 4, QRhiTexture::RenderTarget); // 4x MSAA
+ d.msaaDepthTexture->create();
+ d.releasePool << d.msaaDepthTexture;
+
+ // the non-msaa texture that will be the destination in the resolve
+ d.tex = m_r->newTexture(QRhiTexture::RGBA8, d.msaaColorTexture->pixelSize(), 1, QRhiTexture::RenderTarget);
+ d.releasePool << d.tex;
+ d.tex->create();
+
+ d.depthTex = m_r->newTexture(QRhiTexture::D24S8, d.msaaDepthTexture->pixelSize(), 1, QRhiTexture::RenderTarget);
+ d.releasePool << d.depthTex;
+ d.depthTex->create();
+
+ QRhiTextureRenderTargetDescription rtDesc;
+ QRhiColorAttachment rtAtt(d.msaaColorTexture);
+ rtAtt.setResolveTexture(d.tex);
+ rtDesc.setColorAttachments({ rtAtt });
+ rtDesc.setDepthTexture(d.msaaDepthTexture);
+ rtDesc.setDepthResolveTexture(d.depthTex);
+
+ d.rt = m_r->newTextureRenderTarget(rtDesc);
+ d.releasePool << d.rt;
+ d.rtRp = d.rt->newCompatibleRenderPassDescriptor();
+ d.releasePool << d.rtRp;
+ d.rt->setRenderPassDescriptor(d.rtRp);
+ d.rt->create();
+
+ d.triUbuf = m_r->newBuffer(QRhiBuffer::Dynamic, QRhiBuffer::UniformBuffer, 68);
+ d.releasePool << d.triUbuf;
+ d.triUbuf->create();
+
+ d.triSrb = m_r->newShaderResourceBindings();
+ d.releasePool << d.triSrb;
+ d.triSrb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.triUbuf)
+ });
+ d.triSrb->create();
+
+ d.triPs = m_r->newGraphicsPipeline();
+ d.releasePool << d.triPs;
+ d.triPs->setDepthTest(true);
+ d.triPs->setDepthWrite(true);
+ d.triPs->setSampleCount(4); // must match the render target
+ d.triPs->setShaderStages({
+ { QRhiShaderStage::Vertex, getShader(QLatin1String(":/color.vert.qsb")) },
+ { QRhiShaderStage::Fragment, getShader(QLatin1String(":/color.frag.qsb")) }
+ });
+ QRhiVertexInputLayout inputLayout;
+ inputLayout.setBindings({
+ { 5 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float3, quint32(2 * sizeof(float)) }
+ });
+ d.triPs->setVertexInputLayout(inputLayout);
+ d.triPs->setShaderResourceBindings(d.triSrb);
+ d.triPs->setRenderPassDescriptor(d.rtRp);
+ d.triPs->create();
+
+ d.sampler = m_r->newSampler(QRhiSampler::Linear, QRhiSampler::Linear, QRhiSampler::None,
+ QRhiSampler::ClampToEdge, QRhiSampler::ClampToEdge);
+ d.releasePool << d.sampler;
+ d.sampler->create();
+
+ d.srb = m_r->newShaderResourceBindings();
+ d.releasePool << d.srb;
+ d.srb->setBindings({
+ QRhiShaderResourceBinding::uniformBuffer(0, QRhiShaderResourceBinding::VertexStage | QRhiShaderResourceBinding::FragmentStage, d.ubuf),
+ QRhiShaderResourceBinding::sampledTexture(1, QRhiShaderResourceBinding::FragmentStage, d.tex, d.sampler)
+ });
+ d.srb->create();
+
+ d.ps = m_r->newGraphicsPipeline();
+ d.releasePool << d.ps;
+ d.ps->setShaderStages({
+ { QRhiShaderStage::Vertex, getShader(QLatin1String(":/texture.vert.qsb")) },
+ { QRhiShaderStage::Fragment, getShader(QLatin1String(":/texture.frag.qsb")) }
+ });
+ inputLayout.setBindings({
+ { 4 * sizeof(float) }
+ });
+ inputLayout.setAttributes({
+ { 0, 0, QRhiVertexInputAttribute::Float2, 0 },
+ { 0, 1, QRhiVertexInputAttribute::Float2, quint32(2 * sizeof(float)) }
+ });
+ d.ps->setVertexInputLayout(inputLayout);
+ d.ps->setShaderResourceBindings(d.srb);
+ d.ps->setRenderPassDescriptor(m_rp);
+ d.ps->create();
+
+ d.initialUpdates = m_r->nextResourceUpdateBatch();
+ d.initialUpdates->uploadStaticBuffer(d.vbuf, 0, sizeof(vertexData), vertexData);
+ d.initialUpdates->uploadStaticBuffer(d.vbuf, sizeof(vertexData), sizeof(triangleData), triangleData);
+ d.initialUpdates->uploadStaticBuffer(d.ibuf, indexData);
+
+ d.triBaseMvp = m_r->clipSpaceCorrMatrix();
+ d.triBaseMvp.perspective(45.0f, d.msaaColorTexture->pixelSize().width() / float(d.msaaColorTexture->pixelSize().height()), 0.01f, 1000.0f);
+ d.triBaseMvp.translate(0, 0, -2);
+ float opacity = 1.0f;
+ d.initialUpdates->updateDynamicBuffer(d.triUbuf, 64, 4, &opacity);
+
+ qint32 flip = m_r->isYUpInFramebuffer() ? 1 : 0;
+ d.initialUpdates->updateDynamicBuffer(d.ubuf, 64, 4, &flip);
+}
+
+void Window::customRelease()
+{
+ qDeleteAll(d.releasePool);
+ d.releasePool.clear();
+}
+
+void Window::customRender()
+{
+ QRhiCommandBuffer *cb = m_sc->currentFrameCommandBuffer();
+ QRhiResourceUpdateBatch *u = m_r->nextResourceUpdateBatch();
+ if (d.initialUpdates) {
+ u->merge(d.initialUpdates);
+ d.initialUpdates->release();
+ d.initialUpdates = nullptr;
+ }
+
+ QMatrix4x4 triMvp = d.triBaseMvp;
+ triMvp.rotate(d.triRot, 0, 1, 0);
+ d.triRot += 1;
+ u->updateDynamicBuffer(d.triUbuf, 0, 64, triMvp.constData());
+
+ if (d.winProj != m_proj) {
+ d.winProj = m_proj;
+ QMatrix4x4 mvp = m_proj;
+ mvp.scale(2.5f);
+ u->updateDynamicBuffer(d.ubuf, 0, 64, mvp.constData());
+ }
+
+ // offscreen (triangle, msaa)
+ cb->beginPass(d.rt, QColor::fromRgbF(0.5f, 0.2f, 0.0f, 1.0f), { 1.0f, 0 }, u);
+ cb->setGraphicsPipeline(d.triPs);
+ cb->setViewport({ 0, 0, float(d.msaaColorTexture->pixelSize().width()), float(d.msaaColorTexture->pixelSize().height()) });
+ cb->setShaderResources();
+ QRhiCommandBuffer::VertexInput vbufBinding(d.vbuf, quint32(sizeof(vertexData)));
+ cb->setVertexInput(0, 1, &vbufBinding);
+ cb->draw(3);
+ cb->endPass();
+
+ // onscreen (quad)
+ const QSize outputSizeInPixels = m_sc->currentPixelSize();
+ cb->beginPass(m_sc->currentFrameRenderTarget(), m_clearColor, { 1.0f, 0 });
+ cb->setGraphicsPipeline(d.ps);
+ cb->setViewport({ 0, 0, float(outputSizeInPixels.width()), float(outputSizeInPixels.height()) });
+ cb->setShaderResources();
+ vbufBinding.second = 0;
+ cb->setVertexInput(0, 1, &vbufBinding, d.ibuf, 0, QRhiCommandBuffer::IndexUInt16);
+ cb->drawIndexed(6);
+ cb->endPass();
+}