From 872b10fe6862b64dcdf6f9c3ff02e913b46d4319 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Wed, 25 Feb 2026 13:26:08 -0800 Subject: [PATCH] Fix water descriptor pool leak and add water rendering diagnostics - Add VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT to water material descriptor pool so individual sets can be freed when tiles are unloaded - Free descriptor sets in destroyWaterMesh() instead of leaking them - Add terrain manager unloadAll() during logout to properly clear stale tiles, water surfaces, and queues between sessions - Add diagnostic logging for water surface loading, material allocation failures, and render skip reasons to investigate missing water --- include/rendering/water_renderer.hpp | 1 + src/core/application.cpp | 5 ++++- src/rendering/terrain_manager.cpp | 8 ++++++++ src/rendering/water_renderer.cpp | 29 ++++++++++++++++++++++++---- 4 files changed, 38 insertions(+), 5 deletions(-) diff --git a/include/rendering/water_renderer.hpp b/include/rendering/water_renderer.hpp index cf04cbc5..99767782 100644 --- a/include/rendering/water_renderer.hpp +++ b/include/rendering/water_renderer.hpp @@ -172,6 +172,7 @@ private: VkImageView sceneDepthView = VK_NULL_HANDLE; VkExtent2D sceneHistoryExtent = {0, 0}; bool sceneHistoryReady = false; + mutable uint32_t renderDiagCounter_ = 0; // Planar reflection resources static constexpr uint32_t REFLECTION_WIDTH = 512; diff --git a/src/core/application.cpp b/src/core/application.cpp index eb9d0a7a..e7d7f656 100644 --- a/src/core/application.cpp +++ b/src/core/application.cpp @@ -661,7 +661,10 @@ void Application::logoutToLogin() { if (auto* m2 = renderer->getM2Renderer()) { m2->clear(); } - // TerrainManager will be re-initialized on next world entry + // Unload all terrain tiles + water surfaces so next world entry starts fresh + if (auto* terrain = renderer->getTerrainManager()) { + terrain->unloadAll(); + } if (auto* questMarkers = renderer->getQuestMarkerRenderer()) { questMarkers->clear(); } diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index 5caeab9d..afd50824 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -705,7 +705,15 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) { // Load water immediately after terrain (same frame) — water is now // deduplicated to ~1-2 merged surfaces per tile, so this is fast. if (waterRenderer) { + size_t beforeSurfaces = waterRenderer->getSurfaceCount(); waterRenderer->loadFromTerrain(pending->terrain, true, x, y); + size_t afterSurfaces = waterRenderer->getSurfaceCount(); + if (afterSurfaces > beforeSurfaces) { + LOG_INFO("Water: tile [", x, ",", y, "] added ", afterSurfaces - beforeSurfaces, + " surfaces (total: ", afterSurfaces, ")"); + } + } else { + LOG_WARNING("Water: waterRenderer is null during tile [", x, ",", y, "] finalization!"); } // Ensure M2 renderer has asset manager diff --git a/src/rendering/water_renderer.cpp b/src/rendering/water_renderer.cpp index 8ecb1d7a..09d07b0b 100644 --- a/src/rendering/water_renderer.cpp +++ b/src/rendering/water_renderer.cpp @@ -76,6 +76,7 @@ bool WaterRenderer::initialize(VkContext* ctx, VkDescriptorSetLayout perFrameLay VkDescriptorPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; + poolInfo.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; poolInfo.maxSets = MAX_WATER_SETS; poolInfo.poolSizeCount = 1; poolInfo.pPoolSizes = &poolSize; @@ -541,6 +542,8 @@ void WaterRenderer::updateMaterialUBO(WaterSurface& surface) { write.pBufferInfo = &bufInfo; vkUpdateDescriptorSets(vkCtx->getDevice(), 1, &write, 0, nullptr); + } else { + LOG_WARNING("Water: failed to allocate material descriptor set (pool exhaustion?)"); } } @@ -802,8 +805,10 @@ void WaterRenderer::loadFromTerrain(const pipeline::ADTTerrain& terrain, bool ap totalSurfaces++; } - LOG_DEBUG("Water: Loaded ", totalSurfaces, " surfaces from tile [", tileX, ",", tileY, - "] (", mergeGroups.size(), " groups), total surfaces: ", surfaces.size()); + if (totalSurfaces > 0) { + LOG_INFO("Water: Loaded ", totalSurfaces, " surfaces from tile [", tileX, ",", tileY, + "] (", mergeGroups.size(), " groups), total surfaces: ", surfaces.size()); + } } void WaterRenderer::removeTile(int tileX, int tileY) { @@ -936,8 +941,21 @@ void WaterRenderer::clear() { void WaterRenderer::render(VkCommandBuffer cmd, VkDescriptorSet perFrameSet, const Camera& /*camera*/, float /*time*/, bool use1x) { VkPipeline pipeline = (use1x && water1xPipeline) ? water1xPipeline : waterPipeline; - if (!renderingEnabled || surfaces.empty() || !pipeline) return; - if (!sceneSet) return; + if (!renderingEnabled || surfaces.empty() || !pipeline) { + if (renderDiagCounter_++ % 300 == 0 && !surfaces.empty()) { + LOG_WARNING("Water: render skipped — enabled=", renderingEnabled, + " surfaces=", surfaces.size(), + " pipeline=", (pipeline ? "ok" : "null"), + " use1x=", use1x); + } + return; + } + if (!sceneSet) { + if (renderDiagCounter_++ % 300 == 0) { + LOG_WARNING("Water: render skipped — sceneSet is null, surfaces=", surfaces.size()); + } + return; + } vkCmdBindPipeline(cmd, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); @@ -1251,6 +1269,9 @@ void WaterRenderer::destroyWaterMesh(WaterSurface& surface) { destroyBuffer(allocator, ab); surface.materialUBO = VK_NULL_HANDLE; } + if (surface.materialSet && materialDescPool) { + vkFreeDescriptorSets(vkCtx->getDevice(), materialDescPool, 1, &surface.materialSet); + } surface.materialSet = VK_NULL_HANDLE; }