Fix terrain loss after map transition and GPU crash on WMO-only maps

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.
This commit is contained in:
Kelsi 2026-03-02 09:52:09 -08:00
parent 5519c73f5c
commit 335b1b1c3a
3 changed files with 34 additions and 12 deletions

View file

@ -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<double, std::milli>(

View file

@ -912,8 +912,8 @@ bool TerrainManager::advanceFinalization(FinalizingTile& ft) {
// Commit tile to loadedTiles
auto tile = std::make_unique<TerrainTile>();
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<std::mutex> lock(tileCacheMutex_);
tileCache_.clear();
tileCacheLru_.clear();
tileCacheBytes_ = 0;
}
LOG_INFO("Soft-resetting terrain (clearing tiles + water + cache, workers stay alive)");
loadedTiles.clear();
failedTiles.clear();

View file

@ -320,6 +320,10 @@ void TerrainRenderer::shutdown() {
bool TerrainRenderer::loadTerrain(const pipeline::TerrainMesh& mesh,
const std::vector<std::string>& 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++) {