From 495f87a4ad7a563ea1fadd18b6ea3e9d1eb97a81 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 11 Apr 2026 22:22:49 +0200 Subject: [PATCH] =?UTF-8?q?feat(app):=20Phase=20A.1=20=E2=80=94=20TerrainR?= =?UTF-8?q?enderer.RemoveLandblock=20for=20streaming=20unloads?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TerrainRenderer's internal landblock collection is now a Dictionary keyed by landblock id so the streaming system can release GPU resources per-landblock as the visible window moves. AddLandblock takes the id as its first parameter; if the same id is added twice, the old buffers are freed before the new ones land (defensive but cheap). RemoveLandblock is a no-op for unknown ids and deletes VBO/EBO/VAO for known ones. Single existing caller in GameWindow.cs updated to pass the id. Build green. No unit tests — direct-to-GL methods need a live context. Tasks 5-7 will validate end-to-end. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/AcDream.App/Rendering/GameWindow.cs | 2 +- src/AcDream.App/Rendering/TerrainRenderer.cs | 36 +++++++++++++++++--- 2 files changed, 32 insertions(+), 6 deletions(-) diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index bf46b46..261c158 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -245,7 +245,7 @@ public sealed class GameWindow : IDisposable ((int)lbY - centerY) * 192f, 0f); - _terrain.AddLandblock(meshData, origin); + _terrain.AddLandblock(lb.LandblockId, meshData, origin); } Console.WriteLine($"terrain: {surfaceCache.Count} unique palette codes across {worldView.Landblocks.Count} landblocks"); diff --git a/src/AcDream.App/Rendering/TerrainRenderer.cs b/src/AcDream.App/Rendering/TerrainRenderer.cs index 2c4b768..ecd3d49 100644 --- a/src/AcDream.App/Rendering/TerrainRenderer.cs +++ b/src/AcDream.App/Rendering/TerrainRenderer.cs @@ -22,7 +22,7 @@ public sealed unsafe class TerrainRenderer : IDisposable private readonly GL _gl; private readonly Shader _shader; private readonly TerrainAtlas _atlas; - private readonly List _landblocks = new(); + private readonly Dictionary _landblocks = new(); public TerrainRenderer(GL gl, Shader shader, TerrainAtlas atlas) { @@ -31,10 +31,19 @@ public sealed unsafe class TerrainRenderer : IDisposable _atlas = atlas; } - public void AddLandblock(LandblockMeshData meshData, Vector3 worldOrigin) + public void AddLandblock(uint landblockId, LandblockMeshData meshData, Vector3 worldOrigin) { + if (_landblocks.TryGetValue(landblockId, out var existing)) + { + _gl.DeleteBuffer(existing.Vbo); + _gl.DeleteBuffer(existing.Ebo); + _gl.DeleteVertexArray(existing.Vao); + _landblocks.Remove(landblockId); + } + var gpu = new LandblockGpu { + LandblockId = landblockId, Vao = _gl.GenVertexArray(), WorldOrigin = worldOrigin, IndexCount = meshData.Indices.Length, @@ -76,7 +85,23 @@ public sealed unsafe class TerrainRenderer : IDisposable _gl.VertexAttribIPointer(5, 4, VertexAttribIType.UnsignedByte, stride, (void*)(dataOffset + 12)); _gl.BindVertexArray(0); - _landblocks.Add(gpu); + _landblocks[landblockId] = gpu; + } + + /// + /// Release GPU buffers for a previously-added landblock. No-op if the + /// landblock wasn't added. Called by the streaming system when a + /// landblock falls outside the visible window. + /// + public void RemoveLandblock(uint landblockId) + { + if (!_landblocks.TryGetValue(landblockId, out var gpu)) + return; + + _gl.DeleteBuffer(gpu.Vbo); + _gl.DeleteBuffer(gpu.Ebo); + _gl.DeleteVertexArray(gpu.Vao); + _landblocks.Remove(landblockId); } public void Draw(ICamera camera) @@ -96,7 +121,7 @@ public sealed unsafe class TerrainRenderer : IDisposable int alphaLoc = _gl.GetUniformLocation(_shader.Program, "uAlpha"); if (alphaLoc >= 0) _gl.Uniform1(alphaLoc, 1); - foreach (var lb in _landblocks) + foreach (var lb in _landblocks.Values) { var model = Matrix4x4.CreateTranslation(lb.WorldOrigin); _shader.SetMatrix4("uModel", model); @@ -108,7 +133,7 @@ public sealed unsafe class TerrainRenderer : IDisposable public void Dispose() { - foreach (var lb in _landblocks) + foreach (var lb in _landblocks.Values) { _gl.DeleteBuffer(lb.Vbo); _gl.DeleteBuffer(lb.Ebo); @@ -119,6 +144,7 @@ public sealed unsafe class TerrainRenderer : IDisposable private sealed class LandblockGpu { + public uint LandblockId; public uint Vao; public uint Vbo; public uint Ebo;