phase(N.4) Tasks 22+23: WbDrawDispatcher + surface metadata side-table

WbDrawDispatcher draws all entities through WB's ObjectRenderData
(VAO/VBO per GfxObj, per-batch IBO) using acdream's TextureCache for
texture resolution. Two-pass rendering (opaque+ClipMap, then
translucent) matching the existing InstancedMeshRenderer pattern.
Per-entity single-instance drawing for N.4 simplicity — true
instancing grouping deferred to N.6.

Atlas-tier entities: mesh from WB, texture from TextureCache via
batch SurfaceId. Per-instance-tier entities: AnimatedEntityState
drives part overrides + hidden-parts, palette/surface overrides
resolve through TextureCache's composite-key caches.

Side-table population (Task 23 folded in): WbMeshAdapter now takes
DatCollection and populates AcSurfaceMetadataTable on first
IncrementRefCount per GfxObj. The side-table provides TranslucencyKind
(critical for ClipMap alpha-test on vegetation) plus Luminosity,
Diffuse, SurfOpacity, NeedsUvRepeat, DisableFog for sky-pass and
lighting.

GameWindow wiring: when WbFoundationFlag is enabled, WbDrawDispatcher
draws everything and InstancedMeshRenderer is skipped. Flag-off path
is unchanged.

Matrix composition: restPose * animOverride * entityWorld, matching
the spec. Three MatrixCompositionTests verify the contract.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-08 15:30:33 +02:00
parent 5b4fd4b61d
commit 01cff4144f
5 changed files with 507 additions and 8 deletions

View file

@ -31,6 +31,8 @@ public sealed class GameWindow : IDisposable
/// <summary>Phase N.4: WB-backed rendering pipeline adapter. Non-null only
/// when <c>ACDREAM_USE_WB_FOUNDATION=1</c> is set; null otherwise.</summary>
private AcDream.App.Rendering.Wb.WbMeshAdapter? _wbMeshAdapter;
private AcDream.App.Rendering.Wb.EntitySpawnAdapter? _wbEntitySpawnAdapter;
private AcDream.App.Rendering.Wb.WbDrawDispatcher? _wbDrawDispatcher;
private SamplerCache? _samplerCache;
private DebugLineRenderer? _debugLines;
// K-fix4 (2026-04-26): default OFF. The orange BSP / green cylinder
@ -1434,7 +1436,7 @@ public sealed class GameWindow : IDisposable
if (AcDream.App.Rendering.Wb.WbFoundationFlag.IsEnabled)
{
var wbLogger = Microsoft.Extensions.Logging.Abstractions.NullLogger<AcDream.App.Rendering.Wb.WbMeshAdapter>.Instance;
_wbMeshAdapter = new AcDream.App.Rendering.Wb.WbMeshAdapter(_gl, _datDir, wbLogger);
_wbMeshAdapter = new AcDream.App.Rendering.Wb.WbMeshAdapter(_gl, _datDir, _dats, wbLogger);
Console.WriteLine("[N.4] WbFoundation flag is ENABLED — routing static content through ObjectMeshManager.");
}
@ -1486,12 +1488,20 @@ public sealed class GameWindow : IDisposable
}
wbEntitySpawnAdapter = new AcDream.App.Rendering.Wb.EntitySpawnAdapter(
_textureCache, SequencerFactory);
_wbEntitySpawnAdapter = wbEntitySpawnAdapter;
}
_worldState = new AcDream.App.Streaming.GpuWorldState(wbSpawnAdapter, wbEntitySpawnAdapter);
}
_staticMesh = new InstancedMeshRenderer(_gl, _meshShader, _textureCache, _wbMeshAdapter);
if (AcDream.App.Rendering.Wb.WbFoundationFlag.IsEnabled
&& _wbMeshAdapter is not null && _wbEntitySpawnAdapter is not null)
{
_wbDrawDispatcher = new AcDream.App.Rendering.Wb.WbDrawDispatcher(
_gl, _meshShader, _textureCache, _wbMeshAdapter, _wbEntitySpawnAdapter);
}
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
// with depth writes off + far plane 1e6 so celestial meshes
// never clip. Shares the TextureCache with the static pipeline.
@ -6326,10 +6336,20 @@ public sealed class GameWindow : IDisposable
animatedIds.Add(k);
}
_staticMesh?.Draw(camera, _worldState.LandblockEntries, frustum,
neverCullLandblockId: playerLb,
visibleCellIds: visibility?.VisibleCellIds,
animatedEntityIds: animatedIds);
if (_wbDrawDispatcher is not null)
{
_wbDrawDispatcher.Draw(camera, _worldState.LandblockEntries, frustum,
neverCullLandblockId: playerLb,
visibleCellIds: visibility?.VisibleCellIds,
animatedEntityIds: animatedIds);
}
else
{
_staticMesh?.Draw(camera, _worldState.LandblockEntries, frustum,
neverCullLandblockId: playerLb,
visibleCellIds: visibility?.VisibleCellIds,
animatedEntityIds: animatedIds);
}
// Phase G.1 / E.3: draw all live particles after opaque
// scene geometry so alpha blending composites correctly.
@ -8710,6 +8730,7 @@ public sealed class GameWindow : IDisposable
_combatChatTranslator?.Dispose();
_liveSession?.Dispose();
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
_wbDrawDispatcher?.Dispose();
_staticMesh?.Dispose();
_skyRenderer?.Dispose(); // depends on sampler cache; dispose first
_samplerCache?.Dispose();