From a84572363504aa4c031bb0ca70278f64d7af7b06 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 6 May 2026 00:11:44 -0700 Subject: [PATCH] fix(editor): create M2 instances AFTER upload flush (isValid fix) ROOT CAUSE: createInstance() checks mdlRef.isValid() which requires vertexBuffer != VK_NULL_HANDLE. But vertex buffers are uploaded via staging and only finalized by waitAllUploads(). Instances were being created BEFORE the upload flush, so vertexBuffer was still null, cachedIsValid was set to false, and all instances were skipped during render (0 draws despite loaded models). Fix: split rebuildObjects into two phases: 1. Load all models (upload geometry to staging) 2. waitAllUploads + pollUploadBatches (finalize GPU buffers) 3. Create all instances (vertexBuffer is now valid, isValid() = true) This matches the client's terrain_manager pattern where models are loaded on background threads and instances created after finalization. --- tools/editor/editor_viewport.cpp | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/tools/editor/editor_viewport.cpp b/tools/editor/editor_viewport.cpp index e3037d16..df840bd3 100644 --- a/tools/editor/editor_viewport.cpp +++ b/tools/editor/editor_viewport.cpp @@ -219,8 +219,6 @@ void EditorViewport::rebuildObjects(const std::vector& objects, model.vertices.size(), " verts)"); m2ModelIds[obj.path] = modelId; } - glm::vec3 rotRad = glm::radians(obj.rotation); - m2Renderer_->createInstance(modelId, obj.position, rotRad, obj.scale); } else if (obj.type == PlaceableType::WMO && wmoRenderer_) { uint32_t modelId; @@ -368,14 +366,30 @@ void EditorViewport::rebuildObjects(const std::vector& objects, } m2ModelIds[npc.modelPath] = modelId; } - glm::vec3 rotRad = glm::radians(glm::vec3(0, 0, npc.orientation)); - m2Renderer_->createInstance(modelId, npc.position, rotRad, npc.scale); } } + // Finalize all GPU uploads BEFORE creating instances + // (vertex buffers must be valid for isValid() check in createInstance) vkCtx_->waitAllUploads(); vkCtx_->pollUploadBatches(); + // Now create instances (vertex buffers are finalized) + for (const auto& obj : objects) { + if (obj.type == PlaceableType::M2) { + auto it = m2ModelIds.find(obj.path); + if (it == m2ModelIds.end()) continue; + glm::vec3 rotRad = glm::radians(obj.rotation); + m2Renderer_->createInstance(it->second, obj.position, rotRad, obj.scale); + } + } + for (const auto& npc : npcs) { + auto it = m2ModelIds.find(npc.modelPath); + if (it == m2ModelIds.end()) continue; + glm::vec3 rotRad = glm::radians(glm::vec3(0, 0, npc.orientation)); + m2Renderer_->createInstance(it->second, npc.position, rotRad, npc.scale); + } + // Update NPC markers via dedicated method updateNpcMarkers(npcs); }