From bda5bb0a2b4c1465b42b83f09e33f9079d29be75 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Fri, 20 Mar 2026 05:56:33 -0700 Subject: [PATCH] fix: add negative cache for failed spell visual model loads Spell visual M2 models that fail to load (missing file, empty model, or GPU upload failure) were re-attempted on every subsequent spell cast, causing repeated file I/O during combat. Now caches failed model IDs in spellVisualFailedModels_ so they are skipped on subsequent attempts. --- include/rendering/renderer.hpp | 2 ++ src/rendering/renderer.cpp | 6 ++++++ 2 files changed, 8 insertions(+) diff --git a/include/rendering/renderer.hpp b/include/rendering/renderer.hpp index 1a99a6f4..92c1de59 100644 --- a/include/rendering/renderer.hpp +++ b/include/rendering/renderer.hpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -343,6 +344,7 @@ private: std::unordered_map spellVisualCastPath_; // visualId → cast M2 path std::unordered_map spellVisualImpactPath_; // visualId → impact M2 path std::unordered_map spellVisualModelIds_; // M2 path → M2Renderer modelId + std::unordered_set spellVisualFailedModels_; // modelIds that failed to load (negative cache) uint32_t nextSpellVisualModelId_ = 999000; // Reserved range 999000-999799 bool spellVisualDbcLoaded_ = false; void loadSpellVisualDbc(); diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index c3241337..4dad508d 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -2860,16 +2860,21 @@ void Renderer::playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition spellVisualModelIds_[modelPath] = modelId; } + // Skip models that have previously failed to load (avoid repeated I/O) + if (spellVisualFailedModels_.count(modelId)) return; + // Load the M2 model if not already loaded if (!m2Renderer->hasModel(modelId)) { auto m2Data = cachedAssetManager->readFile(modelPath); if (m2Data.empty()) { LOG_DEBUG("SpellVisual: could not read model: ", modelPath); + spellVisualFailedModels_.insert(modelId); return; } pipeline::M2Model model = pipeline::M2Loader::load(m2Data); if (model.vertices.empty() && model.particleEmitters.empty()) { LOG_DEBUG("SpellVisual: empty model: ", modelPath); + spellVisualFailedModels_.insert(modelId); return; } // Load skin file for WotLK-format M2s @@ -2880,6 +2885,7 @@ void Renderer::playSpellVisual(uint32_t visualId, const glm::vec3& worldPosition } if (!m2Renderer->loadModel(model, modelId)) { LOG_WARNING("SpellVisual: failed to load model to GPU: ", modelPath); + spellVisualFailedModels_.insert(modelId); return; } LOG_DEBUG("SpellVisual: loaded model id=", modelId, " path=", modelPath);