From 4acebff65c50f779218eb9e2a4fb51fe4bc77261 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 30 Mar 2026 14:06:30 -0700 Subject: [PATCH] refactor: extract fallback textures, add why-comments, name WMO constant MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - character_renderer: extract duplicated fallback texture creation (white/transparent/flat-normal) into createFallbackTextures() — was copy-pasted between initialize() and clear() - wmo_renderer: replace magic 8192 with kMaxRetryTracked constant, add why-comment explaining the fallback-retry set cap (Dalaran has 2000+ unique WMO groups) - quest_handler: add why-comment on reqCount=0 fallback — escort/event quests can report kill credit without objective counts in query response --- include/rendering/character_renderer.hpp | 4 ++ src/game/quest_handler.cpp | 5 +- src/rendering/character_renderer.cpp | 67 +++++++++--------------- src/rendering/wmo_renderer.cpp | 8 ++- 4 files changed, 40 insertions(+), 44 deletions(-) diff --git a/include/rendering/character_renderer.hpp b/include/rendering/character_renderer.hpp index c368ded4..8a29d874 100644 --- a/include/rendering/character_renderer.hpp +++ b/include/rendering/character_renderer.hpp @@ -251,6 +251,10 @@ public: private: + // Create 1×1 fallback textures used when real textures are missing or still loading. + // Called during both init and clear to ensure valid descriptor bindings at all times. + void createFallbackTextures(VkDevice device); + VkContext* vkCtx_ = nullptr; VkRenderPass renderPassOverride_ = VK_NULL_HANDLE; VkSampleCountFlagBits msaaSamplesOverride_ = VK_SAMPLE_COUNT_1_BIT; diff --git a/src/game/quest_handler.cpp b/src/game/quest_handler.cpp index 669a3e5f..a10bc83e 100644 --- a/src/game/quest_handler.cpp +++ b/src/game/quest_handler.cpp @@ -533,7 +533,10 @@ void QuestHandler::registerOpcodes(DispatchTable& table) { } } } - if (reqCount == 0) reqCount = count; // last-resort: avoid 0/0 display + // Some quests (e.g. escort/event quests) report kill credit updates without + // a corresponding objective count in SMSG_QUEST_QUERY_RESPONSE. Fall back to + // current count so the progress display shows "N/N" instead of "N/0". + if (reqCount == 0) reqCount = count; quest.killCounts[entry] = {count, reqCount}; std::string creatureName = owner_.getCachedCreatureName(entry); diff --git a/src/rendering/character_renderer.cpp b/src/rendering/character_renderer.cpp index aac98830..a3a5aa97 100644 --- a/src/rendering/character_renderer.cpp +++ b/src/rendering/character_renderer.cpp @@ -278,29 +278,7 @@ bool CharacterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFram charVert.destroy(); charFrag.destroy(); - // --- Create white fallback texture --- - { - uint8_t white[] = {255, 255, 255, 255}; - whiteTexture_ = std::make_unique(); - whiteTexture_->upload(*vkCtx_, white, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); - whiteTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); - } - - // --- Create transparent fallback texture --- - { - uint8_t transparent[] = {0, 0, 0, 0}; - transparentTexture_ = std::make_unique(); - transparentTexture_->upload(*vkCtx_, transparent, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); - transparentTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); - } - - // --- Create flat normal placeholder texture (128,128,255,128) = neutral normal, 0.5 height --- - { - uint8_t flatNormal[] = {128, 128, 255, 128}; - flatNormalTexture_ = std::make_unique(); - flatNormalTexture_->upload(*vkCtx_, flatNormal, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); - flatNormalTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); - } + createFallbackTextures(device); // Diagnostics-only: cache lifetime is currently tied to renderer lifetime. textureCacheBudgetBytes_ = envSizeMBOrDefault("WOWEE_CHARACTER_TEX_CACHE_MB", 4096) * 1024ull * 1024ull; @@ -449,24 +427,7 @@ void CharacterRenderer::clear() { whiteTexture_.reset(); transparentTexture_.reset(); flatNormalTexture_.reset(); - { - uint8_t white[] = {255, 255, 255, 255}; - whiteTexture_ = std::make_unique(); - whiteTexture_->upload(*vkCtx_, white, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); - whiteTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); - } - { - uint8_t transparent[] = {0, 0, 0, 0}; - transparentTexture_ = std::make_unique(); - transparentTexture_->upload(*vkCtx_, transparent, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); - transparentTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); - } - { - uint8_t flatNormal[] = {128, 128, 255, 128}; - flatNormalTexture_ = std::make_unique(); - flatNormalTexture_->upload(*vkCtx_, flatNormal, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); - flatNormalTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); - } + createFallbackTextures(device); models.clear(); instances.clear(); @@ -487,6 +448,30 @@ void CharacterRenderer::clear() { } } +void CharacterRenderer::createFallbackTextures(VkDevice device) { + // White: default diffuse when no texture is assigned + { + uint8_t white[] = {255, 255, 255, 255}; + whiteTexture_ = std::make_unique(); + whiteTexture_->upload(*vkCtx_, white, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); + whiteTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); + } + // Transparent: placeholder for optional overlay layers (e.g. hair highlights) + { + uint8_t transparent[] = {0, 0, 0, 0}; + transparentTexture_ = std::make_unique(); + transparentTexture_->upload(*vkCtx_, transparent, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); + transparentTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); + } + // Flat normal: neutral normal map (128,128,255) + 0.5 height in alpha channel + { + uint8_t flatNormal[] = {128, 128, 255, 128}; + flatNormalTexture_ = std::make_unique(); + flatNormalTexture_->upload(*vkCtx_, flatNormal, 1, 1, VK_FORMAT_R8G8B8A8_UNORM, false); + flatNormalTexture_->createSampler(device, VK_FILTER_NEAREST, VK_FILTER_NEAREST, VK_SAMPLER_ADDRESS_MODE_REPEAT); + } +} + void CharacterRenderer::destroyModelGPU(M2ModelGPU& gpuModel) { if (!vkCtx_) return; VmaAllocator alloc = vkCtx_->getAllocator(); diff --git a/src/rendering/wmo_renderer.cpp b/src/rendering/wmo_renderer.cpp index 633e69bf..3d401496 100644 --- a/src/rendering/wmo_renderer.cpp +++ b/src/rendering/wmo_renderer.cpp @@ -363,12 +363,16 @@ bool WMORenderer::loadModel(const pipeline::WMOModel& model, uint32_t id) { break; } } + // Track which WMO models have been force-reloaded after resolving only to + // fallback textures. Cap the set to avoid unbounded memory growth in worlds + // with many unique WMO groups (e.g. Dalaran has 2000+). + static constexpr size_t kMaxRetryTracked = 8192; static std::unordered_set retryReloadedModels; static bool retryReloadedModelsCapped = false; - if (retryReloadedModels.size() > 8192) { + if (retryReloadedModels.size() > kMaxRetryTracked) { retryReloadedModels.clear(); if (!retryReloadedModelsCapped) { - core::Logger::getInstance().warning("WMO fallback-retry set exceeded 8192 entries; reset"); + core::Logger::getInstance().warning("WMO fallback-retry set exceeded ", kMaxRetryTracked, " entries; reset"); retryReloadedModelsCapped = true; } }