mirror of
https://github.com/Kelsidavis/WoWee.git
synced 2026-03-22 23:30:14 +00:00
Fix GPU resource leaks and re-entrant world loading for instance transitions
Reset descriptor pools in CharacterRenderer/M2Renderer/WMORenderer on map change to prevent VK_ERROR_DEVICE_LOST from pool exhaustion. Defer re-entrant SMSG_NEW_WORLD during active world load to avoid recursive cleanup crashes. Gate swim bubbles on swimming state, skip redundant shadow pipeline re-init, add WOWEE_SKIP_* env vars for render isolation debugging.
This commit is contained in:
parent
19652ae521
commit
48eb0b70a3
9 changed files with 255 additions and 47 deletions
|
|
@ -9,6 +9,7 @@
|
|||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <array>
|
||||
#include <optional>
|
||||
|
||||
namespace wowee {
|
||||
|
||||
|
|
@ -189,6 +190,12 @@ private:
|
|||
uint32_t wyvernDisplayId_ = 0;
|
||||
bool lastTaxiFlight_ = false;
|
||||
uint32_t loadedMapId_ = 0xFFFFFFFF; // Map ID of currently loaded terrain (0xFFFFFFFF = none)
|
||||
uint32_t worldLoadGeneration_ = 0; // Incremented on each world entry to detect re-entrant loads
|
||||
bool loadingWorld_ = false; // True while loadOnlineWorldTerrain is running
|
||||
struct PendingWorldEntry {
|
||||
uint32_t mapId; float x, y, z;
|
||||
};
|
||||
std::optional<PendingWorldEntry> pendingWorldEntry_; // Deferred world entry during loading
|
||||
float taxiLandingClampTimer_ = 0.0f;
|
||||
float worldEntryMovementGraceTimer_ = 0.0f;
|
||||
float facingSendCooldown_ = 0.0f; // Rate-limits MSG_MOVE_SET_FACING
|
||||
|
|
|
|||
|
|
@ -48,6 +48,7 @@ public:
|
|||
VkRenderPass renderPassOverride = VK_NULL_HANDLE,
|
||||
VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_1_BIT);
|
||||
void shutdown();
|
||||
void clear(); // Remove all models/instances/textures but keep pipelines/pools
|
||||
|
||||
void setAssetManager(pipeline::AssetManager* am) { assetManager = am; }
|
||||
|
||||
|
|
|
|||
|
|
@ -254,6 +254,7 @@ public:
|
|||
* Initialize shadow pipeline (Phase 7)
|
||||
*/
|
||||
bool initializeShadow(VkRenderPass shadowRenderPass);
|
||||
bool hasShadowPipeline() const { return shadowPipeline_ != VK_NULL_HANDLE; }
|
||||
|
||||
/**
|
||||
* Render depth-only pass for shadow casting
|
||||
|
|
|
|||
|
|
@ -1456,11 +1456,21 @@ void Application::setupUICallbacks() {
|
|||
return;
|
||||
}
|
||||
|
||||
// If a world load is already in progress (re-entrant call from
|
||||
// gameHandler->update() processing SMSG_NEW_WORLD during warmup),
|
||||
// defer this entry. The current load will pick it up when it finishes.
|
||||
if (loadingWorld_) {
|
||||
LOG_WARNING("World entry deferred: map ", mapId, " while loading (will process after current load)");
|
||||
pendingWorldEntry_ = {mapId, x, y, z};
|
||||
return;
|
||||
}
|
||||
|
||||
worldEntryMovementGraceTimer_ = 2.0f;
|
||||
taxiLandingClampTimer_ = 0.0f;
|
||||
lastTaxiFlight_ = false;
|
||||
loadOnlineWorldTerrain(mapId, x, y, z);
|
||||
loadedMapId_ = mapId;
|
||||
// loadedMapId_ is set inside loadOnlineWorldTerrain (including
|
||||
// any deferred entries it processes), so we must NOT override it here.
|
||||
});
|
||||
|
||||
auto sampleBestFloorAt = [this](float x, float y, float probeZ) -> std::optional<float> {
|
||||
|
|
@ -3160,6 +3170,11 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
return;
|
||||
}
|
||||
|
||||
// Guard against re-entrant calls. The worldEntryCallback defers new
|
||||
// entries while this flag is set; we process them at the end.
|
||||
loadingWorld_ = true;
|
||||
pendingWorldEntry_.reset();
|
||||
|
||||
// --- Loading screen for online mode ---
|
||||
rendering::LoadingScreen loadingScreen;
|
||||
loadingScreen.setVkContext(window->getVkContext());
|
||||
|
|
@ -3196,43 +3211,44 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
|
||||
// --- Clean up previous map's state on map change ---
|
||||
// (Same cleanup as logout, but preserves player identity and renderer objects.)
|
||||
if (loadedMapId_ != 0xFFFFFFFF) {
|
||||
LOG_INFO("Map change: cleaning up old map ", loadedMapId_, " before loading map ", mapId);
|
||||
|
||||
// Clear entity instances from old map
|
||||
creatureInstances_.clear();
|
||||
creatureModelIds_.clear();
|
||||
creatureRenderPosCache_.clear();
|
||||
creatureWeaponsAttached_.clear();
|
||||
creatureWeaponAttachAttempts_.clear();
|
||||
deadCreatureGuids_.clear();
|
||||
nonRenderableCreatureDisplayIds_.clear();
|
||||
creaturePermanentFailureGuids_.clear();
|
||||
LOG_WARNING("loadOnlineWorldTerrain: mapId=", mapId, " loadedMapId_=", loadedMapId_);
|
||||
bool hasRendererData = renderer && (renderer->getWMORenderer() || renderer->getM2Renderer());
|
||||
if (loadedMapId_ != 0xFFFFFFFF || hasRendererData) {
|
||||
LOG_WARNING("Map change: cleaning up old map ", loadedMapId_, " before loading map ", mapId);
|
||||
|
||||
// Clear pending queues first (these don't touch GPU resources)
|
||||
pendingCreatureSpawns_.clear();
|
||||
pendingCreatureSpawnGuids_.clear();
|
||||
creatureSpawnRetryCounts_.clear();
|
||||
|
||||
playerInstances_.clear();
|
||||
onlinePlayerAppearance_.clear();
|
||||
pendingOnlinePlayerEquipment_.clear();
|
||||
deferredEquipmentQueue_.clear();
|
||||
pendingPlayerSpawns_.clear();
|
||||
pendingPlayerSpawnGuids_.clear();
|
||||
|
||||
gameObjectInstances_.clear();
|
||||
pendingOnlinePlayerEquipment_.clear();
|
||||
deferredEquipmentQueue_.clear();
|
||||
pendingGameObjectSpawns_.clear();
|
||||
pendingTransportMoves_.clear();
|
||||
pendingTransportDoodadBatches_.clear();
|
||||
|
||||
if (renderer) {
|
||||
// Clear all world geometry from old map (including textures/models)
|
||||
// Clear all world geometry from old map (including textures/models).
|
||||
// WMO clearAll and M2 clear both call vkDeviceWaitIdle internally,
|
||||
// ensuring no GPU command buffers reference old resources.
|
||||
if (auto* wmo = renderer->getWMORenderer()) {
|
||||
wmo->clearAll();
|
||||
}
|
||||
if (auto* m2 = renderer->getM2Renderer()) {
|
||||
m2->clear();
|
||||
}
|
||||
|
||||
// Full clear of character renderer: removes all instances, models,
|
||||
// textures, and resets descriptor pools. This prevents stale GPU
|
||||
// resources from accumulating across map changes (old creature
|
||||
// models, bone buffers, texture descriptor sets) which can cause
|
||||
// VK_ERROR_DEVICE_LOST on some drivers.
|
||||
if (auto* cr = renderer->getCharacterRenderer()) {
|
||||
cr->clear();
|
||||
renderer->setCharacterFollow(0);
|
||||
}
|
||||
|
||||
if (auto* terrain = renderer->getTerrainManager()) {
|
||||
terrain->softReset();
|
||||
terrain->setStreamingEnabled(true); // Re-enable in case previous map disabled it
|
||||
|
|
@ -3243,6 +3259,22 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
renderer->clearMount();
|
||||
}
|
||||
|
||||
// Clear application-level instance tracking (after renderer cleanup)
|
||||
creatureInstances_.clear();
|
||||
creatureModelIds_.clear();
|
||||
creatureRenderPosCache_.clear();
|
||||
creatureWeaponsAttached_.clear();
|
||||
creatureWeaponAttachAttempts_.clear();
|
||||
deadCreatureGuids_.clear();
|
||||
nonRenderableCreatureDisplayIds_.clear();
|
||||
creaturePermanentFailureGuids_.clear();
|
||||
|
||||
playerInstances_.clear();
|
||||
onlinePlayerAppearance_.clear();
|
||||
|
||||
gameObjectInstances_.clear();
|
||||
gameObjectDisplayIdModelCache_.clear();
|
||||
|
||||
// Force player character re-spawn on new map
|
||||
playerCharacterSpawned = false;
|
||||
}
|
||||
|
|
@ -3395,25 +3427,22 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
LOG_WARNING("WMO-only map detected — loading root WMO: ", wdtInfo.rootWMOPath);
|
||||
showProgress("Loading instance geometry...", 0.25f);
|
||||
|
||||
// Still call loadTestTerrain with a dummy path to initialize all renderers
|
||||
// (terrain, WMO, M2, character). The terrain load will fail gracefully.
|
||||
auto [tileX, tileY] = core::coords::canonicalToTile(spawnCanonical.x, spawnCanonical.y);
|
||||
std::string dummyAdtPath = "World\\Maps\\" + mapName + "\\" + mapName + "_" +
|
||||
std::to_string(tileX) + "_" + std::to_string(tileY) + ".adt";
|
||||
LOG_WARNING("WMO-only: calling loadTestTerrain with dummy ADT: ", dummyAdtPath);
|
||||
renderer->loadTestTerrain(assetManager.get(), dummyAdtPath);
|
||||
LOG_WARNING("WMO-only: loadTestTerrain returned");
|
||||
// Initialize renderers if they don't exist yet (first login to a WMO-only map).
|
||||
// On map change, renderers already exist from the previous map.
|
||||
if (!renderer->getWMORenderer() || !renderer->getTerrainManager()) {
|
||||
auto [tileX, tileY] = core::coords::canonicalToTile(spawnCanonical.x, spawnCanonical.y);
|
||||
std::string dummyAdtPath = "World\\Maps\\" + mapName + "\\" + mapName + "_" +
|
||||
std::to_string(tileX) + "_" + std::to_string(tileY) + ".adt";
|
||||
LOG_WARNING("WMO-only: calling loadTestTerrain to create renderers");
|
||||
renderer->loadTestTerrain(assetManager.get(), dummyAdtPath);
|
||||
}
|
||||
|
||||
// Set map name on the newly-created WMO renderer (loadTestTerrain creates it)
|
||||
// Set map name on WMO and terrain renderers
|
||||
if (renderer->getWMORenderer()) {
|
||||
renderer->getWMORenderer()->setMapName(mapName);
|
||||
}
|
||||
if (renderer->getTerrainManager()) {
|
||||
renderer->getTerrainManager()->setMapName(mapName);
|
||||
}
|
||||
|
||||
// Disable terrain streaming — no ADT tiles for WMO-only maps
|
||||
if (renderer->getTerrainManager()) {
|
||||
renderer->getTerrainManager()->setStreamingEnabled(false);
|
||||
}
|
||||
|
||||
|
|
@ -3606,6 +3635,15 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
LOG_INFO("Online world terrain loading initiated");
|
||||
}
|
||||
|
||||
// Set map name on the newly-created WMO/terrain renderers
|
||||
// (loadTestTerrain creates them, so the earlier setMapName at line ~3296 was a no-op)
|
||||
if (renderer->getWMORenderer()) {
|
||||
renderer->getWMORenderer()->setMapName(mapName);
|
||||
}
|
||||
if (renderer->getTerrainManager()) {
|
||||
renderer->getTerrainManager()->setMapName(mapName);
|
||||
}
|
||||
|
||||
// Character renderer is created inside loadTestTerrain(), so spawn the
|
||||
// player model now that the renderer actually exists.
|
||||
if (!playerCharacterSpawned) {
|
||||
|
|
@ -3791,6 +3829,15 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
|
||||
// Drain network and process deferred spawn/composite queues while hidden.
|
||||
if (gameHandler) gameHandler->update(1.0f / 60.0f);
|
||||
|
||||
// If a new world entry was deferred during packet processing,
|
||||
// stop warming up this map — we'll load the new one after cleanup.
|
||||
if (pendingWorldEntry_) {
|
||||
LOG_WARNING("loadOnlineWorldTerrain(map ", mapId,
|
||||
") — deferred world entry pending, stopping warmup");
|
||||
break;
|
||||
}
|
||||
|
||||
if (world) world->update(1.0f / 60.0f);
|
||||
processPlayerSpawnQueue();
|
||||
processCreatureSpawnQueue();
|
||||
|
|
@ -3823,8 +3870,26 @@ void Application::loadOnlineWorldTerrain(uint32_t mapId, float x, float y, float
|
|||
loadingScreen.shutdown();
|
||||
}
|
||||
|
||||
// Track which map we actually loaded (used by same-map teleport check).
|
||||
loadedMapId_ = mapId;
|
||||
|
||||
// Set game state
|
||||
setState(AppState::IN_GAME);
|
||||
|
||||
// Clear loading flag and process any deferred world entry.
|
||||
// A deferred entry occurs when SMSG_NEW_WORLD arrived during our warmup
|
||||
// (e.g., an area trigger in a dungeon immediately teleporting the player out).
|
||||
loadingWorld_ = false;
|
||||
if (pendingWorldEntry_) {
|
||||
auto entry = *pendingWorldEntry_;
|
||||
pendingWorldEntry_.reset();
|
||||
LOG_WARNING("Processing deferred world entry: map ", entry.mapId);
|
||||
worldEntryMovementGraceTimer_ = 2.0f;
|
||||
taxiLandingClampTimer_ = 0.0f;
|
||||
lastTaxiFlight_ = false;
|
||||
// Recursive call — sets loadedMapId_ to entry.mapId inside.
|
||||
loadOnlineWorldTerrain(entry.mapId, entry.x, entry.y, entry.z);
|
||||
}
|
||||
}
|
||||
|
||||
void Application::buildCreatureDisplayLookups() {
|
||||
|
|
@ -6181,7 +6246,15 @@ void Application::spawnOnlineGameObject(uint64_t guid, uint32_t entry, uint32_t
|
|||
auto itCache = gameObjectDisplayIdModelCache_.find(displayId);
|
||||
if (itCache != gameObjectDisplayIdModelCache_.end()) {
|
||||
modelId = itCache->second;
|
||||
} else {
|
||||
if (!m2Renderer->hasModel(modelId)) {
|
||||
LOG_WARNING("GO M2 cache hit but model gone: displayId=", displayId,
|
||||
" modelId=", modelId, " path=", modelPath,
|
||||
" — reloading");
|
||||
gameObjectDisplayIdModelCache_.erase(itCache);
|
||||
itCache = gameObjectDisplayIdModelCache_.end();
|
||||
}
|
||||
}
|
||||
if (itCache == gameObjectDisplayIdModelCache_.end()) {
|
||||
modelId = nextGameObjectModelId_++;
|
||||
|
||||
auto m2Data = assetManager->readFile(modelPath);
|
||||
|
|
|
|||
|
|
@ -383,6 +383,85 @@ void CharacterRenderer::shutdown() {
|
|||
vkCtx_ = nullptr;
|
||||
}
|
||||
|
||||
void CharacterRenderer::clear() {
|
||||
if (!vkCtx_) return;
|
||||
|
||||
LOG_INFO("CharacterRenderer::clear instances=", instances.size(),
|
||||
" models=", models.size());
|
||||
|
||||
vkDeviceWaitIdle(vkCtx_->getDevice());
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
VmaAllocator alloc = vkCtx_->getAllocator();
|
||||
|
||||
// Destroy GPU resources for all models
|
||||
for (auto& pair : models) {
|
||||
destroyModelGPU(pair.second);
|
||||
}
|
||||
|
||||
// Destroy bone buffers for all instances
|
||||
for (auto& pair : instances) {
|
||||
destroyInstanceBones(pair.second);
|
||||
}
|
||||
|
||||
// Clear texture cache (VkTexture unique_ptrs auto-destroy)
|
||||
textureCache.clear();
|
||||
textureHasAlphaByPtr_.clear();
|
||||
textureColorKeyBlackByPtr_.clear();
|
||||
textureCacheBytes_ = 0;
|
||||
textureCacheCounter_ = 0;
|
||||
loggedTextureLoadFails_.clear();
|
||||
|
||||
// Clear composite and failed caches
|
||||
compositeCache_.clear();
|
||||
failedTextureCache_.clear();
|
||||
|
||||
// Recreate default textures (needed by loadModel/loadTexture fallbacks)
|
||||
whiteTexture_.reset();
|
||||
transparentTexture_.reset();
|
||||
flatNormalTexture_.reset();
|
||||
{
|
||||
uint8_t white[] = {255, 255, 255, 255};
|
||||
whiteTexture_ = std::make_unique<VkTexture>();
|
||||
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<VkTexture>();
|
||||
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<VkTexture>();
|
||||
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);
|
||||
}
|
||||
|
||||
models.clear();
|
||||
instances.clear();
|
||||
|
||||
// Release deferred transient material UBOs
|
||||
for (int i = 0; i < 2; i++) {
|
||||
for (const auto& b : transientMaterialUbos_[i]) {
|
||||
if (b.first) {
|
||||
vmaDestroyBuffer(alloc, b.first, b.second);
|
||||
}
|
||||
}
|
||||
transientMaterialUbos_[i].clear();
|
||||
}
|
||||
|
||||
// Reset descriptor pools (don't destroy — reuse for new allocations)
|
||||
for (int i = 0; i < 2; i++) {
|
||||
if (materialDescPools_[i]) {
|
||||
vkResetDescriptorPool(device, materialDescPools_[i], 0);
|
||||
}
|
||||
}
|
||||
if (boneDescPool_) {
|
||||
vkResetDescriptorPool(device, boneDescPool_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void CharacterRenderer::destroyModelGPU(M2ModelGPU& gpuModel) {
|
||||
if (!vkCtx_) return;
|
||||
VmaAllocator alloc = vkCtx_->getAllocator();
|
||||
|
|
|
|||
|
|
@ -3254,6 +3254,35 @@ void M2Renderer::clear() {
|
|||
for (auto& inst : instances) {
|
||||
destroyInstanceBones(inst);
|
||||
}
|
||||
// Reset descriptor pools so new allocations succeed after reload.
|
||||
// destroyModelGPU/destroyInstanceBones don't free individual sets,
|
||||
// so the pools fill up across map changes without this reset.
|
||||
VkDevice device = vkCtx_->getDevice();
|
||||
if (materialDescPool_) {
|
||||
vkResetDescriptorPool(device, materialDescPool_, 0);
|
||||
// Re-allocate the glow texture descriptor set (pre-allocated during init,
|
||||
// invalidated by pool reset).
|
||||
if (glowTexture_ && particleTexLayout_) {
|
||||
VkDescriptorSetAllocateInfo ai{VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO};
|
||||
ai.descriptorPool = materialDescPool_;
|
||||
ai.descriptorSetCount = 1;
|
||||
ai.pSetLayouts = &particleTexLayout_;
|
||||
glowTexDescSet_ = VK_NULL_HANDLE;
|
||||
if (vkAllocateDescriptorSets(device, &ai, &glowTexDescSet_) == VK_SUCCESS) {
|
||||
VkDescriptorImageInfo imgInfo = glowTexture_->descriptorInfo();
|
||||
VkWriteDescriptorSet write{VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET};
|
||||
write.dstSet = glowTexDescSet_;
|
||||
write.dstBinding = 0;
|
||||
write.descriptorCount = 1;
|
||||
write.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
|
||||
write.pImageInfo = &imgInfo;
|
||||
vkUpdateDescriptorSets(device, 1, &write, 0, nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (boneDescPool_) {
|
||||
vkResetDescriptorPool(device, boneDescPool_, 0);
|
||||
}
|
||||
}
|
||||
models.clear();
|
||||
instances.clear();
|
||||
|
|
|
|||
|
|
@ -3165,6 +3165,10 @@ void Renderer::renderOverlay(const glm::vec4& color) {
|
|||
void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
||||
(void)world;
|
||||
|
||||
// GPU crash diagnostic: skip ALL world rendering to isolate crash source
|
||||
static const bool skipAll = (std::getenv("WOWEE_SKIP_ALL_RENDER") != nullptr);
|
||||
if (skipAll) return;
|
||||
|
||||
auto renderStart = std::chrono::steady_clock::now();
|
||||
lastTerrainRenderMs = 0.0;
|
||||
lastWMORenderMs = 0.0;
|
||||
|
|
@ -3208,6 +3212,11 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
|||
skySystem->render(currentCmd, perFrameSet, *camera, skyParams);
|
||||
}
|
||||
|
||||
// GPU crash diagnostic: skip individual renderers to isolate which one faults
|
||||
static const bool skipWMO = (std::getenv("WOWEE_SKIP_WMO") != nullptr);
|
||||
static const bool skipChars = (std::getenv("WOWEE_SKIP_CHARS") != nullptr);
|
||||
static const bool skipM2 = (std::getenv("WOWEE_SKIP_M2") != nullptr);
|
||||
|
||||
// Terrain (opaque pass)
|
||||
if (terrainRenderer && camera && terrainEnabled) {
|
||||
auto terrainStart = std::chrono::steady_clock::now();
|
||||
|
|
@ -3217,7 +3226,7 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
|||
}
|
||||
|
||||
// WMO buildings (opaque, drawn before characters so selection circle sits on top)
|
||||
if (wmoRenderer && camera) {
|
||||
if (wmoRenderer && camera && !skipWMO) {
|
||||
auto wmoStart = std::chrono::steady_clock::now();
|
||||
wmoRenderer->render(currentCmd, perFrameSet, *camera);
|
||||
lastWMORenderMs = std::chrono::duration<double, std::milli>(
|
||||
|
|
@ -3228,12 +3237,12 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) {
|
|||
renderSelectionCircle(view, projection);
|
||||
|
||||
// Characters (after selection circle so units draw over the ring)
|
||||
if (characterRenderer && camera) {
|
||||
if (characterRenderer && camera && !skipChars) {
|
||||
characterRenderer->render(currentCmd, perFrameSet, *camera);
|
||||
}
|
||||
|
||||
// M2 doodads, creatures, glow sprites, particles
|
||||
if (m2Renderer && camera) {
|
||||
if (m2Renderer && camera && !skipM2) {
|
||||
if (cameraController) {
|
||||
m2Renderer->setInsideInterior(cameraController->isInsideWMO());
|
||||
m2Renderer->setOnTaxi(cameraController->isOnTaxi());
|
||||
|
|
@ -3393,21 +3402,21 @@ bool Renderer::loadTestTerrain(pipeline::AssetManager* assetManager, const std::
|
|||
if (!wmoRenderer) {
|
||||
wmoRenderer = std::make_unique<WMORenderer>();
|
||||
wmoRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
||||
if (shadowRenderPass != VK_NULL_HANDLE) {
|
||||
wmoRenderer->initializeShadow(shadowRenderPass);
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize shadow pipelines (Phase 7/8)
|
||||
if (wmoRenderer && shadowRenderPass != VK_NULL_HANDLE) {
|
||||
wmoRenderer->initializeShadow(shadowRenderPass);
|
||||
}
|
||||
if (m2Renderer && shadowRenderPass != VK_NULL_HANDLE) {
|
||||
// Initialize shadow pipelines for M2 if not yet done
|
||||
if (m2Renderer && shadowRenderPass != VK_NULL_HANDLE && !m2Renderer->hasShadowPipeline()) {
|
||||
m2Renderer->initializeShadow(shadowRenderPass);
|
||||
}
|
||||
if (!characterRenderer) {
|
||||
characterRenderer = std::make_unique<CharacterRenderer>();
|
||||
characterRenderer->initialize(vkCtx, perFrameSetLayout, assetManager);
|
||||
}
|
||||
if (characterRenderer && shadowRenderPass != VK_NULL_HANDLE) {
|
||||
characterRenderer->initializeShadow(shadowRenderPass);
|
||||
if (shadowRenderPass != VK_NULL_HANDLE) {
|
||||
characterRenderer->initializeShadow(shadowRenderPass);
|
||||
}
|
||||
}
|
||||
|
||||
// Create and initialize terrain manager
|
||||
|
|
@ -3862,6 +3871,8 @@ void Renderer::renderReflectionPass() {
|
|||
}
|
||||
|
||||
void Renderer::renderShadowPass() {
|
||||
static const bool skipShadows = (std::getenv("WOWEE_SKIP_SHADOWS") != nullptr);
|
||||
if (skipShadows) return;
|
||||
if (!shadowsEnabled || shadowDepthImage == VK_NULL_HANDLE) return;
|
||||
if (currentCmd == VK_NULL_HANDLE) return;
|
||||
|
||||
|
|
|
|||
|
|
@ -557,7 +557,9 @@ void SwimEffects::update(const Camera& camera, const CameraController& cc,
|
|||
}
|
||||
|
||||
// --- Bubble spawning ---
|
||||
bool underwater = camWaterH && camPos.z < *camWaterH;
|
||||
// Require swimming state to prevent spurious bubbles on login/teleport
|
||||
// when camera may briefly appear below a water surface before grounding.
|
||||
bool underwater = swimming && camWaterH && camPos.z < *camWaterH;
|
||||
if (underwater) {
|
||||
float bubbleRate = 20.0f;
|
||||
bubbleSpawnAccum += bubbleRate * deltaTime;
|
||||
|
|
|
|||
|
|
@ -1059,6 +1059,11 @@ void WMORenderer::clearAll() {
|
|||
if (entry.texture) entry.texture->destroy(device, allocator);
|
||||
if (entry.normalHeightMap) entry.normalHeightMap->destroy(device, allocator);
|
||||
}
|
||||
|
||||
// Reset descriptor pool so new allocations succeed after reload
|
||||
if (materialDescPool_) {
|
||||
vkResetDescriptorPool(device, materialDescPool_, 0);
|
||||
}
|
||||
}
|
||||
|
||||
loadedModels.clear();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue