From 963f4d81777f518e600c6a26e433d59053c26277 Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Sun, 4 Oct 2026 06:02:43 -0400 Subject: [PATCH] [Perf] (DirectVulkan): reuse completed command buffers instead of freeing and allocating one per submission --- .../DirectVulkan/Renderer/FrameContext.cpp | 43 ++++++++++++++----- .../DirectVulkan/Renderer/FrameContext.h | 5 +++ 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp index bbf0dfbd..b225af0a 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -66,7 +66,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { FreeRetiredCommandBuffers(frame); } vkFreeCommandBuffers(device, commandPool, frameCount * 2, commandBuffers.data()); + if (!m_spareCommandBuffers.empty()) + vkFreeCommandBuffers(device, commandPool, static_cast(m_spareCommandBuffers.size()), + m_spareCommandBuffers.data()); } + m_spareCommandBuffers.clear(); m_frames.clear(); currentFrameIndex = 0; m_device = VK_NULL_HANDLE; @@ -362,21 +366,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { MOBILEGL_ASSERT(!frame.isPreCommandRecording, "RetireCurrentCommandBuffer called while the pre-pass stream is still recording"); - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = m_commandPool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = 1; VkCommandBuffer replacement = VK_NULL_HANDLE; - VkResult result = vkAllocateCommandBuffers(m_device, &allocInfo, &replacement); + VkResult result = TakeCommandBuffer(replacement); if (result != VK_SUCCESS) { return result; } if (retirePreCommandBuffer) { VkCommandBuffer preReplacement = VK_NULL_HANDLE; - result = vkAllocateCommandBuffers(m_device, &allocInfo, &preReplacement); + result = TakeCommandBuffer(preReplacement); if (result != VK_SUCCESS) { - vkFreeCommandBuffers(m_device, m_commandPool, 1, &replacement); + RecycleCommandBuffer(replacement); return result; } frame.retiredCommandBuffers.push_back({frame.preCommandBuffer, frame.lastSubmitIndex}); @@ -411,8 +410,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { SizeT completedCount = 0; while (completedCount < frame.retiredCommandBuffers.size() && frame.retiredCommandBuffers[completedCount].submitIndex <= completedSubmitIndex) { - vkFreeCommandBuffers(m_device, m_commandPool, 1, - &frame.retiredCommandBuffers[completedCount].commandBuffer); + RecycleCommandBuffer(frame.retiredCommandBuffers[completedCount].commandBuffer); ++completedCount; } if (completedCount > 0) { @@ -422,6 +420,31 @@ namespace MobileGL::MG_Backend::DirectVulkan { } } + VkResult FrameContext::TakeCommandBuffer(VkCommandBuffer& out) { + if (!m_spareCommandBuffers.empty()) { + out = m_spareCommandBuffers.back(); + m_spareCommandBuffers.pop_back(); + return VK_SUCCESS; + } + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = m_commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = 1; + return vkAllocateCommandBuffers(m_device, &allocInfo, &out); + } + + void FrameContext::RecycleCommandBuffer(VkCommandBuffer commandBuffer) { + // A handful covers the buffers in flight between two completions; beyond that, free. + constexpr SizeT kSpareCommandBuffers = 8; + if (commandBuffer == VK_NULL_HANDLE) return; + if (m_spareCommandBuffers.size() < kSpareCommandBuffers) { + m_spareCommandBuffers.push_back(commandBuffer); + return; + } + vkFreeCommandBuffers(m_device, m_commandPool, 1, &commandBuffer); + } + void FrameContext::FreeAllRetiredCommandBuffers() { for (auto& frame : m_frames) { FreeRetiredCommandBuffers(frame); diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h index 50793027..c0a6fa84 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h @@ -150,5 +150,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Stored at Initialize for retired-command-buffer management. VkDevice m_device = VK_NULL_HANDLE; VkCommandPool m_commandPool = VK_NULL_HANDLE; + // Completed command buffers kept for the next retirement instead of being freed and + // reallocated: the pool resets a buffer implicitly at its next vkBeginCommandBuffer. + Vector m_spareCommandBuffers; + VkResult TakeCommandBuffer(VkCommandBuffer& out); + void RecycleCommandBuffer(VkCommandBuffer commandBuffer); }; } // namespace MobileGL::MG_Backend::DirectVulkan