From cd01d07a9170bd374a14eb47f52d57ba0257d289 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Thu, 12 Mar 2026 19:01:15 -0700 Subject: [PATCH] fix: wait for GPU idle before freeing M2/WMO model buffers to prevent device lost cleanupUnusedModels() runs every 5 seconds and freed vertex/index buffers without waiting for the GPU to finish the previous frame's command buffer. This caused VK_ERROR_DEVICE_LOST (-4) after extended gameplay when tiles stream out and their models are freed mid-render. Add vkDeviceWaitIdle() before the buffer destroy loop in both M2Renderer and WMORenderer cleanupUnusedModels(). The wait only happens when there are models to remove, so quiet sessions have no overhead. --- src/rendering/m2_renderer.cpp | 9 ++++++++- src/rendering/wmo_renderer.cpp | 7 ++++++- 2 files changed, 14 insertions(+), 2 deletions(-) diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index c5ef43b2..96659828 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -3905,7 +3905,14 @@ void M2Renderer::cleanupUnusedModels() { } } - // Delete GPU resources and remove from map + // Delete GPU resources and remove from map. + // Wait for the GPU to finish all in-flight frames before destroying any + // buffers — the previous frame's command buffer may still be referencing + // vertex/index buffers that are about to be freed. Without this wait, + // the GPU reads freed memory, which can cause VK_ERROR_DEVICE_LOST. + if (!toRemove.empty() && vkCtx_) { + vkDeviceWaitIdle(vkCtx_->getDevice()); + } for (uint32_t id : toRemove) { auto it = models.find(id); if (it != models.end()) { diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index c2a81301..bc9aa362 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -835,7 +835,12 @@ void WMORenderer::cleanupUnusedModels() { } } - // Delete GPU resources and remove from map + // Delete GPU resources and remove from map. + // Ensure all in-flight frames are complete before freeing vertex/index buffers — + // the GPU may still be reading them from the previous frame's command buffer. + if (!toRemove.empty() && vkCtx_) { + vkDeviceWaitIdle(vkCtx_->getDevice()); + } for (uint32_t id : toRemove) { unloadModel(id); }