From a3b440e7c591f26a3c425910cfa9b6c36b3c574a Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Tue, 17 Feb 2026 00:59:51 +0800 Subject: [PATCH] [Feat] (MG_Backend/DirectVulkan): FrameContext: command buffer lifecycle --- .../DirectVulkan/Renderer/FrameContext.cpp | 48 +++++++++++++++---- .../DirectVulkan/Renderer/FrameContext.h | 5 ++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 16 ++----- 3 files changed, 48 insertions(+), 21 deletions(-) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp index 8417d790..3311f529 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -70,24 +70,51 @@ namespace MobileGL::MG_Backend::DirectVulkan { return m_frames[currentFrameIndex]; } + Bool FrameContext::IsCommandRecording() const { + return GetCurrent().isCommandRecording; + } + void FrameContext::AdvanceToNext() { MOBILEGL_ASSERT(!m_frames.empty(), "FrameContext is not initialized"); currentFrameIndex = (currentFrameIndex + 1) % static_cast(m_frames.size()); + GetCurrent().isCommandRecording = false; GetCurrent().hasCommandBufferRecorded = false; } + VkCommandBuffer& FrameContext::BeginCommandRecording(VkCommandBufferUsageFlags flags, + const VkCommandBufferInheritanceInfo* pInheritanceInfo) { + auto& frame = GetCurrent(); + MOBILEGL_ASSERT(!frame.isCommandRecording, "BeginCommandRecording called while command buffer is already recording"); + + frame.hasCommandBufferRecorded = false; + VK_VERIFY(vkResetCommandBuffer(frame.commandBuffer, 0), "BeginCommandRecording, vkResetCommandBuffer"); + + VkCommandBufferBeginInfo beginInfo{}; + beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; + beginInfo.flags = flags; + beginInfo.pInheritanceInfo = pInheritanceInfo; + VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "BeginCommandRecording, vkBeginCommandBuffer"); + + frame.isCommandRecording = true; + return frame.commandBuffer; + } + + void FrameContext::EndCommandRecording() { + auto& frame = GetCurrent(); + MOBILEGL_ASSERT(frame.isCommandRecording, "EndCommandRecording called without active command buffer recording"); + VK_VERIFY(vkEndCommandBuffer(frame.commandBuffer), "EndCommandRecording, vkEndCommandBuffer"); + frame.isCommandRecording = false; + frame.hasCommandBufferRecorded = true; + } + Bool FrameContext::TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout) { auto& frame = GetCurrent(); - if (frame.hasCommandBufferRecorded || oldLayout == presentLayout || + if (frame.hasCommandBufferRecorded || frame.isCommandRecording || oldLayout == presentLayout || oldLayout == VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) { return false; } - VK_VERIFY(vkResetCommandBuffer(frame.commandBuffer, 0), "TransitionToPresent, vkResetCommandBuffer"); - - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - VK_VERIFY(vkBeginCommandBuffer(frame.commandBuffer, &beginInfo), "TransitionToPresent, vkBeginCommandBuffer"); + auto& commandBuffer = BeginCommandRecording(); VkImageMemoryBarrier presentBarrier{}; presentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; @@ -103,15 +130,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { presentBarrier.subresourceRange.levelCount = 1; presentBarrier.subresourceRange.baseArrayLayer = 0; presentBarrier.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(frame.commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, - 0, 0, nullptr, 0, nullptr, 1, &presentBarrier); + vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, + nullptr, 0, nullptr, 1, &presentBarrier); - VK_VERIFY(vkEndCommandBuffer(frame.commandBuffer), "TransitionToPresent, vkEndCommandBuffer"); + EndCommandRecording(); return true; } FrameContext::SubmitInfoPacket FrameContext::GetSubmitInfo(Bool shouldSubmitCommandBuffer) const { const auto& frame = GetCurrent(); + MOBILEGL_ASSERT(!frame.isCommandRecording, "GetSubmitInfo called while command buffer recording is still active"); SubmitInfoPacket packet{}; packet.waitSemaphore = frame.imageAvailableSemaphore; packet.signalSemaphore = frame.renderFinishedSemaphore; @@ -202,6 +230,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } frame.hasCommandBufferRecorded = false; + frame.isCommandRecording = false; return VK_SUCCESS; } @@ -222,6 +251,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkDestroySemaphore(device, frame.imageAvailableSemaphore, nullptr); } frame.imageAvailableSemaphore = VK_NULL_HANDLE; + frame.isCommandRecording = false; frame.hasCommandBufferRecorded = false; } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h index 2616727d..5924dd26 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h @@ -34,6 +34,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkSemaphore imageAvailableSemaphore = VK_NULL_HANDLE; VkSemaphore renderFinishedSemaphore = VK_NULL_HANDLE; VkFence imageInFlightFence = VK_NULL_HANDLE; + Bool isCommandRecording = false; Bool hasCommandBufferRecorded = false; }; @@ -43,7 +44,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Lifecycle functions FrameData& GetCurrent(); const FrameData& GetCurrent() const; + Bool IsCommandRecording() const; void AdvanceToNext(); + VkCommandBuffer& BeginCommandRecording(VkCommandBufferUsageFlags flags = 0, + const VkCommandBufferInheritanceInfo* pInheritanceInfo = nullptr); + void EndCommandRecording(); Bool TransitionToPresent(VkImage image, VkImageLayout oldLayout, VkImageLayout presentLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); SubmitInfoPacket GetSubmitInfo(Bool shouldSubmitCommandBuffer) const; diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index c773ea60..88113432 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -248,16 +248,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VulkanRenderer::Render() { - auto& frame = m_frameContext.GetCurrent(); - VkCommandBuffer& commandBuffer = frame.commandBuffer; - VK_VERIFY(vkResetCommandBuffer(commandBuffer, 0)); - - // Begin command buffer - VkCommandBufferBeginInfo beginInfo{}; - beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - beginInfo.flags = 0; - beginInfo.pInheritanceInfo = nullptr; - VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo)); + VkCommandBuffer& commandBuffer = m_frameContext.BeginCommandRecording(); // Begin render pass VkRenderPassBeginInfo renderPassInfo{}; @@ -295,8 +286,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { vkCmdEndRenderPass(commandBuffer); // End command buffer - VK_VERIFY(vkEndCommandBuffer(commandBuffer)); - frame.hasCommandBufferRecorded = true; + m_frameContext.EndCommandRecording(); } void VulkanRenderer::Present() { @@ -311,6 +301,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { // 1) Submit current frame work. auto submitPacket = m_frameContext.GetSubmitInfo(shouldSubmitCommandBuffer); VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitPacket.submitInfo, frame.imageInFlightFence)); + frame.isCommandRecording = false; frame.hasCommandBufferRecorded = false; m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); @@ -848,6 +839,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { CreateDefaultRenderPass(); CreateDefaultFramebuffers(); if (m_frameContext.GetFrameCount() > 0) { + m_frameContext.GetCurrent().isCommandRecording = false; m_frameContext.GetCurrent().hasCommandBufferRecorded = false; } }