diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp index 14b55776..ba7b6c7f 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/BufferArena.cpp @@ -111,6 +111,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { } if (buffer.IsValid()) { + // Outgrown, not dead: every BufferSlice handed out from this frame's arena so far + // still names it, and those slices stay in service until the frame slot is rewound + // (VkBufferResource::transientSlice, the converted-vertex-stream cache, the draw + // memos). The release therefore has to survive every mid-frame reclaim and land on + // the next ResetFrame of this slot - see VkBufferManager::CollectAllDeferredReleases. m_deferredReleases[frameIndex].push_back(std::move(buffer)); } diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index cffdb4a0..218a3daf 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -161,12 +161,23 @@ namespace MobileGL::MG_Backend::DirectVulkan { } void VkBufferManager::CollectAllDeferredReleases() { + // Per-resource releases only. Every one of them was deferred behind a BumpSliceEpoch, + // so no memo can still name the handle, and the caller has proved the GPU is idle. + // + // The transient arena's releases are deliberately NOT collected here. A buffer lands + // there when the arena outgrows it mid-frame (BufferArena::EnsureCapacity), and at + // that moment every slice already handed out from this frame's arena still names it - + // VkBufferResource::transientSlice above all, which AcquireStreamedSlice keeps + // serving for the whole frame serial on the strength of transientFrameSerial alone. + // Nothing bumps the slice epoch for those other resources, so freeing the buffer + // here left the streamed memo handing a destroyed VkBuffer to vkCmdBindIndexBuffer + // (llvmpipe then faulted inside the draw; the Create/Flywheel indirect retrace died + // exactly this way). Mid-frame drains do not advance m_frameSerial, so they must not + // free arena storage either: the arena's own ResetFrame/BeginFrame is the point where + // the slot's slices stop being reachable, and that is where these releases land. for (Uint32 frameIndex = 0; frameIndex < m_deferredBufferReleases.size(); ++frameIndex) { CollectDeferredReleases(frameIndex); } - for (Uint32 frameIndex = 0; frameIndex < m_transientUploadArena.GetFrameCount(); ++frameIndex) { - m_transientUploadArena.CollectDeferredReleases(frameIndex); - } } void VkBufferManager::NotifyDeviceIdle() { diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h index 4ec5de50..d4e435d7 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.h @@ -102,10 +102,11 @@ namespace MobileGL::MG_Backend::DirectVulkan { // Recreate all per-frame transient arenas Bool RecreateTransientArenas(Uint32 frameCount); void BeginFrame(Uint32 frameIndex); - // Drains every frame slot's deferred buffer/resource releases (and the - // transient arena's parked superseded blocks). Only valid when the - // caller has proven every queue submission complete; used by the - // present-less frame-boundary drain. + // Drains every frame slot's deferred buffer/resource releases. Only valid when + // the caller has proven every queue submission complete; used by the present-less + // frame-boundary drain. Deliberately does NOT touch the transient arena's parked + // superseded blocks: those are still named by this frame's slices (see the + // definition), and only a frame rewind retires them. void CollectAllDeferredReleases(); // All previously submitted GPU work has completed (vkDeviceWaitIdle). void NotifyDeviceIdle();