refactor(rendering): introduce InstancedMeshRenderer with GfxObj grouping

Groups all (entity, meshRef) pairs by GfxObjId before drawing so each
GfxObj's sub-meshes are processed as a contiguous batch.  Still uses
per-entity uniform uModel — visual output is identical to the old
StaticMeshRenderer — but the _groups dict is the structural prerequisite
for swapping to DrawElementsInstanced in the follow-up commit.

Key changes:
- New InstancedMeshRenderer.cs with CollectGroups() that fills
  _groups[gfxObjId] = List<InstanceEntry> each frame, reusing the
  inner List<> objects to avoid per-frame allocation.
- Same two-pass (opaque+clipmap first, translucent second) draw logic
  from StaticMeshRenderer, now iterating over groups rather than raw
  entity/meshRef pairs.
- GameWindow.cs: field and constructor swapped from StaticMeshRenderer
  to InstancedMeshRenderer — public API is identical.
- 431 tests green, 0 warnings.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-04-13 18:46:20 +02:00
parent 08e309c357
commit b5099e2b21
2 changed files with 369 additions and 2 deletions

View file

@ -23,7 +23,7 @@ public sealed class GameWindow : IDisposable
private DatCollection? _dats;
private float _lastMouseX;
private float _lastMouseY;
private StaticMeshRenderer? _staticMesh;
private InstancedMeshRenderer? _staticMesh;
private Shader? _meshShader;
private TextureCache? _textureCache;
@ -370,7 +370,7 @@ public sealed class GameWindow : IDisposable
_surfaceCache = new Dictionary<uint, AcDream.Core.Terrain.SurfaceInfo>();
_textureCache = new TextureCache(_gl, _dats);
_staticMesh = new StaticMeshRenderer(_gl, _meshShader, _textureCache);
_staticMesh = new InstancedMeshRenderer(_gl, _meshShader, _textureCache);
// Phase A.1: replace the one-shot 3×3 preload with a streaming controller.
// Parse runtime radius from environment (default 2 → 5×5 window).