From 645f2f748f634e25d08f81901a743f837b093430 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 8 Mar 2026 00:24:30 +0800 Subject: [PATCH] [Fix] (MG_Backend/DirectVulkan): fix missing clear when (texture-FBO attach -> clear -> detach) occurs --- .../Renderer/VkRenderPassManager.cpp | 22 +++-- .../Renderer/VkTextureManager.cpp | 14 ++- .../DirectVulkan/Renderer/VulkanRenderer.cpp | 87 ++++++++++++++++++- .../DirectVulkan/Renderer/VulkanRenderer.h | 2 + 4 files changed, 111 insertions(+), 14 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 897031fa..97dbe669 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -173,6 +173,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { desc.samples = VK_SAMPLE_COUNT_1_BIT; ClearAttachmentPayload clearPayload{}; Bool hasClear = m_clearManager.GetPendingClear(texture, clearPayload); + VkImageLayout trackedColorLayout = VK_IMAGE_LAYOUT_UNDEFINED; desc.loadOp = hasClear ? VK_ATTACHMENT_LOAD_OP_CLEAR : VK_ATTACHMENT_LOAD_OP_LOAD; @@ -197,9 +198,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { const auto& swapchainViews = m_swapchainObject.GetImageViews(); MOBILEGL_ASSERT(swapchainImageIndex < swapchainViews.size(), "GetOrCreateRenderPass: swapchain image index out of range"); - desc.initialLayout = hasClear ? - VK_IMAGE_LAYOUT_UNDEFINED : - m_swapchainObject.GetImageLayout(swapchainImageIndex); + trackedColorLayout = m_swapchainObject.GetImageLayout(swapchainImageIndex); trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::SwapchainColor, .swapchainImageIndex = swapchainImageIndex, @@ -210,12 +209,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { textureResources[i] = m_textureManager.SyncTextureAndGetDescriptor(*texture); MOBILEGL_ASSERT(textureResources[i], "GetOrCreateRenderPass: SyncTextureAndGetDescriptor failed at color attachment %d", i); - MOBILEGL_ASSERT(hasClear || textureResources[i]->layout != VK_IMAGE_LAYOUT_UNDEFINED, - "GetOrCreateRenderPass: color attachment textureId=%d has undefined tracked layout with LOAD_OP_LOAD (attachment=%d)", - texture->GetExternalIndex(), i); - desc.initialLayout = hasClear ? - VK_IMAGE_LAYOUT_UNDEFINED : - textureResources[i]->layout; + trackedColorLayout = textureResources[i]->layout; trackedAttachmentLayouts.emplace_back(TrackedAttachmentLayoutInfo { .target = TrackedAttachmentTarget::Texture, .texture = texture, @@ -224,6 +218,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { attachmentViews[i] = textureResources[i]->view; } + if (!hasClear && trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { + MGLOG_W("GetOrCreateRenderPass: color attachment textureId=%d starts with undefined layout and no clear; " + "using LOAD_OP_DONT_CARE", + texture->GetExternalIndex()); + desc.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; + } + desc.initialLayout = (hasClear || trackedColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) ? + VK_IMAGE_LAYOUT_UNDEFINED : + trackedColorLayout; + break; } default: diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index 348bfd2b..29b029a5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -12,6 +12,8 @@ #include "MG_Util/Converters/MGToVk/TextureEnumConverter.h" namespace MobileGL::MG_Backend::DirectVulkan { + static constexpr VkPipelineStageFlags kGraphicsSampledReadStages = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; + static Bool IsValidSampledImageLayout(VkImageLayout layout) { switch (layout) { case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: @@ -90,6 +92,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { if (IsValidSampledImageLayout(resource->layout)) { return true; } + if (resource->layout == VK_IMAGE_LAYOUT_UNDEFINED) { + MGLOG_W("TransitionTextureForSampling: textureId=%d is still in VK_IMAGE_LAYOUT_UNDEFINED before sampling", + texture.GetExternalIndex()); + } VkImageLayout targetLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; @@ -120,7 +126,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } const Bool ok = TransitionImageLayout(commandBuffer, resource->image, resource->layout, targetLayout, srcStageMask, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, srcAccessMask, + kGraphicsSampledReadStages, srcAccessMask, VK_ACCESS_SHADER_READ_BIT, resource->aspect); MOBILEGL_ASSERT(ok, "TransitionTextureForSampling: transition failed for textureId=%d", texture.GetExternalIndex()); return ok; @@ -378,7 +384,9 @@ namespace MobileGL::MG_Backend::DirectVulkan { Bool ok = TransitionImageLayout(commandBuffer, outResource.image, outResource.layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, - VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT : VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? + kGraphicsSampledReadStages : + VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, outResource.layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL ? VK_ACCESS_SHADER_READ_BIT : 0, VK_ACCESS_TRANSFER_WRITE_BIT, @@ -404,7 +412,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { outResource.layout, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, VK_PIPELINE_STAGE_TRANSFER_BIT, - VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, + kGraphicsSampledReadStages, VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, aspectMask); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 5fec42cd..8b306bc7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -117,7 +117,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { outSrcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; break; case VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL: - outSrcStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; + outSrcStageMask = VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT; outSrcAccessMask = VK_ACCESS_SHADER_READ_BIT; break; default: @@ -763,7 +763,8 @@ void main() { MGLOG_D("SetupDraw: sampled textureId=%d layout(before)=%s(%d)", sampledTexture->GetExternalIndex(), VkImageLayoutToString(textureResource->layout), static_cast(textureResource->layout)); - if (!IsValidSampledImageLayout(textureResource->layout)) { + if (m_clearManager->HasPendingClear(sampledTexture) || + !IsValidSampledImageLayout(textureResource->layout)) { needSampledTextureTransitions = true; break; } @@ -779,6 +780,9 @@ void main() { if (!sampledTexture) { continue; } + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sampledTexture); + MOBILEGL_ASSERT(clearReady, "%s: MaterializePendingClearForTexture failed for textureId=%d", + __func__, sampledTexture->GetExternalIndex()); const Bool ready = m_textureManager->TransitionTextureForSampling(frame.commandBuffer, *sampledTexture); MOBILEGL_ASSERT(ready, "%s: TransitionTextureForSampling failed for textureId=%d", __func__, sampledTexture->GetExternalIndex()); @@ -850,6 +854,71 @@ void main() { m_clearManager->QueueClear(mask, payload, *fbo); } + Bool VulkanRenderer::MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, + MG_State::GLState::ITextureObject& texture) { + ClearAttachmentPayload clearPayload{}; + if (!m_clearManager->GetPendingClear(&texture, clearPayload)) { + return true; + } + MOBILEGL_ASSERT(VkRenderPassManager::GetActiveRenderPass() == nullptr, + "MaterializePendingClearForTexture requires no active render pass"); + + auto* resource = m_textureManager->SyncTextureAndGetDescriptor(texture); + MOBILEGL_ASSERT(resource != nullptr, + "MaterializePendingClearForTexture: SyncTextureAndGetDescriptor failed for textureId=%d", + texture.GetExternalIndex()); + + VkPipelineStageFlags srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + VkAccessFlags srcAccessMask = 0; + GetImageTransitionSourceState(resource->layout, srcStageMask, srcAccessMask); + + Bool ok = VkTextureManager::TransitionImageLayout( + commandBuffer, resource->image, resource->layout, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + srcStageMask, VK_PIPELINE_STAGE_TRANSFER_BIT, srcAccessMask, VK_ACCESS_TRANSFER_WRITE_BIT, + resource->aspect); + MOBILEGL_ASSERT(ok, + "MaterializePendingClearForTexture: failed to transition textureId=%d to TRANSFER_DST", + texture.GetExternalIndex()); + + VkImageSubresourceRange subresourceRange{}; + subresourceRange.aspectMask = resource->aspect; + subresourceRange.baseMipLevel = 0; + subresourceRange.levelCount = 1; + subresourceRange.baseArrayLayer = 0; + subresourceRange.layerCount = 1; + + VkImageLayout sampledLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; + if ((resource->aspect & VK_IMAGE_ASPECT_COLOR_BIT) != 0) { + VkClearColorValue clearValue{}; + clearValue.float32[0] = clearPayload.color.x(); + clearValue.float32[1] = clearPayload.color.y(); + clearValue.float32[2] = clearPayload.color.z(); + clearValue.float32[3] = clearPayload.color.w(); + vkCmdClearColorImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + &clearValue, 1, &subresourceRange); + } else { + VkClearDepthStencilValue clearValue{}; + clearValue.depth = clearPayload.depth; + clearValue.stencil = clearPayload.stencil; + vkCmdClearDepthStencilImage(commandBuffer, resource->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, + &clearValue, 1, &subresourceRange); + sampledLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_READ_ONLY_OPTIMAL; + } + + ok = VkTextureManager::TransitionImageLayout( + commandBuffer, resource->image, resource->layout, sampledLayout, + VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT, + VK_ACCESS_TRANSFER_WRITE_BIT, VK_ACCESS_SHADER_READ_BIT, resource->aspect); + MOBILEGL_ASSERT(ok, + "MaterializePendingClearForTexture: failed to transition textureId=%d to sampled layout", + texture.GetExternalIndex()); + + m_clearManager->PopPendingClear(&texture); + MGLOG_D("MaterializePendingClearForTexture: textureId=%d pending clear materialized", + texture.GetExternalIndex()); + return true; + } + Bool VulkanRenderer::TryBlitToDefaultFramebufferWithShader(FrameContext::FrameData& frame, MG_State::GLState::FramebufferObject& readFbo, MG_State::GLState::FramebufferObject& drawFbo, @@ -881,6 +950,10 @@ void main() { const auto& attachment = readFbo.GetAttachment(readFbo.GetReadBuffer()); auto sourceTexture = attachment.GetTexture(); MOBILEGL_ASSERT(sourceTexture != nullptr, "TryBlitToDefaultFramebufferWithShader: source texture is null"); + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); + MOBILEGL_ASSERT(clearReady, + "TryBlitToDefaultFramebufferWithShader: failed to materialize pending clear for textureId=%d", + sourceTexture->GetExternalIndex()); const Bool ready = m_textureManager->TransitionTextureForSampling(frame.commandBuffer, *sourceTexture); if (!ready) { MGLOG_E("BlitFramebuffer skipped: failed to transition source textureId=%d for sampling", @@ -1025,6 +1098,16 @@ void main() { return; } + if (!readIsDefaultFbo) { + const auto& sourceAttachment = readFbo->GetAttachment(readFbo->GetReadBuffer()); + auto sourceTexture = sourceAttachment.GetTexture(); + MOBILEGL_ASSERT(sourceTexture != nullptr, "BlitFramebuffer: source texture attachment is null"); + const Bool clearReady = MaterializePendingClearForTexture(frame.commandBuffer, *sourceTexture); + MOBILEGL_ASSERT(clearReady, + "BlitFramebuffer: failed to materialize pending clear for source textureId=%d", + sourceTexture->GetExternalIndex()); + } + VkImageLayout srcLayout = readIsDefaultFbo ? m_swapchainObject.GetImageLayout(m_imageIndexAcquired) : *srcBinding.trackedLayout; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index 3a0e3285..3763cc9c 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -190,6 +190,8 @@ namespace MobileGL::MG_Backend::DirectVulkan { GLint srcX0, GLint srcY0, GLint srcX1, GLint srcY1, GLint dstX0, GLint dstY0, GLint dstX1, GLint dstY1, GLenum filter); + Bool MaterializePendingClearForTexture(VkCommandBuffer commandBuffer, + MG_State::GLState::ITextureObject& texture); VkPipeline GetOrCreateBlitPipeline(const RenderPassEntry& renderPassEntry); void ShutdownSwapchain();