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