From 335b1b1c3aab37a20dd2152c3a7d90a719b64e21 Mon Sep 17 00:00:00 2001 From: Kelsi Date: Mon, 2 Mar 2026 09:52:09 -0800 Subject: [PATCH] Fix terrain loss after map transition and GPU crash on WMO-only maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Three fixes: 1. Water captureSceneHistory gated on hasSurfaces() — the image layout transitions (PRESENT_SRC→TRANSFER_SRC→PRESENT_SRC) were running every frame even on WMO-only maps with no water, causing VK_ERROR_DEVICE_LOST. 2. Tile cache invalidation: softReset() now clears tileCache_ since cache keys are (x,y) without map name — prevents stale cross-map cache hits. 3. Copy terrain/mesh into TerrainTile instead of std::move — the moved-from PendingTile was cached with empty data, so subsequent map loads returned tiles with 0 valid chunks from cache. Also adds diagnostic skip env vars (WOWEE_SKIP_TERRAIN, WOWEE_SKIP_SKY, WOWEE_SKIP_PREPASSES) and a 0-chunk warning in loadTerrain. --- src/rendering/renderer.cpp | 27 ++++++++++++++++++--------- src/rendering/terrain_manager.cpp | 15 ++++++++++++--- src/rendering/terrain_renderer.cpp | 4 ++++ 3 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/rendering/renderer.cpp b/src/rendering/renderer.cpp index 746ced7d..537e5398 100644 --- a/src/rendering/renderer.cpp +++ b/src/rendering/renderer.cpp @@ -977,6 +977,10 @@ void Renderer::beginFrame() { // Update per-frame UBO with current camera/lighting state updatePerFrameUBO(); + // GPU crash diagnostic: skip all pre-passes to isolate crash source + static const bool skipPrePasses = (std::getenv("WOWEE_SKIP_PREPASSES") != nullptr); + + if (!skipPrePasses) { // --- Off-screen pre-passes (before main render pass) --- // Minimap composite (renders 3x3 tile grid into 768x768 render target) if (minimap && minimap->isEnabled() && camera) { @@ -1004,6 +1008,7 @@ void Renderer::beginFrame() { // Water reflection pre-pass (renders scene from mirrored camera into 512x512 texture) renderReflectionPass(); + } // !skipPrePasses // --- Begin main render pass (clear color + depth) --- VkRenderPassBeginInfo rpInfo{}; @@ -1049,7 +1054,9 @@ void Renderer::endFrame() { vkCmdEndRenderPass(currentCmd); - if (waterRenderer && currentImageIndex < vkCtx->getSwapchainImages().size()) { + // Only capture scene history when water surfaces exist (avoids GPU crash on WMO-only maps + // where scene history images may never be properly used but layout transitions still run) + if (waterRenderer && waterRenderer->hasSurfaces() && currentImageIndex < vkCtx->getSwapchainImages().size()) { waterRenderer->captureSceneHistory( currentCmd, vkCtx->getSwapchainImages()[currentImageIndex], @@ -1059,7 +1066,7 @@ void Renderer::endFrame() { } // Render water in separate 1x pass after MSAA resolve + scene capture - bool waterDeferred = waterRenderer && waterRenderer->hasWater1xPass() + bool waterDeferred = waterRenderer && waterRenderer->hasSurfaces() && waterRenderer->hasWater1xPass() && vkCtx->getMsaaSamples() != VK_SAMPLE_COUNT_1_BIT; if (waterDeferred && camera) { VkExtent2D ext = vkCtx->getSwapchainExtent(); @@ -3183,11 +3190,18 @@ void Renderer::renderWorld(game::World* world, game::GameHandler* gameHandler) { const glm::mat4& view = camera ? camera->getViewMatrix() : glm::mat4(1.0f); const glm::mat4& projection = camera ? camera->getProjectionMatrix() : glm::mat4(1.0f); + // 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); + static const bool skipTerrain = (std::getenv("WOWEE_SKIP_TERRAIN") != nullptr); + static const bool skipSky = (std::getenv("WOWEE_SKIP_SKY") != nullptr); + // Get time of day for sky-related rendering float timeOfDay = (skySystem && skySystem->getSkybox()) ? skySystem->getSkybox()->getTimeOfDay() : 12.0f; // Render sky system (unified coordinator for skybox, stars, celestial, clouds, lens flare) - if (skySystem && camera) { + if (skySystem && camera && !skipSky) { rendering::SkyParams skyParams; skyParams.timeOfDay = timeOfDay; skyParams.gameTime = gameHandler ? gameHandler->getGameTime() : -1.0f; @@ -3216,13 +3230,8 @@ 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) { + if (terrainRenderer && camera && terrainEnabled && !skipTerrain) { auto terrainStart = std::chrono::steady_clock::now(); terrainRenderer->render(currentCmd, perFrameSet, *camera); lastTerrainRenderMs = std::chrono::duration( diff --git a/src/rendering/terrain_manager.cpp b/src/rendering/terrain_manager.cpp index a47806d3..1f5c8066 100644 --- a/src/rendering/terrain_manager.cpp +++ b/src/rendering/terrain_manager.cpp @@ -912,8 +912,8 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) { // Commit tile to loadedTiles auto tile = std::make_unique(); tile->coord = coord; - tile->terrain = std::move(pending->terrain); - tile->mesh = std::move(pending->mesh); + tile->terrain = pending->terrain; // copy (not move) — pending is cached for reuse + tile->mesh = pending->mesh; // copy (not move) — pending is cached for reuse tile->loaded = true; tile->m2InstanceIds = std::move(ft.m2InstanceIds); tile->wmoInstanceIds = std::move(ft.wmoInstanceIds); @@ -1357,7 +1357,16 @@ void TerrainManager::softReset() { finalizingTiles_.clear(); placedDoodadIds.clear(); - LOG_INFO("Soft-resetting terrain (clearing tiles + water, workers stay alive)"); + // Clear tile cache — keys are (x,y) without map name, so stale entries from + // a different map with overlapping coordinates would produce wrong geometry. + { + std::lock_guard lock(tileCacheMutex_); + tileCache_.clear(); + tileCacheLru_.clear(); + tileCacheBytes_ = 0; + } + + LOG_INFO("Soft-resetting terrain (clearing tiles + water + cache, workers stay alive)"); loadedTiles.clear(); failedTiles.clear(); diff --git a/src/rendering/terrain_renderer.cpp b/src/rendering/terrain_renderer.cpp index 208ae25b..6e312233 100644 --- a/src/rendering/terrain_renderer.cpp +++ b/src/rendering/terrain_renderer.cpp @@ -320,6 +320,10 @@ void TerrainRenderer::shutdown() { bool TerrainRenderer::loadTerrain(const pipeline::TerrainMesh& mesh, const std::vector& texturePaths, int tileX, int tileY) { + if (mesh.validChunkCount == 0) { + LOG_WARNING("loadTerrain[", tileX, ",", tileY, "]: mesh has 0 valid chunks (", texturePaths.size(), " textures)"); + return false; + } LOG_DEBUG("Loading terrain mesh: ", mesh.validChunkCount, " chunks"); for (int y = 0; y < 16; y++) {