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>
|
private readonly System.Collections.Generic.Dictionary<uint, AcDream.App.Rendering.Wb.BuildingRegistry>
|
||||||
_buildingRegistries = new();
|
_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>
|
/// <summary>
|
||||||
/// Phase 6.4: per-entity animation playback state for entities whose
|
/// Phase 6.4: per-entity animation playback state for entities whose
|
||||||
/// MotionTable resolved to a real cycle. The render loop ticks each
|
/// MotionTable resolved to a real cycle. The render loop ticks each
|
||||||
|
|
@ -1760,6 +1768,14 @@ public sealed class GameWindow : IDisposable
|
||||||
_classificationCache);
|
_classificationCache);
|
||||||
// A.5 T22.5: apply A2C gate from quality preset.
|
// A.5 T22.5: apply A2C gate from quality preset.
|
||||||
_wbDrawDispatcher.AlphaToCoverage = _resolvedQuality.AlphaToCoverage;
|
_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)
|
// Phase G.1 sky renderer — its own shader (sky.vert / sky.frag)
|
||||||
|
|
@ -1842,6 +1858,7 @@ public sealed class GameWindow : IDisposable
|
||||||
_physicsEngine.RemoveLandblock(id);
|
_physicsEngine.RemoveLandblock(id);
|
||||||
_cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu);
|
_cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu);
|
||||||
_buildingRegistries.Remove(id); // Phase A8
|
_buildingRegistries.Remove(id); // Phase A8
|
||||||
|
_envCellRenderer?.RemoveLandblock(id); // Phase A8
|
||||||
});
|
});
|
||||||
// A.5 T22.5: apply max-completions from resolved quality.
|
// A.5 T22.5: apply max-completions from resolved quality.
|
||||||
_streamingController.MaxCompletionsPerFrame = _resolvedQuality.MaxCompletionsPerFrame;
|
_streamingController.MaxCompletionsPerFrame = _resolvedQuality.MaxCompletionsPerFrame;
|
||||||
|
|
@ -5398,6 +5415,13 @@ public sealed class GameWindow : IDisposable
|
||||||
if (environment is not null
|
if (environment is not null
|
||||||
&& environment.Cells.TryGetValue(envCell.CellStructure, out cellStruct))
|
&& 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);
|
var cellSubMeshes = AcDream.Core.Meshing.CellMesh.Build(envCell, cellStruct, _dats);
|
||||||
if (cellSubMeshes.Count > 0)
|
if (cellSubMeshes.Count > 0)
|
||||||
{
|
{
|
||||||
|
|
@ -5414,23 +5438,23 @@ public sealed class GameWindow : IDisposable
|
||||||
System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
System.Numerics.Matrix4x4.CreateFromQuaternion(envCell.Position.Orientation) *
|
||||||
System.Numerics.Matrix4x4.CreateTranslation(physicsCellOrigin);
|
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
|
// Step 4: build LoadedCell for portal visibility (UNCHANGED from pre-A8).
|
||||||
{
|
|
||||||
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.
|
|
||||||
BuildLoadedCell(envCellId, envCell, cellStruct, cellOrigin, cellTransform);
|
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);
|
_physicsDataCache.CacheCellStruct(envCellId, envCell, cellStruct, physicsCellTransform);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -5854,6 +5878,11 @@ public sealed class GameWindow : IDisposable
|
||||||
AcDream.App.Rendering.Wb.BuildingLoader.Build(
|
AcDream.App.Rendering.Wb.BuildingLoader.Build(
|
||||||
lbInfo, lb.LandblockId, drainedCells);
|
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
|
// N.5: WbMeshAdapter.Tick() handles GPU upload for all GfxObj meshes via
|
||||||
|
|
@ -8923,6 +8952,7 @@ public sealed class GameWindow : IDisposable
|
||||||
_physicsEngine.RemoveLandblock(id);
|
_physicsEngine.RemoveLandblock(id);
|
||||||
_cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu);
|
_cellVisibility.RemoveLandblock((id >> 16) & 0xFFFFu);
|
||||||
_buildingRegistries.Remove(id); // Phase A8
|
_buildingRegistries.Remove(id); // Phase A8
|
||||||
|
_envCellRenderer?.RemoveLandblock(id); // Phase A8
|
||||||
});
|
});
|
||||||
_streamingController.MaxCompletionsPerFrame = newResolved.MaxCompletionsPerFrame;
|
_streamingController.MaxCompletionsPerFrame = newResolved.MaxCompletionsPerFrame;
|
||||||
|
|
||||||
|
|
@ -10562,6 +10592,7 @@ public sealed class GameWindow : IDisposable
|
||||||
_liveSession = null;
|
_liveSession = null;
|
||||||
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
|
_audioEngine?.Dispose(); // Phase E.2: stop all voices, close AL context
|
||||||
_wbDrawDispatcher?.Dispose();
|
_wbDrawDispatcher?.Dispose();
|
||||||
|
_envCellRenderer?.Dispose(); // Phase A8
|
||||||
_skyRenderer?.Dispose(); // depends on sampler cache; dispose first
|
_skyRenderer?.Dispose(); // depends on sampler cache; dispose first
|
||||||
_samplerCache?.Dispose();
|
_samplerCache?.Dispose();
|
||||||
_textureCache?.Dispose();
|
_textureCache?.Dispose();
|
||||||
|
|
|
||||||
|
|
@ -120,6 +120,13 @@ public sealed class WbMeshAdapter : IDisposable, IWbMeshAdapter
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public AcSurfaceMetadataTable MetadataTable => _metadataTable;
|
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>
|
/// <summary>
|
||||||
/// Returns the WB render data for <paramref name="id"/>, or null if not
|
/// Returns the WB render data for <paramref name="id"/>, or null if not
|
||||||
/// yet uploaded or if this adapter is uninitialized. Increments WB's
|
/// yet uploaded or if this adapter is uninitialized. Increments WB's
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue