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.
This commit is contained in:
Kelsi 2026-03-11 20:17:41 -07:00
parent 6dd7213083
commit 8c2f69ca0e
3 changed files with 27 additions and 0 deletions

View file

@ -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;

View file

@ -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;

View file

@ -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;