From d3275b359084a5270fe790a8d2ef1cfb7f5f2596 Mon Sep 17 00:00:00 2001 From: Monika0000 Date: Sat, 27 Dec 2025 14:34:36 +0100 Subject: [PATCH 1/3] (Vulkan) Fixed triple buffering synchronization. --- Core/inc/EvoVulkan/Tools/VulkanTools.h | 3 + Core/inc/EvoVulkan/VulkanKernel.h | 41 +++++-- Core/src/EvoVulkan/Tools/VulkanTools.cpp | 18 +++ Core/src/EvoVulkan/VulkanKernel.cpp | 135 +++++++++++++++++++---- 4 files changed, 169 insertions(+), 28 deletions(-) diff --git a/Core/inc/EvoVulkan/Tools/VulkanTools.h b/Core/inc/EvoVulkan/Tools/VulkanTools.h index ed7da99..11d5a69 100644 --- a/Core/inc/EvoVulkan/Tools/VulkanTools.h +++ b/Core/inc/EvoVulkan/Tools/VulkanTools.h @@ -351,6 +351,9 @@ namespace EvoVulkan::Tools { EVK_MAYBE_UNUSED void DestroySynchronization(const VkDevice& device, Types::Synchronization* sync); EVK_MAYBE_UNUSED Types::Synchronization CreateSynchronization(const VkDevice& device); + EVK_MAYBE_UNUSED VkFence CreateVulkanFence(const VkDevice& device, VkFenceCreateFlags flags); + EVK_MAYBE_UNUSED void DestroyVulkanFence(const VkDevice& device, VkFence* fence); + EVK_MAYBE_UNUSED VkSemaphore CreateVulkanSemaphore(const VkDevice& device); EVK_MAYBE_UNUSED void DestroyVulkanSemaphore(const VkDevice& device, VkSemaphore* semaphore); diff --git a/Core/inc/EvoVulkan/VulkanKernel.h b/Core/inc/EvoVulkan/VulkanKernel.h index 8d7bfca..ffbfd76 100644 --- a/Core/inc/EvoVulkan/VulkanKernel.h +++ b/Core/inc/EvoVulkan/VulkanKernel.h @@ -22,19 +22,31 @@ namespace EvoVulkan::Core { enum class FrameResult : uint8_t { - Error, + None, Success, - OutOfDate, + Error, + Fatal, DeviceLost, + OutOfDate, Dirty, Suboptimal }; enum class RenderResult : uint8_t { - None, Success, Fatal, Error, DeviceLost + None, + Success, + Error, + Fatal, + DeviceLost }; class DLL_EVK_EXPORT VulkanKernel : public Tools::NonCopyable { + protected: + struct FrameSync { + VkSemaphore imageAvailable; // signal: acquire + VkSemaphore renderFinished; // signal: submit + VkFence inFlightFence; // signal: submit done + }; protected: VulkanKernel() = default; @@ -89,6 +101,8 @@ namespace EvoVulkan::Core { EVK_NODISCARD EVK_INLINE Types::RenderPass GetRenderPass() const noexcept { return m_renderPass; } EVK_NODISCARD EVK_INLINE VkFramebuffer* GetFrameBuffers() { return m_frameBuffers.data(); } EVK_NODISCARD EVK_INLINE bool IsMultisamplingEnabled() const noexcept { return m_sampleCount > 1; } + EVK_NODISCARD EVK_INLINE const std::vector& GetFrameSyncs() const noexcept { return m_frames; } + EVK_NODISCARD EVK_INLINE const std::vector& GetInFlightFences() const noexcept { return m_imagesInFlight; } //EVK_NODISCARD EVK_INLINE VkSemaphore GetPresentCompleteSemaphore() const noexcept { return m_syncs.m_presentComplete; } //EVK_NODISCARD EVK_INLINE VkSemaphore GetRenderCompleteSemaphore() const noexcept { return m_syncs.m_renderComplete; } EVK_NODISCARD std::vector& GetWaitSemaphores() { return m_submitInfo.waitSemaphores; } @@ -96,7 +110,9 @@ namespace EvoVulkan::Core { EVK_NODISCARD uint32_t GetCountComputeCmdBuffers() const { return m_countCCB; } EVK_NODISCARD VkCommandBuffer* GetComputeCmdBuffers() const { return m_computeCmdBuffers; } EVK_NODISCARD Types::CmdPool* GetComputeCmdPool() const { return m_computeCmdPool; } - EVK_NODISCARD Types::CmdPool* GetCurrentFrameCmdPool() const { return m_frameCmdPools[m_currentBuffer]; } + //EVK_NODISCARD Types::CmdPool* GetCurrentFrameCmdPool() const { return m_frameCmdPools[m_currentImage]; } + //EVK_NODISCARD Types::CmdPool* GetCurrentFrameCmdPool() const { return m_frameCmdPools[m_frameIndex]; } + EVK_NODISCARD Types::CmdPool* GetCurrentFrameCmdPool() const { return m_frameCmdPools[m_imageIndex]; } EVK_NODISCARD uint8_t GetSampleCount() const; EVK_NODISCARD EvoVulkan::Types::CmdBuffer* CreateSingleTimeCmd() const; @@ -120,8 +136,10 @@ namespace EvoVulkan::Core { EVK_NODISCARD const std::vector& GetSubmitQueue() const { return m_submitQueue; }; EVK_NODISCARD uint16_t GetSwapchainImagesCount() const noexcept { return m_swapchainImages; } EVK_NODISCARD uint16_t GetRequiredSwapchainImagesCount() const noexcept { return m_requiredSwapchainImages; } - EVK_NODISCARD uint8_t GetCurrentFrameIndex() const noexcept { return m_currentBuffer; } - EVK_NODISCARD uint8_t GetCurrentImageIndex() const noexcept { return m_currentImage; } + //EVK_NODISCARD uint8_t GetCurrentFrameIndex() const noexcept { return m_currentImage; } + EVK_NODISCARD uint8_t GetCurrentFrameIndex() const noexcept { return m_frameIndex; } + EVK_NODISCARD uint8_t GetMaxFramesInFlight() const noexcept { return 3; } + EVK_NODISCARD uint8_t GetCurrentImageIndex() const noexcept { return m_imageIndex; } void SetMultisampling(uint32_t sampleCount); void SetSwapchainImagesCount(uint32_t count); @@ -148,6 +166,7 @@ namespace EvoVulkan::Core { bool DestroyDCBuffers(); bool ReCreateDCBuffers(); bool ReCreateFrameBuffers(); + void DestroySynchronizations(); bool ReCreateSynchronizations(); void DestroyFrameBuffers(); @@ -165,6 +184,7 @@ namespace EvoVulkan::Core { bool m_hasErrors = false; bool m_paused = false; bool m_dirty = false; + //bool m_imageAcquiredThisFrame = false; int32_t m_newWidth = -1; int32_t m_newHeight = -1; @@ -196,9 +216,14 @@ namespace EvoVulkan::Core { SubmitInfo m_offscreenSubmitInfo = { }; std::vector m_frameSyncs = { }; + std::vector m_frames; // size = MAX_FRAMES_IN_FLIGHT + std::vector m_imagesInFlight; // size = swapchainImageCount + std::vector m_waitFences = std::vector(); - uint32_t m_currentBuffer = 0; - uint32_t m_currentImage = 0; + //uint32_t m_currentBuffer = 0; + + uint32_t m_frameIndex = 0; + uint32_t m_imageIndex = 0; std::vector m_submitQueue = { }; diff --git a/Core/src/EvoVulkan/Tools/VulkanTools.cpp b/Core/src/EvoVulkan/Tools/VulkanTools.cpp index efa1dd3..058bbb4 100644 --- a/Core/src/EvoVulkan/Tools/VulkanTools.cpp +++ b/Core/src/EvoVulkan/Tools/VulkanTools.cpp @@ -166,6 +166,24 @@ namespace EvoVulkan::Tools { return semaphore; } + VkFence CreateVulkanFence(const VkDevice& device, VkFenceCreateFlags flags) { + VkFence fence = VK_NULL_HANDLE; + VkFenceCreateInfo fenceCreateInfo = Initializers::FenceCreateInfo(flags); + auto result = vkCreateFence(device, &fenceCreateInfo, nullptr, &fence); + if (result != VK_SUCCESS) { + VK_ERROR("Tools::CreateVulkanFence() : failed to create vulkan fence!"); + return VK_NULL_HANDLE; + } + return fence; + } + + void DestroyVulkanFence(const VkDevice& device, VkFence* fence) { + if (fence && *fence != VK_NULL_HANDLE) { + vkDestroyFence(device, *fence, nullptr); + *fence = VK_NULL_HANDLE; + } + } + void DestroyVulkanSemaphore(const VkDevice& device, VkSemaphore* semaphore) { if (semaphore && *semaphore != VK_NULL_HANDLE) { vkDestroySemaphore(device, *semaphore, nullptr); diff --git a/Core/src/EvoVulkan/VulkanKernel.cpp b/Core/src/EvoVulkan/VulkanKernel.cpp index 30f431d..347f94a 100644 --- a/Core/src/EvoVulkan/VulkanKernel.cpp +++ b/Core/src/EvoVulkan/VulkanKernel.cpp @@ -398,12 +398,7 @@ bool EvoVulkan::Core::VulkanKernel::Destroy() { if (m_pipelineCache) Tools::DestroyPipelineCache(*m_device, &m_pipelineCache); - for (auto&& sync : m_frameSyncs) { - if (sync.IsReady()) { - Tools::DestroySynchronization(*m_device, &sync); - } - } - m_frameSyncs.clear(); + DestroySynchronizations(); if (m_renderPass.IsReady()) Types::DestroyRenderPass(m_device, &m_renderPass); @@ -534,9 +529,9 @@ EvoVulkan::Core::RenderResult EvoVulkan::Core::VulkanKernel::NextFrame() { } void EvoVulkan::Core::VulkanKernel::WaitFences() { - if (m_device && !m_waitFences.empty()) { - vkWaitForFences(*m_device, 1, &m_waitFences[m_currentBuffer], VK_TRUE, UINT64_MAX); - } + //if (m_device && !m_waitFences.empty()) { + // vkWaitForFences(*m_device, 1, &m_waitFences[m_currentBuffer], VK_TRUE, UINT64_MAX); + //} } @@ -547,9 +542,17 @@ void EvoVulkan::Core::VulkanKernel::WaitDeviceIdle() { } void EvoVulkan::Core::VulkanKernel::WaitAllFences() { - if (m_device && !m_waitFences.empty()) { - vkWaitForFences(*m_device, static_cast(m_waitFences.size()), m_waitFences.data(), VK_TRUE, UINT64_MAX); + //if (m_device && !m_waitFences.empty()) { + // vkWaitForFences(*m_device, static_cast(m_waitFences.size()), m_waitFences.data(), VK_TRUE, UINT64_MAX); + //} + + for (auto& frame : m_frames) { + vkWaitForFences(*m_device, 1, &frame.inFlightFence, VK_TRUE, UINT64_MAX); } + + //if (!GetInFlightFences().empty()) { + // vkWaitForFences(*GetDevice(), GetInFlightFences().size(), GetInFlightFences().data(), VK_TRUE, UINT64_MAX); + //} } EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { @@ -557,25 +560,64 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { VK_LOG("VulkanKernel::PrepareFrame() : swapchain is dirty!"); } - /// Acquire the next image from the swap chain - VkResult result = m_swapchain->AcquireNextImage(m_frameSyncs[m_currentBuffer].m_presentComplete, &m_currentImage); - /// Recreate the swapchain if it's no longer compatible with the surface (OUT_OF_DATE) or no longer optimal for presentation (SUBOPTIMAL) + /// Note: PrepareFrame() may be called multiple times per frame (e.g., from RenderScene and VulkanKernel) + /// We need to ensure we only acquire a new image once per frame to prevent command buffer mismatches + /// The image should be acquired in the first call, and subsequent calls should use the same image index + //if (m_imageAcquiredThisFrame) { + // m_currentBuffer = m_currentImage; + // return FrameResult::Success; + //} + + //uint32_t previousImage = m_currentImage; + //if (m_waitFences.size() > previousImage && previousImage < GetSwapchainImagesCount()) { + // vkWaitForFences(*m_device, 1, &m_waitFences[previousImage], VK_TRUE, UINT64_MAX); + //} + + FrameSync& frame = m_frames[m_frameIndex]; + + // 1. Ждём, пока этот sync-slot освободится + vkWaitForFences(*m_device, 1, &frame.inFlightFence, VK_TRUE, UINT64_MAX); + + // 2. Получаем image от swapchain + VkResult result = m_swapchain->AcquireNextImage(frame.imageAvailable, &m_imageIndex); if (result == VK_ERROR_OUT_OF_DATE_KHR) { VK_LOG("VulkanKernel::PrepareFrame() : window has been resized!"); return FrameResult::OutOfDate; } else if (result == VK_SUBOPTIMAL_KHR) { VK_LOG("VulkanKernel::PrepareFrame() : window has been suboptimal!"); - return FrameResult::Suboptimal; } else if (result != VK_SUCCESS) { VK_ERROR("VulkanKernel::PrepareFrame() : failed to acquire next image! Reason: " + Tools::Convert::result_to_description(result)); - return FrameResult::Error; } - return FrameResult::Success; + // 3. Если image уже используется — ждём fence + if (m_imagesInFlight[m_imageIndex] != VK_NULL_HANDLE) + { + vkWaitForFences( + *m_device, + 1, + &m_imagesInFlight[m_imageIndex], + VK_TRUE, + UINT64_MAX + ); + } + + // 4. Привязываем image к текущему frame fence + m_imagesInFlight[m_imageIndex] = frame.inFlightFence; + + ///m_currentImage = acquiredImageIndex; + /// + ///if (m_waitFences.size() > m_currentImage && m_currentImage < GetSwapchainImagesCount()) { + ///vkWaitForFences(*m_device, 1, &m_waitFences[m_currentImage], VK_TRUE, UINT64_MAX); + ///} + /// + ///m_currentBuffer = previousImage; + ///m_imageAcquiredThisFrame = true; + + return result == VK_SUBOPTIMAL_KHR ? FrameResult::Suboptimal : FrameResult::Success; } EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::WaitIdle() { @@ -611,9 +653,15 @@ void EvoVulkan::Core::VulkanKernel::WaitComputeIdle() { } EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::QueuePresent() { - VkResult result = m_swapchain->QueuePresent(m_device->GetQueues()->GetGraphicsQueue(), m_currentImage, m_frameSyncs[m_currentBuffer].m_renderComplete); + /// Use m_currentImage for semaphore to match the acquired image index + //VkResult result = m_swapchain->QueuePresent(m_device->GetQueues()->GetGraphicsQueue(), m_currentImage, m_frameSyncs[m_currentImage].m_renderComplete); + + FrameSync& frame = m_frames[m_frameIndex]; + VkResult result = m_swapchain->QueuePresent(m_device->GetQueues()->GetGraphicsQueue(), m_imageIndex, frame.renderFinished); if (result == VK_SUBOPTIMAL_KHR) { + /// Reset the flag for the next frame + // m_imageAcquiredThisFrame = false; return FrameResult::Suboptimal; } @@ -621,6 +669,8 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::QueuePresent() { if (result == VK_ERROR_OUT_OF_DATE_KHR) { /// Swap chain is no longer compatible with the surface and needs to be recreated VK_LOG("VulkanKernel::WaitIdle() : window has been resized!"); + /// Reset the flag for the next frame + //m_imageAcquiredThisFrame = false; return FrameResult::OutOfDate; } else { @@ -628,13 +678,20 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::QueuePresent() { Tools::Convert::result_to_description(result)); if (result == VK_ERROR_DEVICE_LOST) { + /// Reset the flag for the next frame + // m_imageAcquiredThisFrame = false; return FrameResult::DeviceLost; } + /// Reset the flag for the next frame + //m_imageAcquiredThisFrame = false; return FrameResult::Error; } } + /// Reset the flag for the next frame after successful present + // m_imageAcquiredThisFrame = false; + return EvoVulkan::Core::FrameResult::Success; } @@ -647,6 +704,8 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::SubmitFrame() { } bool EvoVulkan::Core::VulkanKernel::ReCreate(FrameResult reason) { + /// Reset the flag when recreating swapchain + // m_imageAcquiredThisFrame = false; VK_LOG("VulkanKernel::ReCreate() : re-creating vulkan kernel..."); if (reason == FrameResult::OutOfDate || reason == FrameResult::Suboptimal) { @@ -727,9 +786,13 @@ bool EvoVulkan::Core::VulkanKernel::ReCreate(FrameResult reason) { VK_GRAPH("VulkanKernel::ReCreate() : re-creating synchronizations..."); if (!ReCreateSynchronizations()) { VK_ERROR("VulkanKernel::ReCreate() : failed to re-create synchronizations!"); + // m_imageAcquiredThisFrame = false; ///< Reset flag on error return false; } + /// Reset the flag after recreating swapchain + // m_imageAcquiredThisFrame = false; + if (!BuildCmdBuffers()) { VK_ERROR("VulkanKernel::ReCreate() : failed to build command buffer!"); return false; @@ -798,7 +861,14 @@ void EvoVulkan::Core::VulkanKernel::SetGUIEnabled(bool enabled) } } -bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations() { +void EvoVulkan::Core::VulkanKernel::DestroySynchronizations() { + for (auto&& frame : m_frames) { + Tools::DestroyVulkanSemaphore(*GetDevice(), &frame.imageAvailable); + Tools::DestroyVulkanSemaphore(*GetDevice(), &frame.renderFinished); + Tools::DestroyVulkanFence(*GetDevice(), &frame.inFlightFence); + } + m_frames.clear(); + for (auto&& sync : m_frameSyncs) { if (sync.IsReady()) { Tools::DestroySynchronization(*m_device, &sync); @@ -806,8 +876,14 @@ bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations() { } m_frameSyncs.clear(); - m_frameSyncs.resize(m_swapchain ? m_swapchain->GetCountImages() : 0); + for (auto&& imageFence : m_imagesInFlight) { + Tools::DestroyVulkanFence(*GetDevice(), &imageFence); + } + m_imagesInFlight.clear(); +} +bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations() { + m_frameSyncs.resize(m_swapchain ? m_swapchain->GetCountImages() : 0); for (auto& sync : m_frameSyncs) { sync = Tools::CreateSynchronization(*m_device); if (!sync.IsReady()) { @@ -816,6 +892,25 @@ bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations() { } } + m_frames.resize(GetMaxFramesInFlight()); + for (auto& frame : m_frames) { + frame.imageAvailable = Tools::CreateVulkanSemaphore(*m_device); + frame.renderFinished = Tools::CreateVulkanSemaphore(*m_device); + frame.inFlightFence = Tools::CreateVulkanFence(*m_device, VK_FENCE_CREATE_SIGNALED_BIT); + + if (!frame.imageAvailable || !frame.renderFinished || !frame.inFlightFence) { + VK_ERROR("VulkanKernel::ReCreateSynchronizations() : failed to create frame synchronization objects!"); + return false; + } + } + + + m_imagesInFlight.resize(m_swapchain ? m_swapchain->GetCountImages() : 0, VK_NULL_HANDLE); + for (auto& imageFence : m_imagesInFlight) { + imageFence = Tools::CreateVulkanFence(*m_device, VK_FENCE_CREATE_SIGNALED_BIT); + } + + /// Set up submit info structure /// Semaphores will stay the same during application lifetime /// Command buffer submission info is set by each example From 0fa4fe4a77913ea68a0bac678f7d9896a9c46eff Mon Sep 17 00:00:00 2001 From: Monika0000 Date: Sat, 27 Dec 2025 15:41:40 +0100 Subject: [PATCH 2/3] (Vulkan) Fixed fence and semaphore memory leak. --- Core/inc/EvoVulkan/VulkanKernel.h | 8 ++-- Core/src/EvoVulkan/VulkanKernel.cpp | 71 +++++++++++++++++------------ 2 files changed, 46 insertions(+), 33 deletions(-) diff --git a/Core/inc/EvoVulkan/VulkanKernel.h b/Core/inc/EvoVulkan/VulkanKernel.h index ffbfd76..73e93f8 100644 --- a/Core/inc/EvoVulkan/VulkanKernel.h +++ b/Core/inc/EvoVulkan/VulkanKernel.h @@ -138,7 +138,7 @@ namespace EvoVulkan::Core { EVK_NODISCARD uint16_t GetRequiredSwapchainImagesCount() const noexcept { return m_requiredSwapchainImages; } //EVK_NODISCARD uint8_t GetCurrentFrameIndex() const noexcept { return m_currentImage; } EVK_NODISCARD uint8_t GetCurrentFrameIndex() const noexcept { return m_frameIndex; } - EVK_NODISCARD uint8_t GetMaxFramesInFlight() const noexcept { return 3; } + EVK_NODISCARD uint8_t GetMaxFramesInFlight() const noexcept; EVK_NODISCARD uint8_t GetCurrentImageIndex() const noexcept { return m_imageIndex; } void SetMultisampling(uint32_t sampleCount); @@ -166,8 +166,8 @@ namespace EvoVulkan::Core { bool DestroyDCBuffers(); bool ReCreateDCBuffers(); bool ReCreateFrameBuffers(); - void DestroySynchronizations(); - bool ReCreateSynchronizations(); + void DestroySynchronizations(FrameResult reason); + bool ReCreateSynchronizations(FrameResult reason); void DestroyFrameBuffers(); public: @@ -214,7 +214,7 @@ namespace EvoVulkan::Core { VkSemaphore m_offscreenSemaphore = VK_NULL_HANDLE; SubmitInfo m_submitInfo = { }; SubmitInfo m_offscreenSubmitInfo = { }; - std::vector m_frameSyncs = { }; + //std::vector m_frameSyncs = { }; std::vector m_frames; // size = MAX_FRAMES_IN_FLIGHT std::vector m_imagesInFlight; // size = swapchainImageCount diff --git a/Core/src/EvoVulkan/VulkanKernel.cpp b/Core/src/EvoVulkan/VulkanKernel.cpp index 347f94a..e1f4c41 100644 --- a/Core/src/EvoVulkan/VulkanKernel.cpp +++ b/Core/src/EvoVulkan/VulkanKernel.cpp @@ -342,7 +342,7 @@ bool EvoVulkan::Core::VulkanKernel::PostInit() { //!================================================================================================================= VK_GRAPH("VulkanKernel::PostInit() : creating synchronizations..."); - if (!ReCreateSynchronizations()) { + if (!ReCreateSynchronizations(FrameResult::None)) { VK_ERROR("VulkanKernel::PostInit() : failed to create synchronizations!"); return false; } @@ -398,7 +398,7 @@ bool EvoVulkan::Core::VulkanKernel::Destroy() { if (m_pipelineCache) Tools::DestroyPipelineCache(*m_device, &m_pipelineCache); - DestroySynchronizations(); + DestroySynchronizations(FrameResult::None); if (m_renderPass.IsReady()) Types::DestroyRenderPass(m_device, &m_renderPass); @@ -556,6 +556,8 @@ void EvoVulkan::Core::VulkanKernel::WaitAllFences() { } EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { + EVK_TRACY_ZONE; + if (m_swapchain->IsDirty()) { VK_LOG("VulkanKernel::PrepareFrame() : swapchain is dirty!"); } @@ -576,7 +578,11 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { FrameSync& frame = m_frames[m_frameIndex]; // 1. Ждём, пока этот sync-slot освободится - vkWaitForFences(*m_device, 1, &frame.inFlightFence, VK_TRUE, UINT64_MAX); + { + EVK_TRACY_ZONE_N("Wait for in-flight fence"); + EVK_TRACY_ZONE_COLOR(0xffa500); + vkWaitForFences(*m_device, 1, &frame.inFlightFence, VK_TRUE, UINT64_MAX); + } // 2. Получаем image от swapchain VkResult result = m_swapchain->AcquireNextImage(frame.imageAvailable, &m_imageIndex); @@ -596,6 +602,8 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { // 3. Если image уже используется — ждём fence if (m_imagesInFlight[m_imageIndex] != VK_NULL_HANDLE) { + EVK_TRACY_ZONE_N("Wait for image in-flight fence"); + EVK_TRACY_ZONE_COLOR(0xff4500); vkWaitForFences( *m_device, 1, @@ -784,7 +792,7 @@ bool EvoVulkan::Core::VulkanKernel::ReCreate(FrameResult reason) { } VK_GRAPH("VulkanKernel::ReCreate() : re-creating synchronizations..."); - if (!ReCreateSynchronizations()) { + if (!ReCreateSynchronizations(reason)) { VK_ERROR("VulkanKernel::ReCreate() : failed to re-create synchronizations!"); // m_imageAcquiredThisFrame = false; ///< Reset flag on error return false; @@ -861,42 +869,43 @@ void EvoVulkan::Core::VulkanKernel::SetGUIEnabled(bool enabled) } } -void EvoVulkan::Core::VulkanKernel::DestroySynchronizations() { +void EvoVulkan::Core::VulkanKernel::DestroySynchronizations(FrameResult reason) { for (auto&& frame : m_frames) { Tools::DestroyVulkanSemaphore(*GetDevice(), &frame.imageAvailable); Tools::DestroyVulkanSemaphore(*GetDevice(), &frame.renderFinished); - Tools::DestroyVulkanFence(*GetDevice(), &frame.inFlightFence); - } - m_frames.clear(); - - for (auto&& sync : m_frameSyncs) { - if (sync.IsReady()) { - Tools::DestroySynchronization(*m_device, &sync); + if (reason != FrameResult::OutOfDate && reason != FrameResult::Suboptimal) { + Tools::DestroyVulkanFence(*GetDevice(), &frame.inFlightFence); } } - m_frameSyncs.clear(); - for (auto&& imageFence : m_imagesInFlight) { - Tools::DestroyVulkanFence(*GetDevice(), &imageFence); + if (reason != FrameResult::OutOfDate && reason != FrameResult::Suboptimal) { + m_frames.clear(); } - m_imagesInFlight.clear(); + + //for (auto&& sync : m_frameSyncs) { + // if (sync.IsReady()) { + // Tools::DestroySynchronization(*m_device, &sync); + // } + //} + //m_frameSyncs.clear(); + + //for (auto&& imageFence : m_imagesInFlight) { + // Tools::DestroyVulkanFence(*GetDevice(), &imageFence); + //} + //m_imagesInFlight.clear(); } -bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations() { - m_frameSyncs.resize(m_swapchain ? m_swapchain->GetCountImages() : 0); - for (auto& sync : m_frameSyncs) { - sync = Tools::CreateSynchronization(*m_device); - if (!sync.IsReady()) { - VK_ERROR("VulkanKernel::ReCreateSynchronizations() : failed to create synchronization!"); - return false; - } - } +bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations(FrameResult reason) { + DestroySynchronizations(reason); m_frames.resize(GetMaxFramesInFlight()); for (auto& frame : m_frames) { frame.imageAvailable = Tools::CreateVulkanSemaphore(*m_device); frame.renderFinished = Tools::CreateVulkanSemaphore(*m_device); - frame.inFlightFence = Tools::CreateVulkanFence(*m_device, VK_FENCE_CREATE_SIGNALED_BIT); + + if (reason != FrameResult::OutOfDate && reason != FrameResult::Suboptimal || frame.inFlightFence == VK_NULL_HANDLE) { + frame.inFlightFence = Tools::CreateVulkanFence(*m_device, VK_FENCE_CREATE_SIGNALED_BIT); + } if (!frame.imageAvailable || !frame.renderFinished || !frame.inFlightFence) { VK_ERROR("VulkanKernel::ReCreateSynchronizations() : failed to create frame synchronization objects!"); @@ -906,9 +915,9 @@ bool EvoVulkan::Core::VulkanKernel::ReCreateSynchronizations() { m_imagesInFlight.resize(m_swapchain ? m_swapchain->GetCountImages() : 0, VK_NULL_HANDLE); - for (auto& imageFence : m_imagesInFlight) { - imageFence = Tools::CreateVulkanFence(*m_device, VK_FENCE_CREATE_SIGNALED_BIT); - } + //for (auto& imageFence : m_imagesInFlight) { + // imageFence = Tools::CreateVulkanFence(*m_device, VK_FENCE_CREATE_SIGNALED_BIT); + //} /// Set up submit info structure @@ -1055,3 +1064,7 @@ bool EvoVulkan::Core::VulkanKernel::DestroyDCBuffers() { m_drawCmdBuffs.clear(); return true; } + +uint8_t EvoVulkan::Core::VulkanKernel::GetMaxFramesInFlight() const noexcept { + return 3; +} From 15edb97e969b6360576f0d85a60d804e7eb7cdf0 Mon Sep 17 00:00:00 2001 From: NikitaMantsurov Date: Thu, 1 Jan 2026 23:10:09 +0500 Subject: [PATCH 3/3] (Validation) Fixed "hazard" validation error. --- Core/inc/EvoVulkan/Types/RenderPass.h | 4 +-- Core/src/EvoVulkan/VulkanKernel.cpp | 36 +++------------------------ 2 files changed, 5 insertions(+), 35 deletions(-) diff --git a/Core/inc/EvoVulkan/Types/RenderPass.h b/Core/inc/EvoVulkan/Types/RenderPass.h index b72b19b..5e88f32 100644 --- a/Core/inc/EvoVulkan/Types/RenderPass.h +++ b/Core/inc/EvoVulkan/Types/RenderPass.h @@ -395,10 +395,10 @@ namespace EvoVulkan::Types { dependencies[0].pNext = nullptr; dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL; dependencies[0].dstSubpass = 0; - dependencies[0].srcStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; + dependencies[0].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependencies[0].srcAccessMask = 0; - dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; dependencies[1].sType = VK_STRUCTURE_TYPE_SUBPASS_DEPENDENCY_2; diff --git a/Core/src/EvoVulkan/VulkanKernel.cpp b/Core/src/EvoVulkan/VulkanKernel.cpp index e1f4c41..55ca935 100644 --- a/Core/src/EvoVulkan/VulkanKernel.cpp +++ b/Core/src/EvoVulkan/VulkanKernel.cpp @@ -562,19 +562,6 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { VK_LOG("VulkanKernel::PrepareFrame() : swapchain is dirty!"); } - /// Note: PrepareFrame() may be called multiple times per frame (e.g., from RenderScene and VulkanKernel) - /// We need to ensure we only acquire a new image once per frame to prevent command buffer mismatches - /// The image should be acquired in the first call, and subsequent calls should use the same image index - //if (m_imageAcquiredThisFrame) { - // m_currentBuffer = m_currentImage; - // return FrameResult::Success; - //} - - //uint32_t previousImage = m_currentImage; - //if (m_waitFences.size() > previousImage && previousImage < GetSwapchainImagesCount()) { - // vkWaitForFences(*m_device, 1, &m_waitFences[previousImage], VK_TRUE, UINT64_MAX); - //} - FrameSync& frame = m_frames[m_frameIndex]; // 1. Ждём, пока этот sync-slot освободится @@ -594,37 +581,20 @@ EvoVulkan::Core::FrameResult EvoVulkan::Core::VulkanKernel::PrepareFrame() { VK_LOG("VulkanKernel::PrepareFrame() : window has been suboptimal!"); } else if (result != VK_SUCCESS) { - VK_ERROR("VulkanKernel::PrepareFrame() : failed to acquire next image! Reason: " + - Tools::Convert::result_to_description(result)); + VK_ERROR("VulkanKernel::PrepareFrame() : failed to acquire next image! Reason: " + Tools::Convert::result_to_description(result)); return FrameResult::Error; } // 3. Если image уже используется — ждём fence - if (m_imagesInFlight[m_imageIndex] != VK_NULL_HANDLE) - { + if (m_imagesInFlight[m_imageIndex] != VK_NULL_HANDLE) { EVK_TRACY_ZONE_N("Wait for image in-flight fence"); EVK_TRACY_ZONE_COLOR(0xff4500); - vkWaitForFences( - *m_device, - 1, - &m_imagesInFlight[m_imageIndex], - VK_TRUE, - UINT64_MAX - ); + vkWaitForFences(*m_device, 1, &m_imagesInFlight[m_imageIndex], VK_TRUE, UINT64_MAX); } // 4. Привязываем image к текущему frame fence m_imagesInFlight[m_imageIndex] = frame.inFlightFence; - ///m_currentImage = acquiredImageIndex; - /// - ///if (m_waitFences.size() > m_currentImage && m_currentImage < GetSwapchainImagesCount()) { - ///vkWaitForFences(*m_device, 1, &m_waitFences[m_currentImage], VK_TRUE, UINT64_MAX); - ///} - /// - ///m_currentBuffer = previousImage; - ///m_imageAcquiredThisFrame = true; - return result == VK_SUBOPTIMAL_KHR ? FrameResult::Suboptimal : FrameResult::Success; }