From 8c2f69ca0ea1eca1b61e2b3cfca0a8ae4b963fc9 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 11 Mar 2026 20:17:41 -0700 Subject: [PATCH] Rate-limit icon GPU uploads in spellbook, action bar, and inventory screens Opening the spellbook on a new tab, logging in with many auras/action slots, or opening a full bag all triggered synchronous BLP-decode + GPU uploads for every uncached icon in one frame, causing a visible stall. Apply the same 4-per-frame upload cap that was added to talent_screen, so icons load progressively. --- src/ui/game_screen.cpp | 9 +++++++++ src/ui/inventory_screen.cpp | 9 +++++++++ src/ui/spellbook_screen.cpp | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/src/ui/game_screen.cpp b/src/ui/game_screen.cpp index acb6cf22..f1bcdf66 100644 --- a/src/ui/game_screen.cpp +++ b/src/ui/game_screen.cpp @@ -4108,6 +4108,14 @@ VkDescriptorSet GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManage } } + // Rate-limit GPU uploads per frame to prevent stalls when many icons are uncached + // (e.g., first login, after loading screen, or many new auras appearing at once). + static int gsLoadsThisFrame = 0; + static int gsLastImGuiFrame = -1; + int gsCurFrame = ImGui::GetFrameCount(); + if (gsCurFrame != gsLastImGuiFrame) { gsLoadsThisFrame = 0; gsLastImGuiFrame = gsCurFrame; } + if (gsLoadsThisFrame >= 4) return VK_NULL_HANDLE; // defer — do NOT cache null here + // Look up spellId -> SpellIconID -> icon path auto iit = spellIconIds_.find(spellId); if (iit == spellIconIds_.end()) { @@ -4143,6 +4151,7 @@ VkDescriptorSet GameScreen::getSpellIcon(uint32_t spellId, pipeline::AssetManage return VK_NULL_HANDLE; } + ++gsLoadsThisFrame; VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); spellIconCache_[spellId] = ds; return ds; diff --git a/src/ui/inventory_screen.cpp b/src/ui/inventory_screen.cpp index 2f63c34a..7899d654 100644 --- a/src/ui/inventory_screen.cpp +++ b/src/ui/inventory_screen.cpp @@ -101,6 +101,14 @@ VkDescriptorSet InventoryScreen::getItemIcon(uint32_t displayInfoId) { auto it = iconCache_.find(displayInfoId); if (it != iconCache_.end()) return it->second; + // Rate-limit GPU uploads per frame to avoid stalling when many items appear at once + // (e.g., opening a full bag, vendor window, or loot from a boss with many drops). + static int iiLoadsThisFrame = 0; + static int iiLastImGuiFrame = -1; + int iiCurFrame = ImGui::GetFrameCount(); + if (iiCurFrame != iiLastImGuiFrame) { iiLoadsThisFrame = 0; iiLastImGuiFrame = iiCurFrame; } + if (iiLoadsThisFrame >= 4) return VK_NULL_HANDLE; // defer — do NOT cache null here + // Load ItemDisplayInfo.dbc auto displayInfoDbc = assetManager_->loadDBC("ItemDisplayInfo.dbc"); if (!displayInfoDbc) { @@ -143,6 +151,7 @@ VkDescriptorSet InventoryScreen::getItemIcon(uint32_t displayInfoId) { return VK_NULL_HANDLE; } + ++iiLoadsThisFrame; VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); iconCache_[displayInfoId] = ds; return ds; diff --git a/src/ui/spellbook_screen.cpp b/src/ui/spellbook_screen.cpp index ef8815f5..8f3edb0f 100644 --- a/src/ui/spellbook_screen.cpp +++ b/src/ui/spellbook_screen.cpp @@ -411,6 +411,14 @@ VkDescriptorSet SpellbookScreen::getSpellIcon(uint32_t iconId, pipeline::AssetMa auto cit = spellIconCache.find(iconId); if (cit != spellIconCache.end()) return cit->second; + // Rate-limit GPU uploads to avoid a multi-frame stall when switching tabs. + // Icons not loaded this frame will be retried next frame (progressive load). + static int loadsThisFrame = 0; + static int lastImGuiFrame = -1; + int curFrame = ImGui::GetFrameCount(); + if (curFrame != lastImGuiFrame) { loadsThisFrame = 0; lastImGuiFrame = curFrame; } + if (loadsThisFrame >= 4) return VK_NULL_HANDLE; // defer — do NOT cache null here + auto pit = spellIconPaths.find(iconId); if (pit == spellIconPaths.end()) { spellIconCache[iconId] = VK_NULL_HANDLE; @@ -437,6 +445,7 @@ VkDescriptorSet SpellbookScreen::getSpellIcon(uint32_t iconId, pipeline::AssetMa return VK_NULL_HANDLE; } + ++loadsThisFrame; VkDescriptorSet ds = vkCtx->uploadImGuiTexture(image.data.data(), image.width, image.height); spellIconCache[iconId] = ds; return ds;