feat(render): Phase A8 Wave 3 — wire EnvCellRenderer into landblock streaming
Six surgical edits to GameWindow.cs (+1 MeshManager accessor on WbMeshAdapter): 1. Field declarations (line 166-167): _envCellRenderer + _envCellFrustum. 2. Ctor init (line 1775-1778): construct WbFrustum + EnvCellRenderer, Initialize with the existing _meshShader (loaded from mesh_modern.vert/frag). 3. BuildInteriorEntitiesForStreaming (line 5444): _envCellRenderer.RegisterCell(...) replaces the cell-as-WorldEntity creation block. staticObjects is empty — cell stabs continue as WorldEntity records via the dispatcher's IndoorPass. 4. ApplyLoadedTerrainLocked (line 5885): _envCellRenderer.FinalizeLandblock(...) immediately after _buildingRegistries[lb.LandblockId] = ... — atomically commits the landblock's per-cell instance store. 5. RemoveLandblock callbacks (lines 1861 + 8955): mirror the existing _buildingRegistries.Remove(id) sites so EnvCellRenderer's storage clears in lockstep. 6. Dispose (line 10595): _envCellRenderer?.Dispose() after _wbDrawDispatcher. Plan revision (vs original plan.md Task 6): we keep the static-object stab WorldEntity hydration (lines 5440-5489) instead of deleting it — stabs need WorldEntity records for interaction (clicking) and physics. EnvCellRenderer receives empty staticObjects so it only renders cell geometry; stab rendering continues unchanged through the dispatcher. Build green. 23/23 EnvCellRenderer + WbFrustum + EnvCellSceneryInstance tests pass. App.Tests baseline holds (82/82). Pre-existing Core.Tests static-leak flakiness (8-19 failures, documented baseline) unrelated. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
aad9ed4cdb
commit
4b4f687070
2 changed files with 52 additions and 14 deletions
|
|
@ -158,6 +158,14 @@ public sealed class GameWindow : IDisposable
|
|||
private readonly System.Collections.Generic.Dictionary<uint, AcDream.App.Rendering.Wb.BuildingRegistry>
|
||||
_buildingRegistries = new();
|
||||
|
||||
// Phase A8 (2026-05-28): WB EnvCellRenderManager port. Cells render
|
||||
// through this dedicated pipeline now, NOT through WbDrawDispatcher.
|
||||
// The dispatcher's IndoorPass still walks cell-static stabs (WorldEntity
|
||||
// records with real GfxObj MeshRefs); only the cell GEOMETRY (walls /
|
||||
// floors / ceilings) flows through here.
|
||||
private AcDream.App.Rendering.Wb.EnvCellRenderer? _envCellRenderer;
|
||||
private AcDream.App.Rendering.Wb.WbFrustum? _envCellFrustum;
|
||||
|
||||
/// <summary>
|
||||
/// Phase 6.4: per-entity animation playback state for entities whose
|
||||
/// MotionTable resolved to a real cycle. The render loop ticks each
|
||||
|
|
@ -1760,6 +1768,14 @@ public sealed class GameWindow : IDisposable
|
|||
_classificationCache);
|
||||
// A.5 T22.5: apply A2C gate from quality preset.
|
||||
_wbDrawDispatcher.AlphaToCoverage = _resolvedQuality.AlphaToCoverage;
|
||||
|
||||
// Phase A8: EnvCellRenderer init. Shares _meshShader (mesh_modern.{vert,frag})
|
||||
// with the dispatcher — both consume the same global mesh buffer (VAO/IBO)
|
||||
// from ObjectMeshManager.GlobalBuffer.
|
||||
_envCellFrustum = new AcDream.App.Rendering.Wb.WbFrustum();
|
||||
_envCellRenderer = new AcDream.App.Rendering.Wb.EnvCellRenderer(
|
||||
_gl, _wbMeshAdapter!.MeshManager!, _envCellFrustum);
|
||||
_envCellRenderer.Initialize(_meshShader!);
|
||||
}
|
||||
|
||||
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
|
||||
|
|
@ -1842,6 +1858,7 @@ public sealed class GameWindow : IDisposable
|
|||
_physicsEngine.RemoveLandblock(id);
|
||||
_cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu);
|
||||
_buildingRegistries.Remove(id); // Phase A8
|
||||
_envCellRenderer?.RemoveLandblock(id); // Phase A8
|
||||
});
|
||||
// A.5 T22.5: apply max-completions from resolved quality.
|
||||
_streamingController.MaxCompletionsPerFrame = _resolvedQuality.MaxCompletionsPerFrame;
|
||||
|
|
@ -5398,6 +5415,13 @@ public sealed class GameWindow : IDisposable
|
|||
if (environment is not null
|
||||
&& environment.Cells.TryGetValue(envCell.CellStructure, out cellStruct))
|
||||
{
|
||||
// Phase A8 (2026-05-28): cells render through EnvCellRenderer, NOT as
|
||||
// WorldEntities with fake MeshRefs. The CellMesh.Build call stays for
|
||||
// _pendingCellMeshes (still populated for any consumer) and the physics
|
||||
// data cache (CacheCellStruct uses cellStruct directly, not the sub-meshes).
|
||||
// Static objects inside the cell continue to flow through the dispatcher
|
||||
// as WorldEntity records below — they have real GfxObj MeshRefs that work
|
||||
// fine; EnvCellRenderer.RegisterCell receives an empty staticObjects list.
|
||||
var cellSubMeshes = AcDream.Core.Meshing.CellMesh.Build(envCell, cellStruct, _dats);
|
||||
if (cellSubMeshes.Count > 0)
|
||||
{
|
||||
|
|
@ -5414,23 +5438,23 @@ public sealed class GameWindow : IDisposable
|
|||
System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
||||
System.Numerics.Matrix4x4.CreateTranslation(physicsCellOrigin);
|
||||
|
||||
var cellMeshRef = new AcDream.Core.World.MeshRef(envCellId, cellTransform);
|
||||
// Phase A8: register the cell with EnvCellRenderer for rendering.
|
||||
// staticObjects is empty — cell stabs continue as separate WorldEntity
|
||||
// records via the dispatcher (see lines below for the unchanged stab path).
|
||||
_envCellRenderer?.RegisterCell(
|
||||
landblockId: landblockId,
|
||||
envCellId: envCellId,
|
||||
envCell: envCell,
|
||||
cellStruct: cellStruct,
|
||||
cellTransform: cellTransform,
|
||||
cellWorldPosition: cellOrigin,
|
||||
cellRotation: envCell.Position.Orientation,
|
||||
staticObjects: System.Array.Empty<(uint, System.Numerics.Vector3, System.Numerics.Quaternion, bool, System.Numerics.Matrix4x4)>());
|
||||
|
||||
var cellEntity = new AcDream.Core.World.WorldEntity
|
||||
{
|
||||
Id = interiorIdBase + localCounter++,
|
||||
SourceGfxObjOrSetupId = envCellId,
|
||||
Position = System.Numerics.Vector3.Zero,
|
||||
Rotation = System.Numerics.Quaternion.Identity,
|
||||
MeshRefs = new[] { cellMeshRef },
|
||||
ParentCellId = envCellId,
|
||||
};
|
||||
result.Add(cellEntity);
|
||||
|
||||
// Step 4: build LoadedCell for portal visibility.
|
||||
// Step 4: build LoadedCell for portal visibility (UNCHANGED from pre-A8).
|
||||
BuildLoadedCell(envCellId, envCell, cellStruct, cellOrigin, cellTransform);
|
||||
|
||||
// Cache CellStruct physics BSP for indoor collision.
|
||||
// Cache CellStruct physics BSP for indoor collision (UNCHANGED).
|
||||
_physicsDataCache.CacheCellStruct(envCellId, envCell, cellStruct, physicsCellTransform);
|
||||
}
|
||||
}
|
||||
|
|
@ -5854,6 +5878,11 @@ public sealed class GameWindow : IDisposable
|
|||
AcDream.App.Rendering.Wb.BuildingLoader.Build(
|
||||
lbInfo, lb.LandblockId, drainedCells);
|
||||
}
|
||||
|
||||
// Phase A8: finalize EnvCellRenderer's per-landblock instance store.
|
||||
// Atomically swaps PendingInstances -> committed, computes TotalEnvCellBounds,
|
||||
// populates StaticPartGroups/BuildingPartGroups via PopulateRecursive.
|
||||
_envCellRenderer?.FinalizeLandblock(lb.LandblockId);
|
||||
}
|
||||
|
||||
// N.5: WbMeshAdapter.Tick() handles GPU upload for all GfxObj meshes via
|
||||
|
|
@ -8923,6 +8952,7 @@ public sealed class GameWindow : IDisposable
|
|||
_physicsEngine.RemoveLandblock(id);
|
||||
_cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu);
|
||||
_buildingRegistries.Remove(id); // Phase A8
|
||||
_envCellRenderer?.RemoveLandblock(id); // Phase A8
|
||||
});
|
||||
_streamingController.MaxCompletionsPerFrame = newResolved.MaxCompletionsPerFrame;
|
||||
|
||||
|
|
@ -10562,6 +10592,7 @@ public sealed class GameWindow : IDisposable
|
|||
_liveSession = null;
|
||||
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
|
||||
_wbDrawDispatcher?.Dispose();
|
||||
_envCellRenderer?.Dispose(); // Phase A8
|
||||
_skyRenderer?.Dispose(); // depends on sampler cache; dispose first
|
||||
_samplerCache?.Dispose();
|
||||
_textureCache?.Dispose();
|
||||
|
|
|
|||
|
|
@ -120,6 +120,13 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
|||
/// </summary>
|
||||
public AcSurfaceMetadataTable MetadataTable => _metadataTable;
|
||||
|
||||
/// <summary>
|
||||
/// Phase A8 (2026-05-28): exposes the underlying <see cref="ObjectMeshManager"/>
|
||||
/// so <c>EnvCellRenderer</c> can share the same global mesh buffer (VAO/VBO/IBO).
|
||||
/// Returns null when the adapter is uninitialized.
|
||||
/// </summary>
|
||||
public ObjectMeshManager? MeshManager => _meshManager;
|
||||
|
||||
/// <summary>
|
||||
/// Returns the WB render data for <paramref name="id"/>, or null if not
|
||||
/// yet uploaded or if this adapter is uninitialized. Increments WB's
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue