mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-23 07:40:14 +00:00
fix: eliminate 490ms transport-doodad stall and GPU device-loss crash
Three root causes identified from wowee.log crash at frame 134368: 1. processPendingTransportDoodads() was doing N separate synchronous GPU uploads (vkQueueSubmit + vkWaitForFences per texture per doodad). With 30+ doodads × multiple textures, this caused the 489ms stall in the 'gameobject/transport queues' update stage. Fixed by wrapping the entire batch in beginUploadBatch()/endUploadBatch() so all texture layout transitions are submitted in a single async command buffer. 2. Game objects whose M2 model has no geometry/particles (empty or unsupported format) were retried every frame because loadModel() returns false without adding to gameObjectDisplayIdModelCache_. Added gameObjectDisplayIdFailedCache_ to permanently skip these display IDs after the first failure, stopping the per-frame spam. 3. renderM2Ribbons() only checked ribbonPipeline_ != null, not ribbonAdditivePipeline_. If additive pipeline creation failed, any ribbon with additive blending would call vkCmdBindPipeline with VK_NULL_HANDLE, causing VK_ERROR_DEVICE_LOST on the GPU side. Extended the early-return guard to cover both ribbon pipelines.
This commit is contained in:
parent
367b48af6b
commit
f855327054
3 changed files with 20 additions and 1 deletions
|
|
@ -271,6 +271,7 @@ private:
|
||||||
};
|
};
|
||||||
std::unordered_map<uint32_t, std::string> gameObjectDisplayIdToPath_;
|
std::unordered_map<uint32_t, std::string> gameObjectDisplayIdToPath_;
|
||||||
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdModelCache_; // displayId → M2 modelId
|
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdModelCache_; // displayId → M2 modelId
|
||||||
|
std::unordered_set<uint32_t> gameObjectDisplayIdFailedCache_; // displayIds that permanently fail to load
|
||||||
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdWmoCache_; // displayId → WMO modelId
|
std::unordered_map<uint32_t, uint32_t> gameObjectDisplayIdWmoCache_; // displayId → WMO modelId
|
||||||
std::unordered_map<uint64_t, GameObjectInstanceInfo> gameObjectInstances_; // guid → instance info
|
std::unordered_map<uint64_t, GameObjectInstanceInfo> gameObjectInstances_; // guid → instance info
|
||||||
struct PendingTransportMove {
|
struct PendingTransportMove {
|
||||||
|
|
|
||||||
|
|
@ -7234,6 +7234,11 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
||||||
auto* m2Renderer = renderer->getM2Renderer();
|
auto* m2Renderer = renderer->getM2Renderer();
|
||||||
if (!m2Renderer) return;
|
if (!m2Renderer) return;
|
||||||
|
|
||||||
|
// Skip displayIds that permanently failed to load (e.g. empty/unsupported M2s).
|
||||||
|
// Without this guard the same empty model is re-parsed every frame, causing
|
||||||
|
// sustained log spam and wasted CPU.
|
||||||
|
if (gameObjectDisplayIdFailedCache_.count(displayId)) return;
|
||||||
|
|
||||||
uint32_t modelId = 0;
|
uint32_t modelId = 0;
|
||||||
auto itCache = gameObjectDisplayIdModelCache_.find(displayId);
|
auto itCache = gameObjectDisplayIdModelCache_.find(displayId);
|
||||||
if (itCache != gameObjectDisplayIdModelCache_.end()) {
|
if (itCache != gameObjectDisplayIdModelCache_.end()) {
|
||||||
|
|
@ -7252,12 +7257,14 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
||||||
auto m2Data = assetManager->readFile(modelPath);
|
auto m2Data = assetManager->readFile(modelPath);
|
||||||
if (m2Data.empty()) {
|
if (m2Data.empty()) {
|
||||||
LOG_WARNING("Failed to read gameobject M2: ", modelPath);
|
LOG_WARNING("Failed to read gameobject M2: ", modelPath);
|
||||||
|
gameObjectDisplayIdFailedCache_.insert(displayId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
pipeline::M2Model model = pipeline::M2Loader::load(m2Data);
|
pipeline::M2Model model = pipeline::M2Loader::load(m2Data);
|
||||||
if (model.vertices.empty()) {
|
if (model.vertices.empty()) {
|
||||||
LOG_WARNING("Failed to parse gameobject M2: ", modelPath);
|
LOG_WARNING("Failed to parse gameobject M2: ", modelPath);
|
||||||
|
gameObjectDisplayIdFailedCache_.insert(displayId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -7269,6 +7276,7 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
||||||
|
|
||||||
if (!m2Renderer->loadModel(model, modelId)) {
|
if (!m2Renderer->loadModel(model, modelId)) {
|
||||||
LOG_WARNING("Failed to load gameobject model: ", modelPath);
|
LOG_WARNING("Failed to load gameobject model: ", modelPath);
|
||||||
|
gameObjectDisplayIdFailedCache_.insert(displayId);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -8189,6 +8197,13 @@ void Application::processPendingTransportDoodads() {
|
||||||
auto startTime = std::chrono::steady_clock::now();
|
auto startTime = std::chrono::steady_clock::now();
|
||||||
static constexpr float kDoodadBudgetMs = 4.0f;
|
static constexpr float kDoodadBudgetMs = 4.0f;
|
||||||
|
|
||||||
|
// Batch all GPU uploads into a single async command buffer submission so that
|
||||||
|
// N doodads with multiple textures each don't each block on vkQueueSubmit +
|
||||||
|
// vkWaitForFences. Without batching, 30+ doodads × several textures = hundreds
|
||||||
|
// of sync GPU submits → the 490ms stall that preceded the VK_ERROR_DEVICE_LOST.
|
||||||
|
auto* vkCtx = renderer->getVkContext();
|
||||||
|
if (vkCtx) vkCtx->beginUploadBatch();
|
||||||
|
|
||||||
size_t budgetLeft = MAX_TRANSPORT_DOODADS_PER_FRAME;
|
size_t budgetLeft = MAX_TRANSPORT_DOODADS_PER_FRAME;
|
||||||
for (auto it = pendingTransportDoodadBatches_.begin();
|
for (auto it = pendingTransportDoodadBatches_.begin();
|
||||||
it != pendingTransportDoodadBatches_.end() && budgetLeft > 0;) {
|
it != pendingTransportDoodadBatches_.end() && budgetLeft > 0;) {
|
||||||
|
|
@ -8256,6 +8271,9 @@ void Application::processPendingTransportDoodads() {
|
||||||
++it;
|
++it;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Finalize the upload batch — submit all GPU copies in one shot (async, no wait).
|
||||||
|
if (vkCtx) vkCtx->endUploadBatch();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Application::processPendingMount() {
|
void Application::processPendingMount() {
|
||||||
|
|
|
||||||
|
|
@ -3565,7 +3565,7 @@ void M2Renderer::updateRibbons(M2Instance& inst, const M2ModelGPU& gpu, float dt
|
||||||
// Ribbon rendering
|
// Ribbon rendering
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
void M2Renderer::renderM2Ribbons(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
void M2Renderer::renderM2Ribbons(VkCommandBuffer cmd, VkDescriptorSet perFrameSet) {
|
||||||
if (!ribbonPipeline_ || !ribbonVB_ || !ribbonVBMapped_) return;
|
if (!ribbonPipeline_ || !ribbonAdditivePipeline_ || !ribbonVB_ || !ribbonVBMapped_) return;
|
||||||
|
|
||||||
// Build camera right vector for billboard orientation
|
// Build camera right vector for billboard orientation
|
||||||
// For ribbons we orient the quad strip along the spine with screen-space up.
|
// For ribbons we orient the quad strip along the spine with screen-space up.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue