From 09f42d062bec417e48d7b4f786c15da2ae47e8c6 Mon Sep 17 00:00:00 2001 From: Sebastien Lebreton Date: Fri, 12 Jun 2026 22:44:54 +0200 Subject: [PATCH] fix(heightmap): Prevent per-frame full terrain rebuild with DrawEntireTerrain With DrawEntireTerrain=Yes (and likewise StretchTerrain=Yes), WorldHeightMap::createDrawArea() overrides the stored draw size to the full map (or the stretch size), but W3DView::updateTerrain() keeps requesting the normal draw size through setTerrainDrawSize() every frame. Because setTerrainDrawSize() compared the requested normal size against the stored full-map size, its unchanged-check never matched. As a result it reinitialized and fully updated the entire terrain (vertices and lighting) on every frame, and additionally set m_needFullUpdate, causing updateCenter() to perform a second full-map update. This per-frame full rebuild only appeared with these settings and caused the massive performance degradation reported in #2743 (regressed by #2677, which introduced the per-frame setTerrainDrawSize() call). Fix in two parts: 1. Resolve the effective draw size inside setTerrainDrawSize() the same way createDrawArea() does, by applying the StretchTerrain and DrawEntireTerrain overrides there as well, so the unchanged-check matches and the terrain is no longer reinitialized and rebuilt every frame. 2. That per-frame rebuild was, however, the only thing repainting the terrain when the vertex grid already covers the whole map: HeightMapRenderObjClass::updateCenter() early-returns in that case (small maps, StretchTerrain or DrawEntireTerrain) and so never reached its m_needFullUpdate handling. Once the per-frame rebuild is gone, pending full updates requested by staticLightingChanged(), ReAcquireResources() after a device reset, time-of-day changes or late texture loads would never be applied, leaving undrawn (black) and untextured (grey) tiles on the shell map. updateCenter() now honors a pending m_needFullUpdate in that early-return path with a single full updateBlock, so those event-driven repaints still happen, but only when actually requested instead of every frame. Steady-state behavior returns to a one-time full update plus the normal full-map draw (i.e. the pre-#2677 cost), while terrain still updates correctly on lighting, device-reset and texture changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../Source/W3DDevice/GameClient/HeightMap.cpp | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp index 0789177cf81..7939d9457e3 100644 --- a/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp +++ b/Core/GameEngineDevice/Source/W3DDevice/GameClient/HeightMap.cpp @@ -1207,6 +1207,23 @@ void HeightMapRenderObjClass::setTerrainDrawSize(Int width, Int height) else height = std::min(height, m_map->getYExtent()); + // TheSuperHackers @bugfix sailro 13/06/2026 Apply the StretchTerrain and DrawEntireTerrain size + // overrides here so the requested size can match the stored full-map draw size. Otherwise the normal + // size is requested every frame and never matches, rebuilding the whole terrain (vertices and + // lighting) every frame and badly degrading performance. DrawEntireTerrain takes precedence. + if (TheGlobalData && TheGlobalData->m_stretchTerrain) + { + width = WorldHeightMap::STRETCH_DRAW_WIDTH; + height = WorldHeightMap::STRETCH_DRAW_HEIGHT; + } + if (TheGlobalData && TheGlobalData->m_drawEntireTerrain) + { + width = m_map->getXExtent(); + height = m_map->getYExtent(); + } + width = std::min(width, m_map->getXExtent()); + height = std::min(height, m_map->getYExtent()); + if (width == m_map->getDrawWidth() && height == m_map->getDrawHeight()) return; @@ -1672,6 +1689,17 @@ void HeightMapRenderObjClass::updateCenter(CameraClass *camera, const Vector3 *c if (m_x >= m_map->getXExtent() && m_y >= m_map->getYExtent()) { + // TheSuperHackers @bugfix sailro 13/06/2026 The vertex grid already covers the whole terrain + // (small maps, or StretchTerrain/DrawEntireTerrain), so there is no scrolling to do. Still honor a + // pending full update here, otherwise lighting, texture or height changes would never be applied. + // This repaints only when an update is actually requested, not every frame. + if (m_needFullUpdate) + { + m_updating = true; + m_needFullUpdate = false; + updateBlock(0, 0, m_x-1, m_y-1, m_map, pLightsIterator); + m_updating = false; + } return; // no need to center. }