From 8f57e837b8ec6d0b8f2a60b0cb5f51c0b0ee81cf Mon Sep 17 00:00:00 2001 From: Swung0x48 Date: Mon, 16 Feb 2026 11:56:05 +0800 Subject: [PATCH] [Chore] (MG_Test/Backend/DirectVulkan): encapsulate FrameContext (cont.) --- CMakeLists.txt | 1 + .../DirectVulkan/Renderer/FrameContext.cpp | 240 ++++++++++++++++++ .../DirectVulkan/Renderer/FrameContext.h | 67 +++++ .../DirectVulkan/Renderer/VulkanRenderer.cpp | 120 +++------ .../DirectVulkan/Renderer/VulkanRenderer.h | 6 +- .../MG_Test/Backend/DirectVulkan/TestExec.cpp | 5 +- 6 files changed, 351 insertions(+), 88 deletions(-) create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp create mode 100644 MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 875d1b4c..43d72c1f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -210,6 +210,7 @@ set(SOURCE_FILES MobileGL/MG_Backend/DirectVulkan/DirectVulkan.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp MobileGL/MG_Backend/DirectVulkan/Renderer/SwapchainObject.cpp + MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp # MobileGL/MG_Backend/DirectVulkan/Managers/ProgramManager.cpp # MobileGL/MG_Backend/DirectVulkan/Managers/VertexInputStateManager.cpp # MobileGL/MG_Backend/DirectVulkan/Managers/RenderStateManager.cpp diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp new file mode 100644 index 00000000..212f5f1f --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp @@ -0,0 +1,240 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.cpp +// 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 + +#include "FrameContext.h" + +namespace MobileGL::MG_Backend::DirectVulkan { + VkResult FrameContext::Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount) { + Destroy(device, commandPool); + commandBuffers.resize(frameCount, VK_NULL_HANDLE); + imageAvailableSemaphores.resize(frameCount, VK_NULL_HANDLE); + renderFinishedSemaphores.resize(frameCount, VK_NULL_HANDLE); + imageInFlightFences.resize(frameCount, VK_NULL_HANDLE); + hasCommandBufferRecorded.assign(frameCount, false); + currentFrameIndex = 0; + + VkCommandBufferAllocateInfo allocInfo{}; + allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; + allocInfo.commandPool = commandPool; + allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; + allocInfo.commandBufferCount = frameCount; + VkResult result = vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data()); + if (result != VK_SUCCESS) { + return result; + } + + VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; + VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; + fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; + + for (Uint32 i = 0; i < frameCount; ++i) { + result = CreateSyncObjectsForFrame(device, i, semaphoreInfo, fenceInfo); + if (result != VK_SUCCESS) { + Destroy(device, commandPool); + return result; + } + } + + return VK_SUCCESS; + } + + void FrameContext::Destroy(VkDevice device, VkCommandPool commandPool) { + const Uint32 frameCount = static_cast(commandBuffers.size()); + for (Uint32 i = 0; i < frameCount; ++i) { + DestroySyncObjectsForFrame(device, i); + } + if (device != VK_NULL_HANDLE && commandPool != VK_NULL_HANDLE && !commandBuffers.empty()) { + vkFreeCommandBuffers(device, commandPool, frameCount, commandBuffers.data()); + } + commandBuffers.clear(); + imageAvailableSemaphores.clear(); + renderFinishedSemaphores.clear(); + imageInFlightFences.clear(); + hasCommandBufferRecorded.clear(); + currentFrameIndex = 0; + } + + void FrameContext::AdvanceFrame() { + MOBILEGL_ASSERT(!commandBuffers.empty(), "FrameContext is not initialized"); + currentFrameIndex = (currentFrameIndex + 1) % static_cast(commandBuffers.size()); + } + + Uint32 FrameContext::GetCurrentFrameIndex() const { + return currentFrameIndex; + } + + Uint32 FrameContext::GetFrameCount() const { + return static_cast(commandBuffers.size()); + } + + VkCommandBuffer& FrameContext::GetCommandBuffer(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + return commandBuffers[frameIndex]; + } + + VkSemaphore& FrameContext::GetImageAvailableSemaphore(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + return imageAvailableSemaphores[frameIndex]; + } + + VkSemaphore& FrameContext::GetRenderFinishedSemaphore(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + return renderFinishedSemaphores[frameIndex]; + } + + VkFence& FrameContext::GetImageInFlightFence(Uint32 frameIndex) { + AssertValidFrameIndex(frameIndex); + return imageInFlightFences[frameIndex]; + } + + void FrameContext::SetHasCommandBufferRecorded(Uint32 frameIndex, Bool value) { + AssertValidFrameIndex(frameIndex); + hasCommandBufferRecorded[frameIndex] = value; + } + + void FrameContext::SetCurrentCommandBuffer(VkCommandBuffer value) { + SetCurrentHasCommandBufferRecorded(false); + GetCommandBuffer(GetCurrentFrameIndex()) = value; + } + + void FrameContext::SetCurrentImageAvailableSemaphore(VkSemaphore value) { + GetImageAvailableSemaphore(GetCurrentFrameIndex()) = value; + } + + void FrameContext::SetCurrentRenderFinishedSemaphore(VkSemaphore value) { + GetRenderFinishedSemaphore(GetCurrentFrameIndex()) = value; + } + + void FrameContext::SetCurrentImageInFlightFence(VkFence value) { + GetImageInFlightFence(GetCurrentFrameIndex()) = value; + } + + void FrameContext::SetCurrentHasCommandBufferRecorded(Bool value) { + SetHasCommandBufferRecorded(GetCurrentFrameIndex(), value); + } + + VkCommandBuffer& FrameContext::GetCurrentCommandBuffer() { + return GetCommandBuffer(GetCurrentFrameIndex()); + } + + VkSemaphore& FrameContext::GetCurrentImageAvailableSemaphore() { + return GetImageAvailableSemaphore(GetCurrentFrameIndex()); + } + + VkSemaphore& FrameContext::GetCurrentRenderFinishedSemaphore() { + return GetRenderFinishedSemaphore(GetCurrentFrameIndex()); + } + + VkFence& FrameContext::GetCurrentImageInFlightFence() { + return GetImageInFlightFence(GetCurrentFrameIndex()); + } + + Bool FrameContext::GetCurrentHasCommandBufferRecorded() const { + return GetHasCommandBufferRecorded(GetCurrentFrameIndex()); + } + + const VkCommandBuffer& FrameContext::GetCommandBuffer(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return commandBuffers[frameIndex]; + } + + const VkSemaphore& FrameContext::GetImageAvailableSemaphore(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return imageAvailableSemaphores[frameIndex]; + } + + const VkSemaphore& FrameContext::GetRenderFinishedSemaphore(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return renderFinishedSemaphores[frameIndex]; + } + + const VkFence& FrameContext::GetImageInFlightFence(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return imageInFlightFences[frameIndex]; + } + + Bool FrameContext::GetHasCommandBufferRecorded(Uint32 frameIndex) const { + AssertValidFrameIndex(frameIndex); + return hasCommandBufferRecorded[frameIndex]; + } + + const VkCommandBuffer& FrameContext::GetCurrentCommandBuffer() const { + return GetCommandBuffer(GetCurrentFrameIndex()); + } + + const VkSemaphore& FrameContext::GetCurrentImageAvailableSemaphore() const { + return GetImageAvailableSemaphore(GetCurrentFrameIndex()); + } + + const VkSemaphore& FrameContext::GetCurrentRenderFinishedSemaphore() const { + return GetRenderFinishedSemaphore(GetCurrentFrameIndex()); + } + + const VkFence& FrameContext::GetCurrentImageInFlightFence() const { + return GetImageInFlightFence(GetCurrentFrameIndex()); + } + + void FrameContext::ResetPerFrameState() { + for (SizeT i = 0; i < hasCommandBufferRecorded.size(); ++i) { + hasCommandBufferRecorded[i] = false; + } + } + + void FrameContext::AssertValidFrameIndex(Uint32 frameIndex) const { + MOBILEGL_ASSERT(frameIndex < commandBuffers.size(), "FrameContext index out of range"); + } + + VkResult FrameContext::CreateSyncObjectsForFrame(VkDevice device, Uint32 frameIndex, + const VkSemaphoreCreateInfo& semaphoreInfo, + const VkFenceCreateInfo& fenceInfo) { + DestroySyncObjectsForFrame(device, frameIndex); + + VkResult result = + vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphores[frameIndex]); + if (result != VK_SUCCESS) { + return result; + } + + result = vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphores[frameIndex]); + if (result != VK_SUCCESS) { + vkDestroySemaphore(device, imageAvailableSemaphores[frameIndex], nullptr); + imageAvailableSemaphores[frameIndex] = VK_NULL_HANDLE; + return result; + } + + result = vkCreateFence(device, &fenceInfo, nullptr, &imageInFlightFences[frameIndex]); + if (result != VK_SUCCESS) { + vkDestroySemaphore(device, renderFinishedSemaphores[frameIndex], nullptr); + vkDestroySemaphore(device, imageAvailableSemaphores[frameIndex], nullptr); + renderFinishedSemaphores[frameIndex] = VK_NULL_HANDLE; + imageAvailableSemaphores[frameIndex] = VK_NULL_HANDLE; + return result; + } + + hasCommandBufferRecorded[frameIndex] = false; + return VK_SUCCESS; + } + + void FrameContext::DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex) { + if (device != VK_NULL_HANDLE && imageInFlightFences[frameIndex] != VK_NULL_HANDLE) { + vkDestroyFence(device, imageInFlightFences[frameIndex], nullptr); + } + imageInFlightFences[frameIndex] = VK_NULL_HANDLE; + + if (device != VK_NULL_HANDLE && renderFinishedSemaphores[frameIndex] != VK_NULL_HANDLE) { + vkDestroySemaphore(device, renderFinishedSemaphores[frameIndex], nullptr); + } + renderFinishedSemaphores[frameIndex] = VK_NULL_HANDLE; + + if (device != VK_NULL_HANDLE && imageAvailableSemaphores[frameIndex] != VK_NULL_HANDLE) { + vkDestroySemaphore(device, imageAvailableSemaphores[frameIndex], nullptr); + } + imageAvailableSemaphores[frameIndex] = VK_NULL_HANDLE; + hasCommandBufferRecorded[frameIndex] = false; + } +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h new file mode 100644 index 00000000..51567e36 --- /dev/null +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.h @@ -0,0 +1,67 @@ +// MobileGL - MobileGL/MG_Backend/DirectVulkan/Renderer/FrameContext.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 { + class FrameContext { + public: + VkResult Initialize(VkDevice device, VkCommandPool commandPool, Uint32 frameCount); + void Destroy(VkDevice device, VkCommandPool commandPool); + void AdvanceFrame(); + void ResetPerFrameState(); + + Uint32 GetCurrentFrameIndex() const; + Uint32 GetFrameCount() const; + + VkCommandBuffer& GetCommandBuffer(Uint32 frameIndex); + VkSemaphore& GetImageAvailableSemaphore(Uint32 frameIndex); + VkSemaphore& GetRenderFinishedSemaphore(Uint32 frameIndex); + VkFence& GetImageInFlightFence(Uint32 frameIndex); + void SetHasCommandBufferRecorded(Uint32 frameIndex, Bool value); + void SetCurrentCommandBuffer(VkCommandBuffer value); + void SetCurrentImageAvailableSemaphore(VkSemaphore value); + void SetCurrentRenderFinishedSemaphore(VkSemaphore value); + void SetCurrentImageInFlightFence(VkFence value); + void SetCurrentHasCommandBufferRecorded(Bool value); + + VkCommandBuffer& GetCurrentCommandBuffer(); + VkSemaphore& GetCurrentImageAvailableSemaphore(); + VkSemaphore& GetCurrentRenderFinishedSemaphore(); + VkFence& GetCurrentImageInFlightFence(); + Bool GetCurrentHasCommandBufferRecorded() const; + + const VkCommandBuffer& GetCommandBuffer(Uint32 frameIndex) const; + const VkSemaphore& GetImageAvailableSemaphore(Uint32 frameIndex) const; + const VkSemaphore& GetRenderFinishedSemaphore(Uint32 frameIndex) const; + const VkFence& GetImageInFlightFence(Uint32 frameIndex) const; + Bool GetHasCommandBufferRecorded(Uint32 frameIndex) const; + const VkCommandBuffer& GetCurrentCommandBuffer() const; + const VkSemaphore& GetCurrentImageAvailableSemaphore() const; + const VkSemaphore& GetCurrentRenderFinishedSemaphore() const; + const VkFence& GetCurrentImageInFlightFence() const; + + private: + void AssertValidFrameIndex(Uint32 frameIndex) const; + + VkResult CreateSyncObjectsForFrame(VkDevice device, Uint32 frameIndex, + const VkSemaphoreCreateInfo& semaphoreInfo, + const VkFenceCreateInfo& fenceInfo); + void DestroySyncObjectsForFrame(VkDevice device, Uint32 frameIndex); + + Vector commandBuffers; + Vector imageAvailableSemaphores; + Vector renderFinishedSemaphores; + Vector imageInFlightFences; + Vector hasCommandBufferRecorded; + Uint32 currentFrameIndex = 0; + }; +} // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp index 6550224e..8ee02fb7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.cpp @@ -209,22 +209,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("PrepareDemoPipeline completed"); } - void VulkanRenderer::CreateSyncObjects() { - VkSemaphoreCreateInfo semaphoreInfo{VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; - VkFenceCreateInfo fenceInfo{VK_STRUCTURE_TYPE_FENCE_CREATE_INFO}; - fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; - - m_frameContexts.resize(m_config.MaxFramesInFlight); - - for (SizeT i = 0; i < m_config.MaxFramesInFlight; i++) { - auto& frameContext = m_frameContexts[i]; - VK_VERIFY(vkCreateSemaphore(m_device, &semaphoreInfo, nullptr, &frameContext.imageAvailableSemaphore)); - VK_VERIFY(vkCreateSemaphore(m_device, &semaphoreInfo, nullptr, &frameContext.renderFinishedSemaphore)); - VK_VERIFY(vkCreateFence(m_device, &fenceInfo, nullptr, &frameContext.imageInFlightFence)); - frameContext.hasCommandBufferRecorded = false; - } - - MGLOG_I("CreateSyncObjects completed"); + void VulkanRenderer::CreateFrameContexts() { + VK_VERIFY(m_frameContext.Initialize(m_device, m_commandPool, m_config.MaxFramesInFlight), + "CreateFrameContexts"); + MGLOG_I("CreateFrameContexts completed"); } void VulkanRenderer::Initialize() { @@ -234,19 +222,17 @@ namespace MobileGL::MG_Backend::DirectVulkan { CreateLogicalDeviceAndQueues(); CreateCommandPool(); - CreateCommandBuffers(); RecreateSwapchain(); PrepareDemoPipeline(); - CreateSyncObjects(); + CreateFrameContexts(); // Prime the first frame so Render() always targets an acquired swapchain image. - auto& frameContext = m_frameContexts[m_currentFrameIndex]; - VK_VERIFY(vkWaitForFences(m_device, 1, &frameContext.imageInFlightFence, VK_TRUE, UINT64_MAX)); - VK_VERIFY(vkResetFences(m_device, 1, &frameContext.imageInFlightFence)); + VK_VERIFY(vkWaitForFences(m_device, 1, &m_frameContext.GetCurrentImageInFlightFence(), VK_TRUE, UINT64_MAX)); + VK_VERIFY(vkResetFences(m_device, 1, &m_frameContext.GetCurrentImageInFlightFence())); VK_VERIFY(vkAcquireNextImageKHR(m_device, m_swapchainObject.GetHandle(), UINT64_MAX, - frameContext.imageAvailableSemaphore, VK_NULL_HANDLE, + m_frameContext.GetCurrentImageAvailableSemaphore(), VK_NULL_HANDLE, &m_imageIndexAcquired)); MGLOG_D("VulkanRenderer initialized"); @@ -255,22 +241,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { void VulkanRenderer::Shutdown() { VK_VERIFY(vkDeviceWaitIdle(m_device)); - for (auto& frameContext : m_frameContexts) { - if (frameContext.imageInFlightFence != VK_NULL_HANDLE) { - vkDestroyFence(m_device, frameContext.imageInFlightFence, nullptr); - frameContext.imageInFlightFence = VK_NULL_HANDLE; - } - if (frameContext.renderFinishedSemaphore != VK_NULL_HANDLE) { - vkDestroySemaphore(m_device, frameContext.renderFinishedSemaphore, nullptr); - frameContext.renderFinishedSemaphore = VK_NULL_HANDLE; - } - if (frameContext.imageAvailableSemaphore != VK_NULL_HANDLE) { - vkDestroySemaphore(m_device, frameContext.imageAvailableSemaphore, nullptr); - frameContext.imageAvailableSemaphore = VK_NULL_HANDLE; - } - frameContext.hasCommandBufferRecorded = false; - } - m_frameContexts.clear(); + m_frameContext.Destroy(m_device, m_commandPool); if (m_pipeline != VK_NULL_HANDLE) { vkDestroyPipeline(m_device, m_pipeline, nullptr); @@ -310,15 +281,15 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VulkanRenderer::Render() { - auto& frameContext = m_frameContexts[m_currentFrameIndex]; - VK_VERIFY(vkResetCommandBuffer(frameContext.commandBuffer, 0)); + VkCommandBuffer& commandBuffer = m_frameContext.GetCurrentCommandBuffer(); + 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(frameContext.commandBuffer, &beginInfo)); + VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo)); // Begin render pass VkRenderPassBeginInfo renderPassInfo{}; @@ -331,10 +302,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; - vkCmdBeginRenderPass(frameContext.commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); + vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); // Render commands - vkCmdBindPipeline(frameContext.commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline); + vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, m_pipeline); VkViewport viewport{}; viewport.x = 0.0f; @@ -343,28 +314,28 @@ namespace MobileGL::MG_Backend::DirectVulkan { viewport.height = static_cast(swapchainExtent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; - vkCmdSetViewport(frameContext.commandBuffer, 0, 1, &viewport); + vkCmdSetViewport(commandBuffer, 0, 1, &viewport); VkRect2D scissor{}; scissor.offset = {0, 0}; scissor.extent = swapchainExtent; - vkCmdSetScissor(frameContext.commandBuffer, 0, 1, &scissor); + vkCmdSetScissor(commandBuffer, 0, 1, &scissor); - vkCmdDraw(frameContext.commandBuffer, 3, 1, 0, 0); + vkCmdDraw(commandBuffer, 3, 1, 0, 0); // End render pass - vkCmdEndRenderPass(frameContext.commandBuffer); + vkCmdEndRenderPass(commandBuffer); // End command buffer - VK_VERIFY(vkEndCommandBuffer(frameContext.commandBuffer)); - frameContext.hasCommandBufferRecorded = true; + VK_VERIFY(vkEndCommandBuffer(commandBuffer)); + m_frameContext.SetCurrentHasCommandBufferRecorded(true); } void VulkanRenderer::Present() { MOBILEGL_ASSERT(m_imageIndexAcquired < m_swapchainObject.GetImageCount(), "Present, acquired image index out of range"); - auto& frameContext = m_frameContexts[m_currentFrameIndex]; - const Bool hasRecordedWork = frameContext.hasCommandBufferRecorded; + VkCommandBuffer& commandBuffer = m_frameContext.GetCurrentCommandBuffer(); + const Bool hasRecordedWork = m_frameContext.GetCurrentHasCommandBufferRecorded(); Bool needsLayoutTransitionForPresent = false; if (!hasRecordedWork) { @@ -373,12 +344,12 @@ namespace MobileGL::MG_Backend::DirectVulkan { acquiredImageLayout != VK_IMAGE_LAYOUT_SHARED_PRESENT_KHR) { needsLayoutTransitionForPresent = true; - VK_VERIFY(vkResetCommandBuffer(frameContext.commandBuffer, 0), + VK_VERIFY(vkResetCommandBuffer(commandBuffer, 0), "Present, vkResetCommandBuffer(layout transition)"); VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; - VK_VERIFY(vkBeginCommandBuffer(frameContext.commandBuffer, &beginInfo), + VK_VERIFY(vkBeginCommandBuffer(commandBuffer, &beginInfo), "Present, vkBeginCommandBuffer(layout transition)"); VkImageMemoryBarrier presentBarrier{}; @@ -395,11 +366,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { presentBarrier.subresourceRange.levelCount = 1; presentBarrier.subresourceRange.baseArrayLayer = 0; presentBarrier.subresourceRange.layerCount = 1; - vkCmdPipelineBarrier(frameContext.commandBuffer, VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, + 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(frameContext.commandBuffer), + VK_VERIFY(vkEndCommandBuffer(commandBuffer), "Present, vkEndCommandBuffer(layout transition)"); } } @@ -409,18 +380,18 @@ namespace MobileGL::MG_Backend::DirectVulkan { // 1) Submit current frame work. VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; - VkSemaphore waitSemaphores[] = {frameContext.imageAvailableSemaphore}; + VkSemaphore waitSemaphores[] = {m_frameContext.GetCurrentImageAvailableSemaphore()}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = shouldSubmitCommandBuffer ? 1U : 0U; - submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &frameContext.commandBuffer : nullptr; - VkSemaphore signalSemaphores[] = {frameContext.renderFinishedSemaphore}; + submitInfo.pCommandBuffers = shouldSubmitCommandBuffer ? &commandBuffer : nullptr; + VkSemaphore signalSemaphores[] = {m_frameContext.GetCurrentRenderFinishedSemaphore()}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; - VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, frameContext.imageInFlightFence)); - frameContext.hasCommandBufferRecorded = false; + VK_VERIFY(vkQueueSubmit(m_graphicsQueue, 1, &submitInfo, m_frameContext.GetCurrentImageInFlightFence())); + m_frameContext.SetCurrentHasCommandBufferRecorded(false); m_swapchainObject.SetImageLayout(m_imageIndexAcquired, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR); // 2) Present current frame. @@ -442,16 +413,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { VK_VERIFY(result, "Present, vkQueuePresentKHR"); // 3) Advance frame slot. - m_currentFrameIndex = (m_currentFrameIndex + 1) % m_config.MaxFramesInFlight; + m_frameContext.AdvanceFrame(); // 4) Wait/reset/acquire for next frame. - auto& nextFrameContext = m_frameContexts[m_currentFrameIndex]; - VK_VERIFY(vkWaitForFences(m_device, 1, &nextFrameContext.imageInFlightFence, VK_TRUE, UINT64_MAX), + VK_VERIFY(vkWaitForFences(m_device, 1, &m_frameContext.GetCurrentImageInFlightFence(), VK_TRUE, UINT64_MAX), "Present, vkWaitForFences"); - VK_VERIFY(vkResetFences(m_device, 1, &nextFrameContext.imageInFlightFence), "Present, vkResetFences"); + VK_VERIFY(vkResetFences(m_device, 1, &m_frameContext.GetCurrentImageInFlightFence()), + "Present, vkResetFences"); result = vkAcquireNextImageKHR(m_device, m_swapchainObject.GetHandle(), UINT64_MAX, - nextFrameContext.imageAvailableSemaphore, VK_NULL_HANDLE, + m_frameContext.GetCurrentImageAvailableSemaphore(), VK_NULL_HANDLE, &m_imageIndexAcquired); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { MGLOG_D("Present, vkAcquireNextImageKHR got %d, recreating swapchain", result); @@ -804,21 +775,6 @@ namespace MobileGL::MG_Backend::DirectVulkan { MGLOG_I("Command pool created"); } - void VulkanRenderer::CreateCommandBuffers() { - m_frameContexts.resize(m_config.MaxFramesInFlight); - Vector commandBuffers(m_config.MaxFramesInFlight, VK_NULL_HANDLE); - VkCommandBufferAllocateInfo allocInfo{}; - allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; - allocInfo.commandPool = m_commandPool; - allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; - allocInfo.commandBufferCount = m_config.MaxFramesInFlight; - VK_VERIFY(vkAllocateCommandBuffers(m_device, &allocInfo, commandBuffers.data())); - for (SizeT i = 0; i < m_config.MaxFramesInFlight; ++i) { - m_frameContexts[i].commandBuffer = commandBuffers[i]; - } - MGLOG_I("Command buffer created"); - } - void VulkanRenderer::CreateDefaultRenderPass() { VkAttachmentDescription color{}; color.format = m_swapchainObject.GetSurfaceFormat().format; @@ -986,9 +942,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { CreateSwapchain(); CreateDefaultRenderPass(); CreateDefaultFramebuffers(); - for (auto& frameContext : m_frameContexts) { - frameContext.hasCommandBufferRecorded = false; - } + m_frameContext.ResetPerFrameState(); } } // namespace MobileGL::MG_Backend::DirectVulkan diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h index dfe7382d..40de30a4 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VulkanRenderer.h @@ -85,8 +85,7 @@ namespace MobileGL::MG_Backend::DirectVulkan { VkPipeline m_pipeline = VK_NULL_HANDLE; Uint m_imageIndexAcquired = 0; - Uint m_currentFrameIndex = 0; - Vector m_frameContexts; + FrameContext m_frameContext; void CreateInstance(); VkResult SetupDebugMessenger(); @@ -97,11 +96,10 @@ namespace MobileGL::MG_Backend::DirectVulkan { void CreateLogicalDeviceAndQueues(); void CreateSwapchain(); void CreateCommandPool(); - void CreateCommandBuffers(); + void CreateFrameContexts(); void CreateDefaultRenderPass(); void CreateDefaultFramebuffers(); void PrepareDemoPipeline(); - void CreateSyncObjects(); void ShutdownSwapchain(); diff --git a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp index ca19470a..2914b2bf 100644 --- a/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp +++ b/MobileGL/MG_Test/Backend/DirectVulkan/TestExec.cpp @@ -72,9 +72,12 @@ int main() { EGLSurface surface = eglCreateWindowSurface(display, config, nativewindow, nullptr); eglMakeCurrent(display, surface, surface, context); + int i = 0; while(!glfwWindowShouldClose(window)) { glfwPollEvents(); - MobileGL::MG_Backend::DirectVulkan::pVulkanRenderer->Render(); + + if (i % 2 == 0) + MobileGL::MG_Backend::DirectVulkan::pVulkanRenderer->Render(); eglSwapBuffers(display, surface); }