refactor(render): remove the production portal frame carrier

This commit is contained in:
Erik 2026-08-31 05:34:46 +02:00
parent 69e69408e8
commit cb22691beb
10 changed files with 128 additions and 443 deletions

View file

@ -1,9 +1,9 @@
// ClipFrameAssembler.cs
//
// Retail PView assembly policy. PortalVisibilityBuilder produces a retail-like
// view graph: one portal_view list per visible cell plus an outside_view list.
// This assembler packs each visible polygon as an individual GPU clip slot so
// the renderer can draw the exact PView order:
// Retail view assembly policy. The production frame walk supplies its
// outside_view directly; the legacy PortalVisibilityBuilder overload remains
// only for isolated research/replay tests. Each visible polygon is packed as
// an individual GPU clip slot.
//
// outside_view landscape slices
// reverse cell_draw_list exit masks
@ -40,15 +40,7 @@ public enum TerrainClipMode
public readonly record struct ClipViewSlice(int Slot, Vector4 NdcAabb, Vector4[] Planes);
/// <summary>
/// Identifies one cell inside one nested building look-in. The same EnvCell can
/// be reached by more than one building PView, so a cell id alone is not a
/// sufficient routing key.
/// </summary>
public readonly record struct LookInClipCell(int FrameIndex, uint CellId);
/// <summary>
/// Result of <see cref="ClipFrameAssembler.Assemble"/>: populated clip buffers
/// plus routing data consumed by the render orchestration.
/// Populated clip buffers plus routing data consumed by the render walk.
/// </summary>
public sealed class ClipFrameAssembly
{
@ -64,12 +56,6 @@ public sealed class ClipFrameAssembly
/// <summary>Full retail portal_view slices per visible cell.</summary>
public Dictionary<uint, ClipViewSlice[]> CellIdToViewSlices { get; } = new();
/// <summary>First drawable slice slot per nested look-in cell.</summary>
public Dictionary<LookInClipCell, int> LookInCellToSlot { get; } = new();
/// <summary>All retail portal_view slices per nested look-in cell.</summary>
public Dictionary<LookInClipCell, ClipViewSlice[]> LookInCellToViewSlices { get; } = new();
/// <summary>Full retail outside_view slices.</summary>
public ClipViewSlice[] OutsideViewSlices { get; private set; } = System.Array.Empty<ClipViewSlice>();
@ -106,8 +92,6 @@ public sealed class ClipFrameAssembly
Frame = frame;
foreach (ClipViewSlice[] slices in CellIdToViewSlices.Values)
ReturnSlices(slices);
foreach (ClipViewSlice[] slices in LookInCellToViewSlices.Values)
ReturnSlices(slices);
foreach (int[] slots in CellIdToViewSlots.Values)
ReturnSlots(slots);
if (OutsideViewSlices.Length != 0)
@ -116,8 +100,6 @@ public sealed class ClipFrameAssembly
CellIdToSlot.Clear();
CellIdToViewSlots.Clear();
CellIdToViewSlices.Clear();
LookInCellToSlot.Clear();
LookInCellToViewSlices.Clear();
PerCellPlaneCounts.Clear();
OutsideViewSlices = System.Array.Empty<ClipViewSlice>();
SliceScratch.Clear();
@ -272,6 +254,53 @@ public sealed class ClipFrameAssembly
public static class ClipFrameAssembler
{
/// <summary>
/// Starts one production walk assembly without constructing a parallel
/// PortalVisibilityFrame. Outdoor roots begin with retail's full-screen
/// default view; interior roots begin empty and are populated from
/// <see cref="Walk.RetailFrameWalk.InteriorOutsideView"/> after Collect.
/// </summary>
public static ClipFrameAssembly BeginWalkFrame(
ClipFrame frame,
bool outdoorRoot,
ClipFrameAssembly? reuseAssembly = null)
{
System.ArgumentNullException.ThrowIfNull(frame);
frame.Reset();
ClipFrameAssembly assembly = reuseAssembly ?? new ClipFrameAssembly();
assembly.Reset(frame);
if (outdoorRoot)
{
List<ClipViewSlice> slices = assembly.SliceScratch;
slices.Clear();
var fullScreen = new Vector4(-1f, -1f, 1f, 1f);
slices.Add(new ClipViewSlice(0, fullScreen, System.Array.Empty<Vector4>()));
assembly.SetOutsideViewSlices(assembly.CopySlices(slices));
assembly.OutdoorSlot = 0;
assembly.OutdoorVisible = true;
assembly.TerrainMode = TerrainClipMode.Scissor;
assembly.TerrainScissorNdcAabb = fullScreen;
assembly.HasOutsideView = true;
assembly.OutsideViewNdcAabb = fullScreen;
assembly.OutsidePlaneCount = 0;
assembly.ScissorFallbacks = 1;
}
else
{
assembly.OutdoorSlot = 0;
assembly.OutdoorVisible = false;
assembly.TerrainMode = TerrainClipMode.Skip;
assembly.TerrainScissorNdcAabb = Vector4.Zero;
assembly.HasOutsideView = false;
assembly.OutsideViewNdcAabb = Vector4.Zero;
assembly.OutsidePlaneCount = 0;
assembly.ScissorFallbacks = 0;
}
return assembly;
}
public static ClipFrameAssembly Assemble(
ClipFrame frame,
PortalVisibilityFrame pvFrame,
@ -529,70 +558,6 @@ public static class ClipFrameAssembler
assembly.ScissorFallbacks = scissorFallbacks;
}
/// <summary>
/// Appends the cell views used by nested <c>DrawBuilding -&gt; DrawPortal</c>
/// PViews to the already assembled frame. Retail installs each nested
/// cell's own <c>portal_view</c> before drawing its shell and object list;
/// these slots must therefore be published with the main frame before the
/// first draw is recorded.
/// </summary>
public static void AppendLookInFrames(
ClipFrame frame,
IReadOnlyList<PortalVisibilityFrame> lookInFrames,
ClipFrameAssembly assembly)
{
System.ArgumentNullException.ThrowIfNull(frame);
System.ArgumentNullException.ThrowIfNull(lookInFrames);
System.ArgumentNullException.ThrowIfNull(assembly);
if (!ReferenceEquals(frame, assembly.Frame))
throw new System.ArgumentException(
"The look-in slots must be appended to the assembly's clip frame.",
nameof(frame));
for (int frameIndex = 0; frameIndex < lookInFrames.Count; frameIndex++)
{
PortalVisibilityFrame lookIn = lookInFrames[frameIndex];
foreach (uint cellId in lookIn.OrderedVisibleCells)
{
if (!lookIn.CellViews.TryGetValue(cellId, out CellView? view))
continue;
List<ClipViewSlice> slices = assembly.SliceScratch;
slices.Clear();
foreach (ViewPolygon poly in view.Polygons)
{
ClipPlaneSet cps = ClipPlaneSet.From(poly);
if (cps.IsNothingVisible)
continue;
int slot;
Vector4[] planes;
if (cps.Count > 0)
{
planes = cps.PlaneArray;
slot = frame.AppendSlot(planes);
}
else
{
planes = System.Array.Empty<Vector4>();
slot = 0;
assembly.ScissorFallbacks++;
}
slices.Add(new ClipViewSlice(slot, AabbOf(poly), planes));
}
if (slices.Count == 0)
continue;
ClipViewSlice[] packed = assembly.CopySlices(slices);
var key = new LookInClipCell(frameIndex, cellId);
assembly.LookInCellToViewSlices.Add(key, packed);
assembly.LookInCellToSlot.Add(key, packed[0].Slot);
}
}
}
private static Vector4 AabbOf(ViewPolygon poly) =>
new(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY);