diff --git a/include/rendering/m2_renderer.hpp b/include/rendering/m2_renderer.hpp index f698ea4b..de4fbf84 100644 --- a/include/rendering/m2_renderer.hpp +++ b/include/rendering/m2_renderer.hpp @@ -287,6 +287,8 @@ public: bool hasModel(uint32_t modelId) const; bool loadModel(const pipeline::M2Model& model, uint32_t modelId); + /** Force-remove a model and all its GPU resources. Caller must ensure no instances reference it. */ + void unloadModel(uint32_t modelId); /** Mark a loaded model as a spell effect (full-brightness particles, no collision). */ void markModelAsSpellEffect(uint32_t modelId); diff --git a/src/rendering/m2_renderer_instance.cpp b/src/rendering/m2_renderer_instance.cpp index 2e2a6c0d..269fab62 100644 --- a/src/rendering/m2_renderer_instance.cpp +++ b/src/rendering/m2_renderer_instance.cpp @@ -543,6 +543,15 @@ void M2Renderer::cleanupUnusedModels() { } } +void M2Renderer::unloadModel(uint32_t modelId) { + auto it = models.find(modelId); + if (it == models.end()) return; + if (vkCtx_) vkDeviceWaitIdle(vkCtx_->getDevice()); + destroyModelGPU(it->second); + models.erase(it); + modelUnusedSince_.erase(modelId); +} + VkTexture* M2Renderer::loadTexture(const std::string& path, uint32_t texFlags) { constexpr uint64_t kFailedTextureRetryLookups = 512; auto normalizeKey = [](std::string key) { diff --git a/tools/editor/editor_viewport.cpp b/tools/editor/editor_viewport.cpp index 1a6c0eea..d1014fc3 100644 --- a/tools/editor/editor_viewport.cpp +++ b/tools/editor/editor_viewport.cpp @@ -623,7 +623,9 @@ void EditorViewport::clearGhostPreview() { ghostInstanceId_ = 0; } if (ghostModelId_ != 0 && m2Renderer_) { - // Don't unload the model — it might be used by placed objects too + // Ghost ID is reserved for previews only — safe to unload so a path + // change can re-load with the new model under the same ID. + m2Renderer_->unloadModel(ghostModelId_); ghostModelId_ = 0; ghostModelPath_.clear(); }