From 780f7bf00a29573473b90dcfab9f92a7d8687702 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Branimir=20Karad=C5=BEi=C4=87?= Date: Sun, 23 Aug 2026 04:44:43 +0000 Subject: [PATCH] Fix MSAA blit into a multisampled destination and stale MSAA resolve. (#3921) --- src/renderer_d3d11.cpp | 15 ++++++++-- src/renderer_d3d12.cpp | 37 ++++++++++++++++++----- src/renderer_d3d12.h | 1 + src/renderer_gl.cpp | 66 +++++++++++++++++++++++++++++++++++++++--- src/renderer_gl.h | 8 +++++ src/renderer_vk.cpp | 28 ++++++++++++++---- 6 files changed, 135 insertions(+), 20 deletions(-) diff --git a/src/renderer_d3d11.cpp b/src/renderer_d3d11.cpp index 85d608cdc..a4e663917 100644 --- a/src/renderer_d3d11.cpp +++ b/src/renderer_d3d11.cpp @@ -5442,7 +5442,7 @@ namespace bgfx { namespace d3d11 for (uint32_t ii = _layer, end = _layer + _numLayers; ii < end; ++ii) { const UINT resource = _mip + (ii * m_numMips); - deviceCtx->ResolveSubresource(m_texture2d, resource, m_rt, resource, resolveFormat); + deviceCtx->ResolveSubresource(m_texture2d, resource, m_rt, ii, resolveFormat); } } @@ -6122,6 +6122,15 @@ namespace bgfx { namespace d3d11 const TextureD3D11& src = m_textures[blit.m_src.idx]; const TextureD3D11& dst = m_textures[blit.m_dst.idx]; + const bool srcReadsMsaaRt = NULL != src.m_rt && dst.isMsaaSurface(); + + if (NULL != src.m_rt + && !srcReadsMsaaRt + && 0 == blit.m_srcMip) + { + src.resolve(BGFX_RESOLVE_NONE, blit.m_srcZ, 1, 0); + } + if ( src.isMsaaSurface() && !dst.isMsaaSurface() && TextureD3D11::Texture3D != src.m_type) @@ -6229,7 +6238,7 @@ namespace bgfx { namespace d3d11 , "When blitting depthstencil surface, source resolution must match destination." ); - const bool msaaToMsaa = src.isMsaaSurface() && dst.isMsaaSurface(); + const bool msaaToMsaa = (src.isMsaaSurface() || srcReadsMsaaRt) && dst.isMsaaSurface(); const bool wholeSubResource = depthStencil || msaaToMsaa; const D3D11_BOX box = @@ -6251,7 +6260,7 @@ namespace bgfx { namespace d3d11 , wholeSubResource ? 0 : blit.m_dstX , wholeSubResource ? 0 : blit.m_dstY , 0 - , src.m_ptr + , srcReadsMsaaRt ? src.m_rt : src.m_ptr , srcZ*src.m_numMips+blit.m_srcMip , wholeSubResource ? NULL : &box ); diff --git a/src/renderer_d3d12.cpp b/src/renderer_d3d12.cpp index 3aec24ab7..334fac978 100644 --- a/src/renderer_d3d12.cpp +++ b/src/renderer_d3d12.cpp @@ -7062,6 +7062,14 @@ namespace bgfx { namespace d3d12 , D3D12_RESOURCE_STATE_RESOLVE_DEST ); + const TextureFormatInfo& tfi = s_textureFormat[m_textureFormat]; + const bool useSrgb = true + && 0 != (m_flags & BGFX_TEXTURE_SRGB) + && !bimg::isDepth(bimg::TextureFormat::Enum(m_textureFormat) ) + && DXGI_FORMAT_UNKNOWN != tfi.m_fmtSrgb + ; + const DXGI_FORMAT resolveFormat = useSrgb ? tfi.m_fmtSrgb : tfi.m_fmt; + for (uint32_t ii = _layer, end = _layer + _numLayers; ii < end; ++ii) { const uint32_t resource = _mip + (ii * m_numMips); @@ -7070,7 +7078,7 @@ namespace bgfx { namespace d3d12 , resource , m_ptr , resource - , s_textureFormat[m_textureFormat].m_fmt + , resolveFormat ); } @@ -7122,6 +7130,12 @@ namespace bgfx { namespace d3d12 ; } + bool TextureD3D12::isMultisampled() const + { + const uint32_t msaaQuality = bx::satSub(uint32_t( (m_flags&BGFX_TEXTURE_RT_MSAA_MASK) >> BGFX_TEXTURE_RT_MSAA_SHIFT), 1u); + return 1 < s_msaa[msaaQuality].Count; + } + D3D12_RESOURCE_STATES TextureD3D12::setState(ID3D12GraphicsCommandList* _commandList, D3D12_RESOURCE_STATES _state) { if (m_state != _state) @@ -7753,10 +7767,10 @@ namespace bgfx { namespace d3d12 { ID3D12GraphicsCommandList* commandList = s_renderD3D12->m_commandList; - Query& query = m_query[_idx]; + Query& query = m_query[_idx]; query.m_ready = true; - query.m_fence = s_renderD3D12->m_cmd.m_currentFence - 1; - uint32_t offset = _idx * 2; + query.m_fence = s_renderD3D12->m_cmd.m_currentFence; + const uint32_t offset = _idx * 2; commandList->EndQuery(m_queryHeap , D3D12_QUERY_TYPE_TIMESTAMP @@ -7873,7 +7887,7 @@ namespace bgfx { namespace d3d12 , m_readback , handle.idx * sizeof(uint64_t) ); - m_fence[idx] = s_renderD3D12->m_cmd.m_currentFence - 1; + m_fence[idx] = s_renderD3D12->m_cmd.m_currentFence; m_control.commit(1); } @@ -7956,6 +7970,13 @@ namespace bgfx { namespace d3d12 if (currentSrc.idx != blit.m_src.idx) { + if (NULL != src.m_singleMsaa + && !dst.isMultisampled() + && 0 == blit.m_srcMip) + { + src.resolve(m_commandList, BGFX_RESOLVE_NONE, blit.m_srcZ, 1, 0); + } + if (D3D12_RESOURCE_STATES(UINT32_MAX) != srcState) { TextureD3D12& prev = m_textures[currentSrc.idx]; @@ -8094,7 +8115,7 @@ namespace bgfx { namespace d3d12 const D3D12_TEXTURE_COPY_LOCATION srcLocation = { - .pResource = NULL != src.m_singleMsaa ? src.m_singleMsaa : src.m_ptr, + .pResource = NULL != src.m_singleMsaa && !dst.isMultisampled() ? src.m_singleMsaa : src.m_ptr, .Type = D3D12_TEXTURE_COPY_TYPE_SUBRESOURCE_INDEX, .SubresourceIndex = srcZ*src.m_numMips+blit.m_srcMip, }; @@ -8238,7 +8259,7 @@ namespace bgfx { namespace d3d12 UniformCacheState ucs(_render); BlitState bs(_render); - uint32_t blendFactor = 0; + uint32_t blendFactor = UINT32_MAX; const uint64_t primType = _render->m_debug&BGFX_DEBUG_WIREFRAME ? BGFX_STATE_PT_LINES : 0; uint8_t primIndex = uint8_t(primType >> BGFX_STATE_PT_SHIFT); @@ -8442,6 +8463,7 @@ namespace bgfx { namespace d3d12 if (commandListChanged) { commandListChanged = false; + blendFactor = UINT32_MAX; m_commandList->SetComputeRootSignature(m_computeRootSignature); ID3D12DescriptorHeap* heaps[] = { @@ -8762,6 +8784,7 @@ namespace bgfx { namespace d3d12 { wasCompute = false; commandListChanged = false; + blendFactor = UINT32_MAX; m_commandList->SetGraphicsRootSignature(m_rootSignature); ID3D12DescriptorHeap* heaps[] = { diff --git a/src/renderer_d3d12.h b/src/renderer_d3d12.h index 0960f88b4..2c2ea07da 100644 --- a/src/renderer_d3d12.h +++ b/src/renderer_d3d12.h @@ -457,6 +457,7 @@ namespace bgfx { namespace d3d12 void resolve(ID3D12GraphicsCommandList* _commandList, uint8_t _resolve, uint32_t _layer, uint32_t _numLayers, uint32_t _mip); D3D12_RESOURCE_STATES setState(ID3D12GraphicsCommandList* _commandList, D3D12_RESOURCE_STATES _state); bool isMsaaSurface() const; + bool isMultisampled() const; D3D12_SHADER_RESOURCE_VIEW_DESC m_srvd; D3D12_UNORDERED_ACCESS_VIEW_DESC m_uavd; diff --git a/src/renderer_gl.cpp b/src/renderer_gl.cpp index 4185be81a..9ab565342 100644 --- a/src/renderer_gl.cpp +++ b/src/renderer_gl.cpp @@ -7605,6 +7605,51 @@ namespace bgfx { namespace gl return true; } + static void resolveMsaaRbo(const TextureGL& _src, GLuint _currentFbo) + { + if (0 == _src.m_rbo + || NULL == glBlitFramebuffer + || bimg::isDepth(bimg::TextureFormat::Enum(_src.m_textureFormat) ) ) + { + return; + } + + GLuint fbo[2]; + GL_CHECK(glGenFramebuffers(BX_COUNTOF(fbo), fbo) ); + + GL_CHECK(glBindFramebuffer(GL_READ_FRAMEBUFFER, fbo[0]) ); + GL_CHECK(glFramebufferRenderbuffer(GL_READ_FRAMEBUFFER + , GL_COLOR_ATTACHMENT0 + , GL_RENDERBUFFER + , _src.m_rbo + ) ); + + GL_CHECK(glBindFramebuffer(GL_DRAW_FRAMEBUFFER, fbo[1]) ); + GL_CHECK(glFramebufferTexture2D(GL_DRAW_FRAMEBUFFER + , GL_COLOR_ATTACHMENT0 + , _src.m_target + , _src.m_id + , 0 + ) ); + + GL_CHECK(glDisable(GL_SCISSOR_TEST) ); + GL_CHECK(glBlitFramebuffer( + 0 + , 0 + , _src.m_width + , _src.m_height + , 0 + , 0 + , _src.m_width + , _src.m_height + , GL_COLOR_BUFFER_BIT + , GL_NEAREST + ) ); + + GL_CHECK(glDeleteFramebuffers(BX_COUNTOF(fbo), fbo) ); + GL_CHECK(glBindFramebuffer(GL_FRAMEBUFFER, _currentFbo) ); + } + static bool blitCompressed2D( const BlitItem& _bi , const TextureGL& _src @@ -7742,13 +7787,21 @@ namespace bgfx { namespace gl continue; } + const bool srcReadsMsaaRbo = 0 != src.m_rbo && dst.isMsaaSurface(); + + if (!srcReadsMsaaRbo + && 0 == bi.m_srcMip) + { + resolveMsaaRbo(src, m_currentFbo); + } + GL_CHECK(glCopyImageSubData( - src.m_id - , src.m_target - , bi.m_srcMip + srcReadsMsaaRbo ? src.m_rbo : src.m_id + , srcReadsMsaaRbo ? GL_RENDERBUFFER : src.m_target + , srcReadsMsaaRbo ? 0 : bi.m_srcMip , bi.m_srcX , bi.m_srcY - , bi.m_srcZ + , srcReadsMsaaRbo ? 0 : bi.m_srcZ , dst.m_id , dst.m_target , bi.m_dstMip @@ -7774,6 +7827,11 @@ namespace bgfx { namespace gl , "Blitting 3D regions is not supported" ); + if (0 == bi.m_srcMip) + { + resolveMsaaRbo(src, m_currentFbo); + } + GLuint fbo; GL_CHECK(glGenFramebuffers(1, &fbo) ); diff --git a/src/renderer_gl.h b/src/renderer_gl.h index 9b469d85b..b151853ad 100644 --- a/src/renderer_gl.h +++ b/src/renderer_gl.h @@ -1465,6 +1465,14 @@ namespace bgfx { namespace gl ; } + bool isMsaaSurface() const + { + return 0 + || GL_TEXTURE_2D_MULTISAMPLE == m_target + || GL_TEXTURE_2D_MULTISAMPLE_ARRAY == m_target + ; + } + GLuint m_id; GLuint m_rbo; GLenum m_target; diff --git a/src/renderer_vk.cpp b/src/renderer_vk.cpp index 546d31002..5412273f5 100644 --- a/src/renderer_vk.cpp +++ b/src/renderer_vk.cpp @@ -9530,6 +9530,14 @@ VK_DESTROY BX_ASSERT(false, "Removing external texture failed!"); } + static bool blitReadsSingleMsaa(const TextureVK& _src, const TextureVK& _dst) + { + return true + && VK_NULL_HANDLE != _src.m_singleMsaaImage + && 1 == _dst.m_sampler.Count + ; + } + void RendererContextVK::submitBlitBatch(BlitState& _bs, uint16_t _view) { constexpr uint32_t kMaxItems = 128; @@ -9550,7 +9558,13 @@ VK_DESTROY TextureVK& src = m_textures[blit.m_src.idx]; TextureVK& dst = m_textures[blit.m_dst.idx]; - srcLayouts[item] = VK_NULL_HANDLE != src.m_singleMsaaImage + if (blitReadsSingleMsaa(src, dst) + && 0 == blit.m_srcMip) + { + src.resolve(m_commandBuffer, BGFX_RESOLVE_NONE, blit.m_srcZ, 1, 0); + } + + srcLayouts[item] = blitReadsSingleMsaa(src, dst) ? src.m_currentSingleMsaaImageLayout : src.m_currentImageLayout ; @@ -9566,12 +9580,14 @@ VK_DESTROY TextureVK& src = m_textures[blit.m_src.idx]; TextureVK& dst = m_textures[blit.m_dst.idx]; + const bool srcSingleMsaa = blitReadsSingleMsaa(src, dst); + src.setState( m_commandBuffer , blit.m_src.idx == blit.m_dst.idx ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL - , VK_NULL_HANDLE != src.m_singleMsaaImage + , srcSingleMsaa ); if (blit.m_src.idx != blit.m_dst.idx) @@ -9579,7 +9595,7 @@ VK_DESTROY dst.setState(m_commandBuffer, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); } - const uint16_t srcSamples = VK_NULL_HANDLE != src.m_singleMsaaImage ? 1 : src.m_sampler.Count; + const uint16_t srcSamples = srcSingleMsaa ? 1 : src.m_sampler.Count; const uint16_t dstSamples = dst.m_sampler.Count; const bool resolve = true @@ -9675,8 +9691,8 @@ VK_DESTROY { vkCmdCopyImage( m_commandBuffer - , VK_NULL_HANDLE != src.m_singleMsaaImage ? src.m_singleMsaaImage : src.m_textureImage - , VK_NULL_HANDLE != src.m_singleMsaaImage ? src.m_currentSingleMsaaImageLayout : src.m_currentImageLayout + , srcSingleMsaa ? src.m_singleMsaaImage : src.m_textureImage + , srcSingleMsaa ? src.m_currentSingleMsaaImageLayout : src.m_currentImageLayout , dst.m_textureImage , dst.m_currentImageLayout , 1 @@ -9698,7 +9714,7 @@ VK_DESTROY TextureVK& src = m_textures[blit.m_src.idx]; TextureVK& dst = m_textures[blit.m_dst.idx]; - src.setState(m_commandBuffer, srcLayouts[item], VK_NULL_HANDLE != src.m_singleMsaaImage); + src.setState(m_commandBuffer, srcLayouts[item], blitReadsSingleMsaa(src, dst) ); dst.setState(m_commandBuffer, dstLayouts[item]); } }