diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/RenderPassGuard.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/RenderPassGuard.h new file mode 100644 index 00000000..d1cd3741 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/RenderPassGuard.h @@ -0,0 +1,27 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/RenderPassGuard.h +// Copyright (c) 2025-2026 MobileGL-Dev +// Licensed under the GNU Lesser General Public License v3.0: +// https://www.gnu.org/licenses/gpl-3.0.txt +// https://www.gnu.org/licenses/lgpl-3.0.txt +// SPDX-License-Identifier: LGPL-3.0-only +// End of Source File Header + +#pragma once +#include "../VkIncludes.h" +#include + +namespace MobileGL::MG_Backend::DirectVulkan { + // Ends the render pass that is active on `commandBuffer`, if there is one. Called right before + // recording a command a render pass may not contain (a barrier, a copy, a blit, a clear of an + // image) into a command buffer that may hold an open pass: the wire draw path keeps its pass + // open across consecutive draws to the same attachments, so the resource work that falls + // between two draws must close it first. A pass on another command buffer is left alone. + void EndActiveRenderPassOn(VkCommandBuffer commandBuffer); + + // A counter that moves whenever images may have been written since it was last read: a render + // pass ended (its attachments), resource work recorded outside a pass (copies, clears, blits, + // dispatches all close the pass first), or a draw with storage images. A sampled image needs + // the memory barrier that makes earlier writes visible only when this moved since the last one. + Uint64 WireImageWriteEpoch(); + void BumpWireImageWriteEpoch(); +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp index fde6b6e2..8738182a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.cpp @@ -6,6 +6,7 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header +#include "RenderPassGuard.h" #include "UniformManager.h" #include "MG_Backend/DirectVulkan/DirectVulkanResourceState.h" @@ -251,11 +252,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { WireDescriptorFatal("image-descriptor-transition"); // A second descriptor/draw can keep GENERAL after a shader write. A layout // equality fast return is not a memory dependency for that prior write. - VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; - memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; - memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; - vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, - 0, 1, &memory, 0, nullptr, 0, nullptr); + // Only when something may have written an image since the last such barrier + // (WireImageWriteEpoch): with nothing written in between, consecutive draws keep their + // render pass open instead of closing it for a barrier that orders nothing. + if (WireImageWriteEpoch() != m_wireImageBarrierEpoch) { + VkMemoryBarrier memory{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; + memory.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; + memory.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + EndActiveRenderPassOn(commandBuffer); + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, + 0, 1, &memory, 0, nullptr, 0, nullptr); + m_wireImageBarrierEpoch = WireImageWriteEpoch(); + } VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; info.image = resource->image; info.viewType = type; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h index 83419127..3d5f98c5 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/UniformManager.h @@ -660,5 +660,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { m_samplerResolveMemoHighWater = binding + 1; } } + // WireImageWriteEpoch() when ResolveWireImageDescriptor last made image writes visible. + mutable Uint64 m_wireImageBarrierEpoch = 0; }; } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index f8008728..40d89fa1 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -6,6 +6,7 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header +#include "RenderPassGuard.h" #include "VkBufferManager.h" #include "../DirectVulkan.h" #include "VulkanRenderer.h" @@ -337,6 +338,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier barrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; barrier.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_HOST_READ_BIT | VK_ACCESS_HOST_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &barrier, 0, nullptr, 0, nullptr); // P8-D: counted here, past the idle early-out, so `whw` is waits TAKEN, not calls. @@ -531,13 +533,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; before.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT; before.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &before, 0, nullptr, 0, nullptr); const VkBufferCopy copy{offset, dst.offset, size}; + EndActiveRenderPassOn(commands); vkCmdCopyBuffer(commands, resource->buffer.GetHandle(), dst.buffer, 1, ©); VkMemoryBarrier after{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; after.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; after.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &after, 0, nullptr, 0, nullptr); resource->lastUseSerial = m_frameSerial; @@ -575,22 +580,27 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier before{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; before.srcAccessMask = VK_ACCESS_MEMORY_WRITE_BIT | VK_ACCESS_HOST_WRITE_BIT; before.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT | VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &before, 0, nullptr, 0, nullptr); const VkBufferCopy widen{alignedOffset, staging.offset, stagedSize}; + EndActiveRenderPassOn(commands); vkCmdCopyBuffer(commands, resource->buffer.GetHandle(), staging.buffer, 1, &widen); // The shift reads what the widening copy just wrote, so it needs its own edge even // though both halves are transfers on one command buffer. VkMemoryBarrier staged{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; staged.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; staged.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &staged, 0, nullptr, 0, nullptr); const VkBufferCopy shift{staging.offset + headPad, dst.offset + dstSkip, size}; + EndActiveRenderPassOn(commands); vkCmdCopyBuffer(commands, staging.buffer, dst.buffer, 1, &shift); VkMemoryBarrier after{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; after.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; after.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &after, 0, nullptr, 0, nullptr); resource->lastUseSerial = m_frameSerial; @@ -908,14 +918,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier hostWrites{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; hostWrites.srcAccessMask = VK_ACCESS_HOST_WRITE_BIT; hostWrites.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT | VK_ACCESS_TRANSFER_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &hostWrites, 0, nullptr, 0, nullptr); VkBufferCopy region{0, 0, kWords * 4}; + EndActiveRenderPassOn(commands); vkCmdCopyBuffer(commands, imported.buffer, staging.buffer, 1, ®ion); + EndActiveRenderPassOn(commands); vkCmdFillBuffer(commands, imported.buffer, kFillAt, kWords * 4, kFillWord); VkMemoryBarrier toHost{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; toHost.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; toHost.dstAccessMask = VK_ACCESS_HOST_READ_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost, 0, nullptr, 0, nullptr); vkEndCommandBuffer(commands); @@ -965,11 +979,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { verdict = "steady: the second submission could not be recorded"; break; } + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_HOST_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &hostWrites, 0, nullptr, 0, nullptr); VkBufferCopy steadyRegion{kSteadyAt, 0, kWords * 4}; + EndActiveRenderPassOn(commands); vkCmdCopyBuffer(commands, imported.buffer, staging.buffer, 1, &steadyRegion); + EndActiveRenderPassOn(commands); vkCmdFillBuffer(commands, imported.buffer, kFillAt, kWords * 4, kSteadyFillWord); + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0, 1, &toHost, 0, nullptr, 0, nullptr); vkEndCommandBuffer(commands); @@ -1488,6 +1506,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; beforeBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + EndActiveRenderPassOn(commandBuffer); vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &beforeBarrier, 0, nullptr, 0, nullptr); @@ -1495,11 +1514,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { region.srcOffset = staging.offset; region.dstOffset = static_cast(offset); region.size = static_cast(size); + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, ®ion); VkMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; afterBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + EndActiveRenderPassOn(commandBuffer); vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &afterBarrier, 0, nullptr, 0, nullptr); @@ -1534,6 +1555,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkMemoryBarrier beforeBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; beforeBarrier.srcAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; beforeBarrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; + EndActiveRenderPassOn(commandBuffer); vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 1, &beforeBarrier, 0, nullptr, 0, nullptr); @@ -1541,11 +1563,13 @@ namespace MobileGL::MG_Backend::DirectVulkan { region.srcOffset = staging.offset; region.dstOffset = static_cast(offset); region.size = static_cast(size); + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, ®ion); VkMemoryBarrier afterBarrier{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; afterBarrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; afterBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT | VK_ACCESS_MEMORY_WRITE_BIT; + EndActiveRenderPassOn(commandBuffer); vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &afterBarrier, 0, nullptr, 0, nullptr); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp index 8cf98785..6e91d62f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.cpp @@ -7,6 +7,8 @@ // End of Source File Header #include "VkRenderPassManager.h" +#include "RenderPassGuard.h" +#include #include "MG_Impl/GLImpl/Framebuffer/GL_Framebuffer.h" #include "MG_State/GLState/TextureState/TextureObject2D.h" @@ -1666,6 +1668,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { s_clearManager.Get()->PopPendingClear(pending.key); } } + s_activeRenderPass.Get().commandBuffer = commandBuffer; s_activeRenderPass.Get().hash = renderPassEntry.hash; s_activeRenderPass.Get().compatibilityHash = renderPassEntry.compatibilityHash; s_activeRenderPass.Get().trackedAttachmentLayouts = renderPassEntry.trackedAttachmentLayouts; @@ -1676,6 +1679,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } Bool VkRenderPassManager::EndRenderPass(VkCommandBuffer commandBuffer) { + BumpWireImageWriteEpoch(); auto* activeRenderPass = GetActiveRenderPass(); vkCmdEndRenderPass(commandBuffer); // The fast-path memo reuses the ACTIVE render pass; once the pass ends it must not carry @@ -1734,4 +1738,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { ActiveRenderPassInfo* VkRenderPassManager::GetActiveRenderPass() { return s_hasActiveRenderPass.Get() ? &s_activeRenderPass.Get() : nullptr; } + namespace { + std::atomic g_wireImageWriteEpoch{1}; + } + Uint64 WireImageWriteEpoch() { return g_wireImageWriteEpoch.load(std::memory_order_relaxed); } + void BumpWireImageWriteEpoch() { g_wireImageWriteEpoch.fetch_add(1, std::memory_order_relaxed); } + + void EndActiveRenderPassOn(VkCommandBuffer commandBuffer) { + BumpWireImageWriteEpoch(); + const auto* active = VkRenderPassManager::GetActiveRenderPass(); + if (active != nullptr && active->commandBuffer == commandBuffer && commandBuffer != VK_NULL_HANDLE) + VkRenderPassManager::EndRenderPass(commandBuffer); + } + } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h index 708649a0..e9c19c23 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkRenderPassManager.h @@ -185,6 +185,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { struct ActiveRenderPassInfo { Uint64 hash = 0; Uint64 compatibilityHash = 0; + VkCommandBuffer commandBuffer = VK_NULL_HANDLE; Vector trackedAttachmentLayouts; IntVec2 extent = {0, 0}; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp index b5fb0e37..4608ace4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkTextureManager.cpp @@ -6,6 +6,7 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header +#include "RenderPassGuard.h" #include "VkTextureManager.h" #include "ProgramFactory.h" @@ -376,6 +377,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { copyRegions.push_back(copy); } + EndActiveRenderPassOn(commandBuffer); vkCmdCopyImage(commandBuffer, oldResource.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, newResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, @@ -1658,6 +1660,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { std::max(source->depth >> level, 1u)}; copyRegions.push_back(copy); } + EndActiveRenderPassOn(commandBuffer); vkCmdCopyImage(commandBuffer, source->image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, snapshot.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, static_cast(copyRegions.size()), copyRegions.data()); @@ -1772,6 +1775,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // slices" but will read as "depth slice 0" once VK_KHR_maintenance9 is enabled. The // validation layer warns about that literal 1 by name. barrier.subresourceRange.layerCount = VK_REMAINING_ARRAY_LAYERS; + EndActiveRenderPassOn(commandBuffer); vkCmdPipelineBarrier(commandBuffer, srcStageMask, dstStageMask, 0, 0, nullptr, 0, nullptr, 1, &barrier); trackedLayout = newLayout; @@ -2734,9 +2738,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { stencilCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; stencilCopy.bufferOffset = stagingBase + item.offset + static_cast(texelCount) * 4; const VkBufferImageCopy copies[2] = {depthCopy, stencilCopy}; + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, resource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, copies); } else { + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, resource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); } @@ -3211,6 +3217,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { if ((aspect & copyAspect) == 0) continue; copy.srcSubresource.aspectMask = copyAspect; copy.dstSubresource.aspectMask = copyAspect; + EndActiveRenderPassOn(commandBuffer); vkCmdCopyImage(commandBuffer, old.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, grown.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); } @@ -4770,6 +4777,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { rectCopies[rectCopyCount++] = rectCopy; runningOffset += static_cast(rect.TexelCount() * item.texelBytes); } + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, rectCopyCount, rectCopies.data()); continue; @@ -4811,10 +4819,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { stencilCopy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_STENCIL_BIT; stencilCopy.bufferOffset = stagingBase + item.offset + static_cast(texelCount) * 4; const VkBufferImageCopy copies[2] = {depthCopy, stencilCopy}; + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 2, copies); continue; } + EndActiveRenderPassOn(commandBuffer); vkCmdCopyBufferToImage(commandBuffer, stagingBuffer, outResource.image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ©); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6e17b010..10174bce 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -6,6 +6,7 @@ // SPDX-License-Identifier: LGPL-3.0-only // End of Source File Header +#include "RenderPassGuard.h" #include "VulkanRenderer.h" #include "SubmitFencePrefix.h" #include "MG_Util/X11/DisplayGuard.h" @@ -5200,6 +5201,7 @@ void main() { static_cast(dstTexelSize.x()), static_cast(dstTexelSize.y()) }; + EndActiveRenderPassOn(frame.commandBuffer); vkCmdBeginRenderPass(frame.commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); VkViewport viewport{}; @@ -7829,6 +7831,7 @@ void main() { } MGLOG_D("DirectVulkan: glDispatchCompute(%u, %u, %u)", numGroupsX, numGroupsY, numGroupsZ); + EndActiveRenderPassOn(frame.commandBuffer); vkCmdDispatch(frame.commandBuffer, numGroupsX, numGroupsY, numGroupsZ); } @@ -7906,6 +7909,7 @@ void main() { } MGLOG_D("DirectVulkan: glDispatchComputeIndirect(offset=%zu)", static_cast(indirect)); + EndActiveRenderPassOn(frame.commandBuffer); vkCmdDispatchIndirect(frame.commandBuffer, slice.buffer, slice.offset + static_cast(indirect)); } @@ -8750,6 +8754,7 @@ void main() { beginInfo.clearValueCount = 1; beginInfo.pClearValues = &clearValue; // The load op is the whole operation: begin and end with nothing in between. + EndActiveRenderPassOn(commandBuffer); vkCmdBeginRenderPass(commandBuffer, &beginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdEndRenderPass(commandBuffer); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc index feafca3e..f0f2999a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireColorBlit.inc @@ -378,6 +378,7 @@ begin.renderPass = target->renderPass; begin.framebuffer = framebuffer; begin.renderArea = {{0, 0}, destination.extent}; + EndActiveRenderPassOn(frame.commandBuffer); vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, target->pipeline); const VkViewport viewport{0.0f, 0.0f, static_cast(destination.extent.width), diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.inc index 8f5cc86f..e62e46d2 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthMipmap.inc @@ -299,6 +299,7 @@ begin.renderPass = target->renderPass; begin.framebuffer = framebuffer; begin.renderArea = {{0, 0}, destination.extent}; + EndActiveRenderPassOn(frame.commandBuffer); vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, target->pipeline); const VkViewport viewport{0.0f, 0.0f, static_cast(destination.extent.width), diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp index d3ab8555..323b2d00 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDepthResolveProbe.cpp @@ -11,6 +11,7 @@ // PrimitivesGeneratedNoXfbProbe.cpp): one throwaway command buffer, a bounded fence wait that // leaks rather than idle-waits a hung GPU, teardown on every other path. +#include "RenderPassGuard.h" #include "WireDepthResolveProbe.h" #include @@ -237,6 +238,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // The RECTANGLE is the render area, exactly as the render-pass arm's renderArea is - the // scratch outside it is never read. begin.renderArea = rect; + EndActiveRenderPassOn(commandBuffer); vkCmdBeginRenderPass(commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); // THE VIEWPORT IS THE WHOLE ATTACHMENT AND THE SCISSOR IS THE RECT, and the two are not @@ -746,6 +748,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { begin.renderPass = clear.pass; begin.framebuffer = clear.framebuffer; begin.renderArea = fullRect; + EndActiveRenderPassOn(cmd); vkCmdBeginRenderPass(cmd, &begin, VK_SUBPASS_CONTENTS_INLINE); VkClearAttachment attachment{}; attachment.aspectMask = clear.aspects; @@ -772,6 +775,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { begin.renderPass = item.resolvePass; begin.framebuffer = item.resolveFramebuffer; begin.renderArea = fullRect; + EndActiveRenderPassOn(cmd); vkCmdBeginRenderPass(cmd, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdEndRenderPass(cmd); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc index 1059e53b..c7fe2c5e 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireDraw.inc @@ -215,7 +215,10 @@ MakeXfbWritesVisible(); const auto* fbo = state.DrawFramebuffer(); if (!fbo) MagmaWireFatal("draw-framebuffer-record"); - RetireWireDrawPass(); + // THE PREVIOUS DRAW'S RENDER PASS STAYS OPEN until this draw's attachments are known: a + // draw to the same attachments continues it (below), and on a tiler a pass per draw also + // loaded and stored every attachment once per draw. Resource work in between that may not + // sit inside a pass (a barrier, a copy, a clear) closes it itself (RenderPassGuard.h). // CPU preparation may synchronize GPU-produced vertex/index bytes. Do // not rewind an arena while this draw owns as-yet-unrecorded slices. WirePreparationScope preparing(m_wirePreparationDepth); @@ -490,6 +493,7 @@ // attachments below resolve the same index, so this key has to be taken after it. passKey.swapchainImageIndex = DefaultFramebufferWriteIndex(true); } + Vector sameLayoutAttachments; const auto add = [&](const MG_Pipe::MGPSurface& surface, VkImageAspectFlags aspect) -> Uint32 { WireImage image = ResolveWireImage(*fbo, surface, aspect, /*isWriteTarget=*/true); if (!image.image) return VK_ATTACHMENT_UNUSED; @@ -499,7 +503,10 @@ MGB_CTX->IsCapabilityEnabled(CapabilityInput::FramebufferSrgb)); const VkImageLayout layout = aspect == VK_IMAGE_ASPECT_COLOR_BIT ? VK_IMAGE_LAYOUT_GENERAL : VK_IMAGE_LAYOUT_GENERAL; - TransitionWireImage(image, layout); + // Already in its attachment layout: its barrier (which closes any open pass) waits until + // it is known whether the open pass continues with this very attachment. + if (image.layout == layout) sameLayoutAttachments.push_back(image); + else TransitionWireImage(image, layout); VkImageViewCreateInfo info{VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO}; info.image = image.image; info.viewType = image.viewType; @@ -565,7 +572,17 @@ passInfo.pAttachments = attachments.data(); passInfo.subpassCount = 1; passInfo.pSubpasses = &subpass; - if (!AcquireCachedWireDrawPass(passKey)) { + const Bool continuing = m_wireDrawPass && m_wireDrawPassKey == passKey; + { + const auto* active = VkRenderPassManager::GetActiveRenderPass(); + const Bool stillOpen = continuing && active != nullptr && active->commandBuffer == frame.commandBuffer; + // A new pass orders itself after earlier writes to its attachments; a continued one + // is the same pass, whose attachment writes are ordered by rasterization. + if (!stillOpen) + for (auto& image : sameLayoutAttachments) TransitionWireImage(image, VK_IMAGE_LAYOUT_GENERAL); + } + if (!continuing) RetireWireDrawPass(); + if (!continuing && !AcquireCachedWireDrawPass(passKey)) { m_wireDrawPassKey = Move(passKey); Vector views; views.reserve(viewInfos.size()); @@ -629,13 +646,20 @@ vkCmdBindVertexBuffers(frame.commandBuffer, binding++, 1, &slice.buffer, &slice.offset); } if (indexSlice.IsValid()) vkCmdBindIndexBuffer(frame.commandBuffer, indexSlice.buffer, indexSlice.offset, indexType); - if (!VkRenderPassManager::BeginRenderPass(frame.commandBuffer, pass)) { + // Still open from the previous draw (nothing closed it, same command buffer): continue. + const auto* active = VkRenderPassManager::GetActiveRenderPass(); + const Bool open = continuing && active != nullptr && active->commandBuffer == frame.commandBuffer && + active->hash == pass.hash; + if (!open && active != nullptr) VkRenderPassManager::EndRenderPass(frame.commandBuffer); + if (!open && !VkRenderPassManager::BeginRenderPass(frame.commandBuffer, pass)) { MGL_WIRE_DECLINE_AT(BeginRenderPass, "the render pass for this draw could not be begun"); return false; } vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); ApplyDynamicDrawStateTail(frame, pass.extent, fbo->IsDefault, ResolveDrawViewportCount(programObj.writesViewportIndexBuiltin)); + // Storage images this draw writes are what the next sampled-image barrier must cover. + if (programObj.hasStorageImages) BumpWireImageWriteEpoch(); if (MG_Util::PipeStats::Enabled()) MG_Util::PipeStats::AddCalls(MG_Util::PipeStats::CallClass::Draws, 1); return true; } @@ -800,6 +824,7 @@ return; } vkCmdBindPipeline(frame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + EndActiveRenderPassOn(frame.commandBuffer); vkCmdDispatch(frame.commandBuffer, x, y, z); } @@ -878,6 +903,7 @@ return; } vkCmdBindPipeline(dispatchFrame.commandBuffer, VK_PIPELINE_BIND_POINT_COMPUTE, pipeline); + EndActiveRenderPassOn(dispatchFrame.commandBuffer); vkCmdDispatchIndirect(dispatchFrame.commandBuffer, parameters.buffer, parameters.offset + static_cast(offset)); if (MG_Util::PipeStats::Enabled()) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc index 8f3b4829..f79d6fe6 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireFramebuffer.inc @@ -326,6 +326,7 @@ if (!color) clear.clearValue.depthStencil = {payload.depth, payload.stencil}; VkClearRect rect{area, 0, image.layers}; auto& frame = m_frameContext.GetCurrent(); + EndActiveRenderPassOn(frame.commandBuffer); vkCmdBeginRenderPass(frame.commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdClearAttachments(frame.commandBuffer, 1, &clear, 1, &rect); vkCmdEndRenderPass(frame.commandBuffer); @@ -837,6 +838,7 @@ begin.renderPass = pass; begin.framebuffer = framebuffer; begin.renderArea = {{sxMin, syMin}, {static_cast(width), static_cast(height)}}; + EndActiveRenderPassOn(m_frameContext.GetCurrent().commandBuffer); vkCmdBeginRenderPass(m_frameContext.GetCurrent().commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdEndRenderPass(m_frameContext.GetCurrent().commandBuffer); } else if (!resolvedByShader) { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WirePlaceholderImages.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WirePlaceholderImages.inc index 590ce79f..4e26a6af 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WirePlaceholderImages.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WirePlaceholderImages.inc @@ -215,6 +215,7 @@ VkRenderPassBeginInfo begin{VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO}; begin.renderPass = image.clearPass; begin.framebuffer = image.clearFramebuffer; begin.renderArea.extent = {1, 1}; begin.clearValueCount = 1; begin.pClearValues = &clear; + EndActiveRenderPassOn(commands); vkCmdBeginRenderPass(commands, &begin, VK_SUBPASS_CONTENTS_INLINE); vkCmdEndRenderPass(commands); image.layout = VK_IMAGE_LAYOUT_GENERAL; @@ -226,6 +227,7 @@ const VkImageSubresourceRange range{image.aspect, 0, 1, 0, image.layers}; if (shadow) { const VkClearDepthStencilValue depth{0.0f, 0}; + EndActiveRenderPassOn(commands); vkCmdClearDepthStencilImage(commands, image.image, image.layout, &depth, 1, &range); } else vkCmdClearColorImage(commands, image.image, image.layout, &clear.color, 1, &range); if (!VkTextureManager::TransitionImageLayout(commands, image.image, image.layout, @@ -236,6 +238,7 @@ VkMemoryBarrier visible{VK_STRUCTURE_TYPE_MEMORY_BARRIER}; visible.srcAccessMask = multisample ? VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT : VK_ACCESS_TRANSFER_WRITE_BIT; visible.dstAccessMask = VK_ACCESS_SHADER_READ_BIT | VK_ACCESS_SHADER_WRITE_BIT; + EndActiveRenderPassOn(commands); vkCmdPipelineBarrier(commands, multisample ? VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT : VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_ALL_COMMANDS_BIT, 0, 1, &visible, 0, nullptr, 0, nullptr); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.inc b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.inc index 7e4bed59..9ce9e3a4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.inc +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/WireYuvImage.inc @@ -410,6 +410,7 @@ acquire.dstQueueFamilyIndex = family; acquire.image = source->image; acquire.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1}; + EndActiveRenderPassOn(frame.commandBuffer); vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &acquire); @@ -487,6 +488,7 @@ release.newLayout = VK_IMAGE_LAYOUT_GENERAL; release.srcQueueFamilyIndex = family; release.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT; + EndActiveRenderPassOn(frame.commandBuffer); vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &release); // GENERAL, the layout every wire descriptor samples in, with the pass's writes made visible.