From a680611c9f78293f9031e3f2e076cedf9b506cde Mon Sep 17 00:00:00 2001 From: BZLZHH Date: Tue, 4 Aug 2026 19:56:28 -0400 Subject: [PATCH] [Fix] (DirectVulkan): never stream a buffer whose storage the application holds AcquirePersistentMap promises the storage it creates is never recreated, because the frontend adopts it in place of the shadow and hands out pointers into it. AcquireStreamedSlice broke that promise: its downgrade path releases the resident storage unconditionally to avoid keeping a second stale copy, so binding such a buffer as a vertex or index source freed the memory the application was still pointing at. It also fed that draw the wrong bytes. The streaming copy is uploaded from the shadow, and a persistently mapped buffer can hold bytes the shadow never saw -- a transform feedback capture writes straight into the resident storage. The next capture into the same buffer then landed in freshly recreated storage while the application kept reading the original, which is how the ping-pong in transform_feedback.draw_xfb_feedbackk_test stalled after its first doubling. Route a persistently mapped resource to the resident path instead, where its single piece of storage is bound directly. --- .../DirectVulkan/Renderer/VkBufferManager.cpp | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp index 28a884cd..6f5e9289 100644 --- a/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp +++ b/MobileGL/MG_Backend/DirectVulkan/Renderer/VkBufferManager.cpp @@ -561,6 +561,16 @@ namespace MobileGL::MG_Backend::DirectVulkan { auto resource = GetOrCreateResource(bufferObject); bufferObject->SyncPersistentMappedRange(); + // A persistently mapped resource's storage IS the application's copy of the bytes - + // the frontend adopted it in place of the shadow and hands out pointers into it, and + // a shader can have written bytes the shadow never saw (a transform feedback + // capture). Streaming a second copy would feed this draw the stale shadow, and the + // downgrade below would release the storage the application still points at, + // breaking the "never recreated" promise AcquirePersistentMap makes. + if (resource->persistentMapped) { + return AcquireResidentSlice(kind, bufferObject, outSlice); + } + const VkDeviceSize size = static_cast(bufferObject->GetSize()); if (size == 0) { MGLOG_E("VkBufferManager::AcquireStreamedSlice failed: buffer size is zero");