phase(N.4) Task 9: real WB pipeline bring-up + InstancedMeshRenderer routing

WbMeshAdapter now actually constructs the WB pipeline:
- OpenGLGraphicsDevice(gl, logger, DebugRenderSettings)
- DefaultDatReaderWriter(datDir) — opens its own file handles for now
  (memory cost ~50-100MB of duplicate index caches, acceptable for
  foundation work per plan Adjustment 1)
- ObjectMeshManager(graphicsDevice, dats, NullLogger)

InstancedMeshRenderer.EnsureUploaded routes through the adapter when
ACDREAM_USE_WB_FOUNDATION=1 is set; uses a WbManagedSentinel entry
in the local cache to mark "this GfxObj lives in WB now". CollectGroups
skips sentinel entries; both Draw passes skip them; Dispose skips them
(no GL resources to free — ObjectMeshManager owns those). Task 22's
WbDrawDispatcher will eventually draw WB-managed objects. With flag
off, behavior is byte-identical to before.

WbMeshAdapter constructor signature changed from (GL, DatCollection,
Logger) to (GL, string datDir, Logger). Updated tests to use
CreateUninitialized() for behavior tests and single null-GL guard test
for constructor validation. GameWindow updated to pass _datDir and to
wire _wbMeshAdapter into InstancedMeshRenderer.

AcDream.App.csproj gets direct ProjectReferences to WorldBuilder.Shared
and Chorizite.OpenGLSDLBackend — project refs are not transitive in
.NET, so AcDream.App must list them explicitly even though AcDream.Core
already references them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-08 13:31:30 +02:00
parent 3d111e473e
commit 4ad7a985cf
5 changed files with 137 additions and 49 deletions

View file

@ -10,22 +10,11 @@ public sealed class WbMeshAdapterTests
[Fact]
public void Construct_WithNullGl_ThrowsArgumentNull()
{
// GL is the first guarded parameter; verifies the constructor validates inputs.
// We can't pass a real GL (no context in tests), so we verify only the
// null-GL guard. The real pipeline is tested via integration.
Assert.Throws<ArgumentNullException>(() =>
new WbMeshAdapter(gl: null!, dats: null!, logger: NullLogger<WbMeshAdapter>.Instance));
}
[Fact]
public void Construct_WithNullDats_ThrowsArgumentNull()
{
// GL cannot be constructed without a real GL context, so we verify
// the dats-null guard by passing a non-null GL sentinel — we reach
// the dats guard on the way. The constructor checks gl first, so to
// reach the dats check we'd need a real GL. Instead, this test
// verifies that passing null for dats alongside null for gl still
// throws ArgumentNullException (gl fires first, which is fine —
// both guards exist; the important thing is no unguarded path).
Assert.Throws<ArgumentNullException>(() =>
new WbMeshAdapter(gl: null!, dats: null!, logger: NullLogger<WbMeshAdapter>.Instance));
new WbMeshAdapter(gl: null!, datDir: "some/path", logger: NullLogger<WbMeshAdapter>.Instance));
}
[Fact]
@ -42,6 +31,19 @@ public sealed class WbMeshAdapterTests
var adapter = WbMeshAdapter.CreateUninitialized();
// Should not throw, even though there's no underlying mesh manager.
adapter.IncrementRefCount(0x01000001ul);
}
[Fact]
public void DecrementRefCount_OnUninitializedAdapter_NoOps()
{
var adapter = WbMeshAdapter.CreateUninitialized();
adapter.DecrementRefCount(0x01000001ul);
}
[Fact]
public void GetRenderData_OnUninitializedAdapter_ReturnsNull()
{
var adapter = WbMeshAdapter.CreateUninitialized();
Assert.Null(adapter.GetRenderData(0x01000001ul));
}
}