mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-04-27 05:23:51 +00:00
fix(rendering): game objects invisible due to GPU cull instance limit
MAX_CULL_INSTANCES was 16384 but game object instances were allocated at indices 20000+, beyond the cull buffer. Increased to 65536. Also compute fallback boundRadius from vertex extents when M2 header reports 0, and seed bones for global-sequence-only animated models.
This commit is contained in:
parent
1dd1a431f4
commit
b62df70d09
3 changed files with 70 additions and 13 deletions
|
|
@ -478,7 +478,7 @@ private:
|
|||
uint32_t instanceCount;
|
||||
uint32_t _pad[3] = {};
|
||||
};
|
||||
static constexpr uint32_t MAX_CULL_INSTANCES = 16384;
|
||||
static constexpr uint32_t MAX_CULL_INSTANCES = 65536;
|
||||
VkPipeline cullPipeline_ = VK_NULL_HANDLE;
|
||||
VkPipelineLayout cullPipelineLayout_ = VK_NULL_HANDLE;
|
||||
VkDescriptorSetLayout cullSetLayout_ = VK_NULL_HANDLE;
|
||||
|
|
|
|||
|
|
@ -757,6 +757,12 @@ void EntitySpawner::buildGameObjectDisplayLookups() {
|
|||
gameObjectDisplayIdToPath_[displayId] = modelName;
|
||||
}
|
||||
LOG_INFO("Loaded ", gameObjectDisplayIdToPath_.size(), " gameobject display mappings");
|
||||
} else {
|
||||
LOG_WARNING("GameObjectDisplayInfo.dbc failed to load — no GO display mappings available");
|
||||
}
|
||||
|
||||
if (gameObjectDisplayIdToPath_.empty()) {
|
||||
LOG_WARNING("GO display mapping table is EMPTY — game objects will not render");
|
||||
}
|
||||
|
||||
gameObjectLookupsBuilt_ = true;
|
||||
|
|
@ -3102,6 +3108,10 @@ void EntitySpawner::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_
|
|||
}
|
||||
if (!gameObjectLookupsBuilt_) return;
|
||||
|
||||
LOG_DEBUG("GO spawn attempt: guid=0x", std::hex, guid, std::dec,
|
||||
" displayId=", displayId, " entry=", entry,
|
||||
" pos=(", x, ", ", y, ", ", z, ")");
|
||||
|
||||
auto goIt = gameObjectInstances_.find(guid);
|
||||
if (goIt != gameObjectInstances_.end()) {
|
||||
// Already have a render instance — update its position (e.g. transport re-creation)
|
||||
|
|
@ -3363,8 +3373,17 @@ void EntitySpawner::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_
|
|||
auto skinData = assetManager_->readFile(skinPath);
|
||||
if (!skinData.empty() && model.version >= 264) {
|
||||
pipeline::M2Loader::loadSkin(skinData, model);
|
||||
} else if (skinData.empty() && model.version >= 264) {
|
||||
LOG_WARNING("GO skin file MISSING for WotLK M2 (no indices/batches): ", skinPath);
|
||||
}
|
||||
|
||||
LOG_DEBUG("GO model: ", modelPath, " v=", model.version,
|
||||
" verts=", model.vertices.size(),
|
||||
" idx=", model.indices.size(),
|
||||
" batches=", model.batches.size(),
|
||||
" bones=", model.bones.size(),
|
||||
" skin=", (skinData.empty() ? "MISSING" : "ok"));
|
||||
|
||||
if (!m2Renderer->loadModel(model, modelId)) {
|
||||
LOG_WARNING("Failed to load gameobject model: ", modelPath);
|
||||
gameObjectDisplayIdFailedCache_.insert(displayId);
|
||||
|
|
@ -4222,6 +4241,13 @@ void EntitySpawner::processGameObjectSpawnQueue() {
|
|||
|
||||
if (pendingGameObjectSpawns_.empty()) return;
|
||||
|
||||
static int goQueueLogCounter = 0;
|
||||
if (++goQueueLogCounter % 60 == 1) {
|
||||
LOG_DEBUG("GO queue: ", pendingGameObjectSpawns_.size(), " pending, ",
|
||||
gameObjectInstances_.size(), " spawned, ",
|
||||
gameObjectDisplayIdFailedCache_.size(), " failed");
|
||||
}
|
||||
|
||||
// Process spawns: cached WMOs and M2s go sync (cheap), uncached WMOs go async
|
||||
auto startTime = std::chrono::steady_clock::now();
|
||||
static constexpr float kBudgetMs = 2.0f;
|
||||
|
|
|
|||
|
|
@ -1371,6 +1371,11 @@ bool M2Renderer::loadModel(const pipeline::M2Model& model, uint32_t modelId) {
|
|||
gpuModel.boundMin = tightMin;
|
||||
gpuModel.boundMax = tightMax;
|
||||
gpuModel.boundRadius = model.boundRadius;
|
||||
// Fallback: compute bound radius from vertex extents when M2 header reports 0
|
||||
if (gpuModel.boundRadius < 0.01f && !model.vertices.empty()) {
|
||||
glm::vec3 extent = tightMax - tightMin;
|
||||
gpuModel.boundRadius = glm::length(extent) * 0.5f;
|
||||
}
|
||||
gpuModel.indexCount = static_cast<uint32_t>(model.indices.size());
|
||||
gpuModel.vertexCount = static_cast<uint32_t>(model.vertices.size());
|
||||
|
||||
|
|
@ -1915,12 +1920,14 @@ uint32_t M2Renderer::createInstance(uint32_t modelId, const glm::vec3& position,
|
|||
|
||||
// Initialize animation: play first sequence (usually Stand/Idle)
|
||||
const auto& mdl = mdlRef;
|
||||
if (mdl.hasAnimation && !mdl.disableAnimation && !mdl.sequences.empty()) {
|
||||
instance.currentSequenceIndex = 0;
|
||||
instance.idleSequenceIndex = 0;
|
||||
instance.animDuration = static_cast<float>(mdl.sequences[0].duration);
|
||||
instance.animTime = static_cast<float>(randRange(std::max(1u, mdl.sequences[0].duration)));
|
||||
instance.variationTimer = randFloat(3000.0f, 11000.0f);
|
||||
if (mdl.hasAnimation && !mdl.disableAnimation) {
|
||||
if (!mdl.sequences.empty()) {
|
||||
instance.currentSequenceIndex = 0;
|
||||
instance.idleSequenceIndex = 0;
|
||||
instance.animDuration = static_cast<float>(mdl.sequences[0].duration);
|
||||
instance.animTime = static_cast<float>(randRange(std::max(1u, mdl.sequences[0].duration)));
|
||||
instance.variationTimer = randFloat(3000.0f, 11000.0f);
|
||||
}
|
||||
|
||||
// Seed bone matrices from an existing instance of the same model so the
|
||||
// new instance renders immediately instead of being invisible until the
|
||||
|
|
@ -2022,12 +2029,14 @@ uint32_t M2Renderer::createInstanceWithMatrix(uint32_t modelId, const glm::mat4&
|
|||
instance.cachedModel = &mdl2;
|
||||
|
||||
// Initialize animation
|
||||
if (mdl2.hasAnimation && !mdl2.disableAnimation && !mdl2.sequences.empty()) {
|
||||
instance.currentSequenceIndex = 0;
|
||||
instance.idleSequenceIndex = 0;
|
||||
instance.animDuration = static_cast<float>(mdl2.sequences[0].duration);
|
||||
instance.animTime = static_cast<float>(randRange(std::max(1u, mdl2.sequences[0].duration)));
|
||||
instance.variationTimer = randFloat(3000.0f, 11000.0f);
|
||||
if (mdl2.hasAnimation && !mdl2.disableAnimation) {
|
||||
if (!mdl2.sequences.empty()) {
|
||||
instance.currentSequenceIndex = 0;
|
||||
instance.idleSequenceIndex = 0;
|
||||
instance.animDuration = static_cast<float>(mdl2.sequences[0].duration);
|
||||
instance.animTime = static_cast<float>(randRange(std::max(1u, mdl2.sequences[0].duration)));
|
||||
instance.variationTimer = randFloat(3000.0f, 11000.0f);
|
||||
}
|
||||
|
||||
// Seed bone matrices from an existing sibling so the instance renders immediately
|
||||
for (const auto& existing : instances) {
|
||||
|
|
@ -2612,6 +2621,28 @@ void M2Renderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const
|
|||
LOG_INFO("M2 render: ", instances.size(), " instances, ", models.size(), " models");
|
||||
}
|
||||
|
||||
// Periodic diagnostic: report render pipeline stats every 10 seconds
|
||||
static int diagCounter = 0;
|
||||
if (++diagCounter == 600) { // ~10s at 60fps
|
||||
diagCounter = 0;
|
||||
uint32_t totalValid = 0, totalAnimated = 0, totalBonesReady = 0, totalMegaBoneOk = 0;
|
||||
for (const auto& inst : instances) {
|
||||
if (inst.cachedIsValid) totalValid++;
|
||||
if (inst.cachedHasAnimation && !inst.cachedDisableAnimation) {
|
||||
totalAnimated++;
|
||||
if (!inst.boneMatrices.empty()) totalBonesReady++;
|
||||
if (inst.megaBoneOffset != 0) totalMegaBoneOk++;
|
||||
}
|
||||
}
|
||||
LOG_INFO("M2 diag: total=", instances.size(),
|
||||
" valid=", totalValid,
|
||||
" animated=", totalAnimated,
|
||||
" bonesReady=", totalBonesReady,
|
||||
" megaBoneOk=", totalMegaBoneOk,
|
||||
" visible=", sortedVisible_.size(),
|
||||
" draws=", lastDrawCallCount);
|
||||
}
|
||||
|
||||
// Reuse persistent buffers (clear instead of reallocating)
|
||||
glowSprites_.clear();
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue