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;