From 3675721016f91e79c70cf08391273ba05461f07d Mon Sep 17 00:00:00 2001 From: Kelsi Date: Sat, 14 Feb 2026 15:48:58 -0800 Subject: [PATCH] Improve equipment texture performance and accuracy Cache composite textures by input key so identical NPC equipment combos share one GPU texture. Use DBC layout system for ItemDisplayInfo texture component fields instead of hardcoded indices (cross-expansion support). Selective player equipment re-emission on item query response instead of broadcasting to all players. --- Data/expansions/classic/dbc_layouts.json | 5 +++- Data/expansions/tbc/dbc_layouts.json | 5 +++- Data/expansions/turtle/dbc_layouts.json | 5 +++- Data/expansions/wotlk/dbc_layouts.json | 5 +++- include/rendering/character_renderer.hpp | 1 + src/core/application.cpp | 34 +++++++++++++++++++----- src/game/game_handler.cpp | 19 +++++++++++-- src/rendering/character_renderer.cpp | 16 +++++++++++ 8 files changed, 78 insertions(+), 12 deletions(-) diff --git a/Data/expansions/classic/dbc_layouts.json b/Data/expansions/classic/dbc_layouts.json index b795f652..0aa17c74 100644 --- a/Data/expansions/classic/dbc_layouts.json +++ b/Data/expansions/classic/dbc_layouts.json @@ -5,7 +5,10 @@ }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, - "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9 + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, diff --git a/Data/expansions/tbc/dbc_layouts.json b/Data/expansions/tbc/dbc_layouts.json index cc6be8fe..a49d1c7a 100644 --- a/Data/expansions/tbc/dbc_layouts.json +++ b/Data/expansions/tbc/dbc_layouts.json @@ -5,7 +5,10 @@ }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, - "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9 + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, diff --git a/Data/expansions/turtle/dbc_layouts.json b/Data/expansions/turtle/dbc_layouts.json index b795f652..0aa17c74 100644 --- a/Data/expansions/turtle/dbc_layouts.json +++ b/Data/expansions/turtle/dbc_layouts.json @@ -5,7 +5,10 @@ }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, - "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9 + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, diff --git a/Data/expansions/wotlk/dbc_layouts.json b/Data/expansions/wotlk/dbc_layouts.json index 90dd98a6..7a5271a3 100644 --- a/Data/expansions/wotlk/dbc_layouts.json +++ b/Data/expansions/wotlk/dbc_layouts.json @@ -5,7 +5,10 @@ }, "ItemDisplayInfo": { "ID": 0, "LeftModel": 1, "LeftModelTexture": 3, - "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9 + "InventoryIcon": 5, "GeosetGroup1": 7, "GeosetGroup3": 9, + "TextureArmUpper": 14, "TextureArmLower": 15, "TextureHand": 16, + "TextureTorsoUpper": 17, "TextureTorsoLower": 18, + "TextureLegUpper": 19, "TextureLegLower": 20, "TextureFoot": 21 }, "CharSections": { "RaceID": 1, "SexID": 2, "BaseSection": 3, diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index f6090df3..668819fa 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -247,6 +247,7 @@ private: uint64_t lastUse = 0; }; std::unordered_map textureCache; + std::unordered_map compositeCache_; // key → GPU texture for reuse size_t textureCacheBytes_ = 0; uint64_t textureCacheCounter_ = 0; size_t textureCacheBudgetBytes_ = 1024ull * 1024 * 1024; // Default, overridden at init diff --git a/src/core/application.cpp b/src/core/application.cpp index 058091d3..bc403754 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -3127,6 +3127,19 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x "TorsoUpperTexture", "TorsoLowerTexture", "LegUpperTexture", "LegLowerTexture", "FootTexture", }; + const auto* idiL = pipeline::getActiveDBCLayout() + ? pipeline::getActiveDBCLayout()->getLayout("ItemDisplayInfo") : nullptr; + // Texture component region fields (8 regions: ArmUpper..Foot) + const uint32_t texRegionFields[8] = { + idiL ? (*idiL)["TextureArmUpper"] : 14u, + idiL ? (*idiL)["TextureArmLower"] : 15u, + idiL ? (*idiL)["TextureHand"] : 16u, + idiL ? (*idiL)["TextureTorsoUpper"]: 17u, + idiL ? (*idiL)["TextureTorsoLower"]: 18u, + idiL ? (*idiL)["TextureLegUpper"] : 19u, + idiL ? (*idiL)["TextureLegLower"] : 20u, + idiL ? (*idiL)["TextureFoot"] : 21u, + }; const bool npcIsFemale = (extra.sexId == 1); // Iterate all 11 NPC equipment slots; let DBC lookup filter which have textures @@ -3138,10 +3151,7 @@ void Application::spawnOnlineCreature(uint64_t guid, uint32_t displayId, float x for (int region = 0; region < 8; region++) { std::string texName = npcItemDisplayDbc->getString( - static_cast(recIdx), 14 + region); - if (texName.empty()) - texName = npcItemDisplayDbc->getString( - static_cast(recIdx), 15 + region); + static_cast(recIdx), texRegionFields[region]); if (texName.empty()) continue; std::string base = "Item\\TextureComponents\\" + @@ -3958,6 +3968,18 @@ void Application::setOnlinePlayerEquipment(uint64_t guid, "FootTexture", }; + // Texture component region fields from DBC layout + const uint32_t texRegionFields[8] = { + idiL ? (*idiL)["TextureArmUpper"] : 14u, + idiL ? (*idiL)["TextureArmLower"] : 15u, + idiL ? (*idiL)["TextureHand"] : 16u, + idiL ? (*idiL)["TextureTorsoUpper"]: 17u, + idiL ? (*idiL)["TextureTorsoLower"]: 18u, + idiL ? (*idiL)["TextureLegUpper"] : 19u, + idiL ? (*idiL)["TextureLegLower"] : 20u, + idiL ? (*idiL)["TextureFoot"] : 21u, + }; + std::vector> regionLayers; const bool isFemale = (st.genderId == 1); @@ -3968,8 +3990,8 @@ void Application::setOnlinePlayerEquipment(uint64_t guid, if (recIdx < 0) continue; for (int region = 0; region < 8; region++) { - std::string texName = displayInfoDbc->getString(static_cast(recIdx), 14 + region); - if (texName.empty()) texName = displayInfoDbc->getString(static_cast(recIdx), 15 + region); + std::string texName = displayInfoDbc->getString( + static_cast(recIdx), texRegionFields[region]); if (texName.empty()) continue; std::string base = "Item\\TextureComponents\\" + std::string(componentDirs[region]) + "\\" + texName; diff --git a/src/game/game_handler.cpp b/src/game/game_handler.cpp index 512bc0b6..03436c40 100644 --- a/src/game/game_handler.cpp +++ b/src/game/game_handler.cpp @@ -5251,10 +5251,25 @@ void GameHandler::handleItemQueryResponse(network::Packet& packet) { itemInfoCache_[data.entry] = data; rebuildOnlineInventory(); maybeDetectVisibleItemLayout(); - emitAllOtherPlayerEquipment(); - // If we have inspect-based item entry lists, re-emit for any players that now resolve. + + // Selectively re-emit only players whose equipment references this item entry + const uint32_t resolvedEntry = data.entry; + for (const auto& [guid, entries] : otherPlayerVisibleItemEntries_) { + for (uint32_t e : entries) { + if (e == resolvedEntry) { + emitOtherPlayerEquipment(guid); + break; + } + } + } + // Same for inspect-based entries if (playerEquipmentCallback_) { for (const auto& [guid, entries] : inspectedPlayerItemEntries_) { + bool relevant = false; + for (uint32_t e : entries) { + if (e == resolvedEntry) { relevant = true; break; } + } + if (!relevant) continue; std::array displayIds{}; std::array invTypes{}; for (int s = 0; s < 19; s++) { diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index d5acf60f..3a0da921 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -572,6 +572,21 @@ GLuint CharacterRenderer::compositeTextures(const std::vector& laye GLuint CharacterRenderer::compositeWithRegions(const std::string& basePath, const std::vector& baseLayers, const std::vector>& regionLayers) { + // Build cache key from all inputs to avoid redundant compositing + std::string cacheKey = basePath; + for (const auto& bl : baseLayers) { cacheKey += '|'; cacheKey += bl; } + cacheKey += '#'; + for (const auto& rl : regionLayers) { + cacheKey += std::to_string(rl.first); + cacheKey += ':'; + cacheKey += rl.second; + cacheKey += ','; + } + auto cacheIt = compositeCache_.find(cacheKey); + if (cacheIt != compositeCache_.end() && cacheIt->second != 0) { + return cacheIt->second; + } + // Region index → pixel coordinates on the 512x512 atlas static const int regionCoords[][2] = { { 0, 0 }, // 0 = ArmUpper @@ -717,6 +732,7 @@ GLuint CharacterRenderer::compositeWithRegions(const std::string& basePath, core::Logger::getInstance().info("compositeWithRegions: created ", width, "x", height, " texture with ", regionLayers.size(), " equipment regions"); + compositeCache_[cacheKey] = texId; return texId; }