From caea24f6ea93e6cb191c3b3af1d7b2533eb96c49 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 9 Mar 2026 18:09:33 -0700 Subject: [PATCH] Fix animated M2 flicker: free bone descriptor sets on instance removal MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The boneDescPool_ had MAX_BONE_SETS=2048 but sets were never freed when instances were removed (only when clear() reset the whole pool on map load). As tiles streamed in/out, each new animated instance consumed 2 pool slots (one per frame index) permanently. After ~1024 animated instances created total, vkAllocateDescriptorSets began failing silently and returning VK_NULL_HANDLE. render() skips instances with null boneSet[frameIndex], making them invisible — appearing as per-frame flicker as the culling pass included them but the render pass excluded them. Fix: destroyInstanceBones() now calls vkFreeDescriptorSets() for each non-null boneSet before destroying the bone SSBO. The pool already had VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT set for this purpose. Also increased MAX_BONE_SETS from 2048 to 8192 for extra headroom. --- include/rendering/m2_renderer.hpp | 2 +- src/rendering/m2_renderer.cpp | 10 +++++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index ee7d6ebf..a37c4a2d 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -381,7 +381,7 @@ private: VkDescriptorPool materialDescPool_ = VK_NULL_HANDLE; VkDescriptorPool boneDescPool_ = VK_NULL_HANDLE; static constexpr uint32_t MAX_MATERIAL_SETS = 8192; - static constexpr uint32_t MAX_BONE_SETS = 2048; + static constexpr uint32_t MAX_BONE_SETS = 8192; // Dynamic particle buffers ::VkBuffer smokeVB_ = VK_NULL_HANDLE; diff --git a/src/rendering/m2_renderer.cpp b/src/rendering/m2_renderer.cpp index eed9a025..a28e49a6 100644 --- a/src/rendering/m2_renderer.cpp +++ b/src/rendering/m2_renderer.cpp @@ -751,14 +751,22 @@ void M2Renderer::destroyModelGPU(M2ModelGPU& model) { void M2Renderer::destroyInstanceBones(M2Instance& inst) { if (!vkCtx_) return; + VkDevice device = vkCtx_->getDevice(); VmaAllocator alloc = vkCtx_->getAllocator(); for (int i = 0; i < 2; i++) { + // Free bone descriptor set so the pool slot is immediately reusable. + // Without this, the pool fills up over a play session as tiles stream + // in/out, eventually causing vkAllocateDescriptorSets to fail and + // making animated instances invisible (perceived as flickering). + if (inst.boneSet[i] != VK_NULL_HANDLE) { + vkFreeDescriptorSets(device, boneDescPool_, 1, &inst.boneSet[i]); + inst.boneSet[i] = VK_NULL_HANDLE; + } if (inst.boneBuffer[i]) { vmaDestroyBuffer(alloc, inst.boneBuffer[i], inst.boneAlloc[i]); inst.boneBuffer[i] = VK_NULL_HANDLE; inst.boneMapped[i] = nullptr; } - // boneSet freed when pool is reset/destroyed } }