mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Async humanoid NPC texture pipeline to eliminate 30-150ms main-thread stalls
Move all DBC lookups (CharSections, ItemDisplayInfo), texture path resolution, and BLP decoding for humanoid NPCs to background threads. Only GPU texture uploads remain on the main thread via pre-decoded BLP cache.
This commit is contained in:
parent
7ac990cff4
commit
faca22ac5f
3 changed files with 703 additions and 327 deletions
|
|
@ -220,6 +220,7 @@ private:
|
||||||
std::unordered_set<uint64_t> deadCreatureGuids_; // GUIDs that should spawn in corpse/death pose
|
std::unordered_set<uint64_t> deadCreatureGuids_; // GUIDs that should spawn in corpse/death pose
|
||||||
std::unordered_map<uint32_t, uint32_t> displayIdModelCache_; // displayId → modelId (model caching)
|
std::unordered_map<uint32_t, uint32_t> displayIdModelCache_; // displayId → modelId (model caching)
|
||||||
std::unordered_set<uint32_t> displayIdTexturesApplied_; // displayIds with per-model textures applied
|
std::unordered_set<uint32_t> displayIdTexturesApplied_; // displayIds with per-model textures applied
|
||||||
|
std::unordered_map<uint32_t, std::unordered_map<std::string, pipeline::BLPImage>> displayIdPredecodedTextures_; // displayId → pre-decoded skin textures
|
||||||
mutable std::unordered_set<uint32_t> warnedMissingDisplayDataIds_; // displayIds already warned
|
mutable std::unordered_set<uint32_t> warnedMissingDisplayDataIds_; // displayIds already warned
|
||||||
mutable std::unordered_set<uint32_t> warnedMissingModelPathIds_; // modelIds/displayIds already warned
|
mutable std::unordered_set<uint32_t> warnedMissingModelPathIds_; // modelIds/displayIds already warned
|
||||||
uint32_t nextCreatureModelId_ = 5000; // Model IDs for online creatures
|
uint32_t nextCreatureModelId_ = 5000; // Model IDs for online creatures
|
||||||
|
|
@ -312,6 +313,49 @@ private:
|
||||||
// Deferred equipment compositing queue — processes max 1 per frame to avoid stutter
|
// Deferred equipment compositing queue — processes max 1 per frame to avoid stutter
|
||||||
std::vector<std::pair<uint64_t, std::pair<std::array<uint32_t, 19>, std::array<uint8_t, 19>>>> deferredEquipmentQueue_;
|
std::vector<std::pair<uint64_t, std::pair<std::array<uint32_t, 19>, std::array<uint8_t, 19>>>> deferredEquipmentQueue_;
|
||||||
void processDeferredEquipmentQueue();
|
void processDeferredEquipmentQueue();
|
||||||
|
// Async equipment texture pre-decode: BLP decode on background thread, composite on main thread
|
||||||
|
struct PreparedEquipmentUpdate {
|
||||||
|
uint64_t guid;
|
||||||
|
std::array<uint32_t, 19> displayInfoIds;
|
||||||
|
std::array<uint8_t, 19> inventoryTypes;
|
||||||
|
std::unordered_map<std::string, pipeline::BLPImage> predecodedTextures;
|
||||||
|
};
|
||||||
|
struct AsyncEquipmentLoad {
|
||||||
|
std::future<PreparedEquipmentUpdate> future;
|
||||||
|
};
|
||||||
|
std::vector<AsyncEquipmentLoad> asyncEquipmentLoads_;
|
||||||
|
void processAsyncEquipmentResults();
|
||||||
|
std::vector<std::string> resolveEquipmentTexturePaths(uint64_t guid,
|
||||||
|
const std::array<uint32_t, 19>& displayInfoIds,
|
||||||
|
const std::array<uint8_t, 19>& inventoryTypes) const;
|
||||||
|
// Deferred NPC texture setup — async DBC lookups + BLP pre-decode to avoid main-thread stalls
|
||||||
|
struct DeferredNpcComposite {
|
||||||
|
uint32_t modelId;
|
||||||
|
uint32_t displayId;
|
||||||
|
// Skin compositing (type-1 slots)
|
||||||
|
std::string basePath; // CharSections skin base texture
|
||||||
|
std::vector<std::string> overlayPaths; // face + underwear overlays
|
||||||
|
std::vector<std::pair<int, std::string>> regionLayers; // equipment region overlays
|
||||||
|
std::vector<uint32_t> skinTextureSlots; // model texture slots needing skin composite
|
||||||
|
bool hasComposite = false; // needs compositing (overlays or equipment regions)
|
||||||
|
bool hasSimpleSkin = false; // just base skin, no compositing needed
|
||||||
|
// Baked skin (type-1 slots)
|
||||||
|
std::string bakedSkinPath; // baked texture path (if available)
|
||||||
|
bool hasBakedSkin = false; // baked skin resolved successfully
|
||||||
|
// Hair (type-6 slots)
|
||||||
|
std::vector<uint32_t> hairTextureSlots; // model texture slots needing hair texture
|
||||||
|
std::string hairTexturePath; // resolved hair texture path
|
||||||
|
bool useBakedForHair = false; // bald NPC: use baked skin for type-6
|
||||||
|
};
|
||||||
|
struct PreparedNpcComposite {
|
||||||
|
DeferredNpcComposite info;
|
||||||
|
std::unordered_map<std::string, pipeline::BLPImage> predecodedTextures;
|
||||||
|
};
|
||||||
|
struct AsyncNpcCompositeLoad {
|
||||||
|
std::future<PreparedNpcComposite> future;
|
||||||
|
};
|
||||||
|
std::vector<AsyncNpcCompositeLoad> asyncNpcCompositeLoads_;
|
||||||
|
void processAsyncNpcCompositeResults();
|
||||||
// Cache base player model geometry by (raceId, genderId)
|
// Cache base player model geometry by (raceId, genderId)
|
||||||
std::unordered_map<uint32_t, uint32_t> playerModelCache_; // key=(race<<8)|gender → modelId
|
std::unordered_map<uint32_t, uint32_t> playerModelCache_; // key=(race<<8)|gender → modelId
|
||||||
struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; };
|
struct PlayerTextureSlots { int skin = -1; int hair = -1; int underwear = -1; };
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
|
|
@ -836,7 +836,19 @@ VkTexture* CharacterRenderer::compositeTextures(const std::vector<std::string>&
|
||||||
}
|
}
|
||||||
|
|
||||||
// Load base layer
|
// Load base layer
|
||||||
auto base = assetManager->loadTexture(layerPaths[0]);
|
pipeline::BLPImage base;
|
||||||
|
if (predecodedBLPCache_) {
|
||||||
|
std::string key = layerPaths[0];
|
||||||
|
std::replace(key.begin(), key.end(), '/', '\\');
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
auto pit = predecodedBLPCache_->find(key);
|
||||||
|
if (pit != predecodedBLPCache_->end()) {
|
||||||
|
base = std::move(pit->second);
|
||||||
|
predecodedBLPCache_->erase(pit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!base.isValid()) base = assetManager->loadTexture(layerPaths[0]);
|
||||||
if (!base.isValid()) {
|
if (!base.isValid()) {
|
||||||
core::Logger::getInstance().warning("Composite: failed to load base layer: ", layerPaths[0]);
|
core::Logger::getInstance().warning("Composite: failed to load base layer: ", layerPaths[0]);
|
||||||
return whiteTexture_.get();
|
return whiteTexture_.get();
|
||||||
|
|
@ -877,7 +889,19 @@ VkTexture* CharacterRenderer::compositeTextures(const std::vector<std::string>&
|
||||||
for (size_t layer = 1; layer < layerPaths.size(); layer++) {
|
for (size_t layer = 1; layer < layerPaths.size(); layer++) {
|
||||||
if (layerPaths[layer].empty()) continue;
|
if (layerPaths[layer].empty()) continue;
|
||||||
|
|
||||||
auto overlay = assetManager->loadTexture(layerPaths[layer]);
|
pipeline::BLPImage overlay;
|
||||||
|
if (predecodedBLPCache_) {
|
||||||
|
std::string key = layerPaths[layer];
|
||||||
|
std::replace(key.begin(), key.end(), '/', '\\');
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
auto pit = predecodedBLPCache_->find(key);
|
||||||
|
if (pit != predecodedBLPCache_->end()) {
|
||||||
|
overlay = std::move(pit->second);
|
||||||
|
predecodedBLPCache_->erase(pit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!overlay.isValid()) overlay = assetManager->loadTexture(layerPaths[layer]);
|
||||||
if (!overlay.isValid()) {
|
if (!overlay.isValid()) {
|
||||||
core::Logger::getInstance().warning("Composite: FAILED to load overlay: ", layerPaths[layer]);
|
core::Logger::getInstance().warning("Composite: FAILED to load overlay: ", layerPaths[layer]);
|
||||||
continue;
|
continue;
|
||||||
|
|
@ -1054,7 +1078,19 @@ VkTexture* CharacterRenderer::compositeWithRegions(const std::string& basePath,
|
||||||
return whiteTexture_.get();
|
return whiteTexture_.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
auto base = assetManager->loadTexture(basePath);
|
pipeline::BLPImage base;
|
||||||
|
if (predecodedBLPCache_) {
|
||||||
|
std::string key = basePath;
|
||||||
|
std::replace(key.begin(), key.end(), '/', '\\');
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
auto pit = predecodedBLPCache_->find(key);
|
||||||
|
if (pit != predecodedBLPCache_->end()) {
|
||||||
|
base = std::move(pit->second);
|
||||||
|
predecodedBLPCache_->erase(pit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!base.isValid()) base = assetManager->loadTexture(basePath);
|
||||||
if (!base.isValid()) {
|
if (!base.isValid()) {
|
||||||
return whiteTexture_.get();
|
return whiteTexture_.get();
|
||||||
}
|
}
|
||||||
|
|
@ -1093,7 +1129,19 @@ VkTexture* CharacterRenderer::compositeWithRegions(const std::string& basePath,
|
||||||
bool upscaled = (base.width == 256 && base.height == 256 && width == 512);
|
bool upscaled = (base.width == 256 && base.height == 256 && width == 512);
|
||||||
for (const auto& ul : baseLayers) {
|
for (const auto& ul : baseLayers) {
|
||||||
if (ul.empty()) continue;
|
if (ul.empty()) continue;
|
||||||
auto overlay = assetManager->loadTexture(ul);
|
pipeline::BLPImage overlay;
|
||||||
|
if (predecodedBLPCache_) {
|
||||||
|
std::string key = ul;
|
||||||
|
std::replace(key.begin(), key.end(), '/', '\\');
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
auto pit = predecodedBLPCache_->find(key);
|
||||||
|
if (pit != predecodedBLPCache_->end()) {
|
||||||
|
overlay = std::move(pit->second);
|
||||||
|
predecodedBLPCache_->erase(pit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!overlay.isValid()) overlay = assetManager->loadTexture(ul);
|
||||||
if (!overlay.isValid()) continue;
|
if (!overlay.isValid()) continue;
|
||||||
|
|
||||||
if (overlay.width == width && overlay.height == height) {
|
if (overlay.width == width && overlay.height == height) {
|
||||||
|
|
@ -1171,7 +1219,19 @@ VkTexture* CharacterRenderer::compositeWithRegions(const std::string& basePath,
|
||||||
int regionIdx = rl.first;
|
int regionIdx = rl.first;
|
||||||
if (regionIdx < 0 || regionIdx >= 8) continue;
|
if (regionIdx < 0 || regionIdx >= 8) continue;
|
||||||
|
|
||||||
auto overlay = assetManager->loadTexture(rl.second);
|
pipeline::BLPImage overlay;
|
||||||
|
if (predecodedBLPCache_) {
|
||||||
|
std::string key = rl.second;
|
||||||
|
std::replace(key.begin(), key.end(), '/', '\\');
|
||||||
|
std::transform(key.begin(), key.end(), key.begin(),
|
||||||
|
[](unsigned char c) { return static_cast<char>(std::tolower(c)); });
|
||||||
|
auto pit = predecodedBLPCache_->find(key);
|
||||||
|
if (pit != predecodedBLPCache_->end()) {
|
||||||
|
overlay = std::move(pit->second);
|
||||||
|
predecodedBLPCache_->erase(pit);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!overlay.isValid()) overlay = assetManager->loadTexture(rl.second);
|
||||||
if (!overlay.isValid()) {
|
if (!overlay.isValid()) {
|
||||||
core::Logger::getInstance().warning("compositeWithRegions: failed to load ", rl.second);
|
core::Logger::getInstance().warning("compositeWithRegions: failed to load ", rl.second);
|
||||||
continue;
|
continue;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue