[Perf] (DirectVulkan): keep the wire draw's render pass open across draws to the same attachments, closing it only for work that cannot sit inside one

This commit is contained in:
2026-10-04 06:18:35 -04:00
parent e5ee31b022
commit bb31f374a3
15 changed files with 142 additions and 9 deletions
@@ -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 <Includes.h>
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
@@ -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;
@@ -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
@@ -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, &copy);
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, &region);
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<VkDeviceSize>(offset);
region.size = static_cast<VkDeviceSize>(size);
EndActiveRenderPassOn(commandBuffer);
vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, &region);
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<VkDeviceSize>(offset);
region.size = static_cast<VkDeviceSize>(size);
EndActiveRenderPassOn(commandBuffer);
vkCmdCopyBuffer(commandBuffer, staging.buffer, resource.buffer.GetHandle(), 1, &region);
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);
@@ -7,6 +7,8 @@
// End of Source File Header
#include "VkRenderPassManager.h"
#include "RenderPassGuard.h"
#include <atomic>
#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<Uint64> 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
@@ -185,6 +185,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
struct ActiveRenderPassInfo {
Uint64 hash = 0;
Uint64 compatibilityHash = 0;
VkCommandBuffer commandBuffer = VK_NULL_HANDLE;
Vector<TrackedAttachmentLayoutInfo> trackedAttachmentLayouts;
IntVec2 extent = {0, 0};
@@ -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<Uint32>(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<VkDeviceSize>(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, &copy);
}
@@ -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, &copy);
}
@@ -4770,6 +4777,7 @@ namespace MobileGL::MG_Backend::DirectVulkan {
rectCopies[rectCopyCount++] = rectCopy;
runningOffset += static_cast<VkDeviceSize>(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<VkDeviceSize>(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, &copy);
}
@@ -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<Uint32>(dstTexelSize.x()), static_cast<Uint32>(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<SizeT>(indirect));
EndActiveRenderPassOn(frame.commandBuffer);
vkCmdDispatchIndirect(frame.commandBuffer, slice.buffer, slice.offset + static_cast<VkDeviceSize>(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);
@@ -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<float>(destination.extent.width),
@@ -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<float>(destination.extent.width),
@@ -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 <MG_Util/Debug/Log.h>
@@ -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);
}
@@ -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<WireImage> 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<VkImageView> 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<VkDeviceSize>(offset));
if (MG_Util::PipeStats::Enabled())
@@ -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<Uint32>(width), static_cast<Uint32>(height)}};
EndActiveRenderPassOn(m_frameContext.GetCurrent().commandBuffer);
vkCmdBeginRenderPass(m_frameContext.GetCurrent().commandBuffer, &begin, VK_SUBPASS_CONTENTS_INLINE);
vkCmdEndRenderPass(m_frameContext.GetCurrent().commandBuffer);
} else if (!resolvedByShader) {
@@ -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);
}
@@ -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.