From e36c1a8d840ee47be0d4fbf8069fda536042a53c Mon Sep 17 00:00:00 2001 From: Laszlo Agocs Date: Wed, 20 Mar 2024 13:57:18 +0100 Subject: [PATCH] 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 --- src/gui/rhi/qrhi.cpp | 52 ++- src/gui/rhi/qrhi.h | 7 +- src/gui/rhi/qrhid3d11.cpp | 4 + src/gui/rhi/qrhid3d12.cpp | 7 +- src/gui/rhi/qrhigles2.cpp | 246 +++++++++++--- src/gui/rhi/qrhigles2_p.h | 2 + src/gui/rhi/qrhimetal.mm | 21 +- src/gui/rhi/qrhivulkan.cpp | 307 +++++++++++++++++- src/gui/rhi/qrhivulkan_p.h | 14 +- tests/auto/gui/rhi/qrhi/tst_qrhi.cpp | 3 +- tests/manual/rhi/CMakeLists.txt | 1 + .../rhi/msaatextureresolve/CMakeLists.txt | 41 +++ .../msaatextureresolve/msaatextureresolve.cpp | 235 ++++++++++++++ 13 files changed, 865 insertions(+), 75 deletions(-) create mode 100644 tests/manual/rhi/msaatextureresolve/CMakeLists.txt create mode 100644 tests/manual/rhi/msaatextureresolve/msaatextureresolve.cpp diff --git a/src/gui/rhi/qrhi.cpp b/src/gui/rhi/qrhi.cpp index 25df9c60031..a39709c7263 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. */ /*! @@ -2692,6 +2706,39 @@ QRhiTextureRenderTargetDescription::QRhiTextureRenderTargetDescription(const QRh \sa setDepthStencilBuffer() */ +/*! + \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 @@ -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 a9836bf8925..d20b7e00d1c 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 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 26e1f45dc75..ba2b783b0e3 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 08224984d2f..d77e2db091b 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 d86b5368c45..62830c291d6 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; } } } - const bool mayDiscardDepthStencil = rtTex->m_desc.depthStencilBuffer() - || (rtTex->m_desc.depthTexture() && rtTex->m_flags.testFlag(QRhiTextureRenderTarget::DoNotStoreDepthStencilContents)); + 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))) + && !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 1365d439c22..4305186c029 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 2cdac0531af..b69bc17f51c 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 dsTex = nil; + id 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 da8c8fc52a8..f0b51146ccc 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 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( @@ -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(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 attDescs2; + QVarLengthArray 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(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 bdd65e177b8..ad8687de5dd 100644 --- a/src/gui/rhi/qrhivulkan_p.h +++ b/src/gui/rhi/qrhivulkan_p.h @@ -164,8 +164,10 @@ struct QVkRenderPassDescriptor : public QRhiRenderPassDescriptor QVarLengthArray resolveRefs; QVarLengthArray subpassDeps; bool hasDepthStencil = false; + bool hasDepthStencilResolve = false; uint32_t multiViewCount = 0; VkAttachmentReference dsRef; + VkAttachmentReference dsResolveRef; QVector 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 cdbf247fceb..7187b6858f4 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 isFeatureSupported(features[i]); diff --git a/tests/manual/rhi/CMakeLists.txt b/tests/manual/rhi/CMakeLists.txt index b0637b208cb..8f48bf219dc 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 00000000000..fb14b119de0 --- /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 00000000000..128cecf7071 --- /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 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(); +}