// 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: // // outside_view landscape slices // reverse cell_draw_list exit masks // reverse cell_draw_list EnvCell shells // reverse cell_draw_list object lists // // Slot 0 is always no-clip. A slice whose polygon cannot be represented by the // <=8 plane budget uses slot 0 and its NDC AABB; the renderer uses scissor for // passes that need that fallback. Empty regions are omitted entirely. using System.Collections.Generic; using System.Numerics; namespace AcDream.App.Rendering; /// /// How the landscape-through-outside_view pass should be interpreted. /// public enum TerrainClipMode { /// All outside_view slices have convex plane clips. Planes, /// At least one outside_view slice requires scissor fallback. Scissor, /// No outside_view slice is visible; skip landscape indoors. Skip, } /// /// One retail portal_view slice mapped to a GPU clip slot. The AABB is retained /// for passes that cannot write gl_ClipDistance and must use scissor. /// public readonly record struct ClipViewSlice(int Slot, Vector4 NdcAabb, Vector4[] Planes); /// /// Result of : populated clip buffers /// plus routing data consumed by the render orchestration. /// public sealed class ClipFrameAssembly { public required ClipFrame Frame { get; init; } /// First drawable slice slot per visible cell. Compatibility map /// for renderer APIs that can accept only one slot at a time. public required Dictionary CellIdToSlot { get; init; } /// Slot-only cell slices, retained for older renderer APIs. public required Dictionary CellIdToViewSlots { get; init; } /// Full retail portal_view slices per visible cell. public required Dictionary CellIdToViewSlices { get; init; } /// Full retail outside_view slices. public required ClipViewSlice[] OutsideViewSlices { get; init; } public required int OutdoorSlot { get; init; } public required bool OutdoorVisible { get; init; } public required TerrainClipMode TerrainMode { get; init; } public required Vector4 TerrainScissorNdcAabb { get; init; } public required bool HasOutsideView { get; init; } public required Vector4 OutsideViewNdcAabb { get; init; } // Probe data. public required int OutsidePlaneCount { get; init; } public required Dictionary PerCellPlaneCounts { get; init; } public required int ScissorFallbacks { get; init; } } public static class ClipFrameAssembler { public static ClipFrameAssembly Assemble(ClipFrame frame, PortalVisibilityFrame pvFrame) { System.ArgumentNullException.ThrowIfNull(frame); System.ArgumentNullException.ThrowIfNull(pvFrame); frame.Reset(); var cellIdToSlot = new Dictionary(); var cellIdToViewSlots = new Dictionary(); var cellIdToViewSlices = new Dictionary(); var perCellPlaneCounts = new Dictionary(); int scissorFallbacks = 0; foreach (uint cellId in pvFrame.OrderedVisibleCells) { if (!pvFrame.CellViews.TryGetValue(cellId, out var view)) continue; var slices = new List(view.Polygons.Count); int maxPlaneCount = 0; foreach (var poly in view.Polygons) { var cps = ClipPlaneSet.From(ViewOf(poly)); if (cps.IsNothingVisible) continue; int slot; Vector4[] planes; if (cps.Count > 0) { planes = ToPlaneSpan(cps); slot = frame.AppendSlot(planes); if (cps.Count > maxPlaneCount) maxPlaneCount = cps.Count; } else { planes = System.Array.Empty(); slot = 0; scissorFallbacks++; } slices.Add(new ClipViewSlice(slot, AabbOf(poly), planes)); } if (slices.Count == 0) continue; var sliceArray = slices.ToArray(); cellIdToViewSlices[cellId] = sliceArray; cellIdToViewSlots[cellId] = ToSlots(sliceArray); cellIdToSlot[cellId] = sliceArray[0].Slot; perCellPlaneCounts[cellId] = maxPlaneCount; } var outsideSlicesList = new List(pvFrame.OutsideView.Polygons.Count); int outsideMaxPlaneCount = 0; bool outsideHasScissorFallback = false; foreach (var poly in pvFrame.OutsideView.Polygons) { var cps = ClipPlaneSet.From(ViewOf(poly)); if (cps.IsNothingVisible) continue; int slot; Vector4[] planes; if (cps.Count > 0) { planes = ToPlaneSpan(cps); slot = frame.AppendSlot(planes); if (cps.Count > outsideMaxPlaneCount) outsideMaxPlaneCount = cps.Count; } else { planes = System.Array.Empty(); slot = 0; outsideHasScissorFallback = true; scissorFallbacks++; } outsideSlicesList.Add(new ClipViewSlice(slot, AabbOf(poly), planes)); } var outsideViewSlices = outsideSlicesList.ToArray(); bool outdoorVisible = outsideViewSlices.Length > 0; int outdoorSlot = outdoorVisible ? outsideViewSlices[0].Slot : 0; TerrainClipMode terrainMode = !outdoorVisible ? TerrainClipMode.Skip : (outsideHasScissorFallback ? TerrainClipMode.Scissor : TerrainClipMode.Planes); Vector4 outsideViewNdcAabb = outdoorVisible ? new Vector4(pvFrame.OutsideView.MinX, pvFrame.OutsideView.MinY, pvFrame.OutsideView.MaxX, pvFrame.OutsideView.MaxY) : Vector4.Zero; Vector4 terrainScissor = terrainMode == TerrainClipMode.Scissor ? outsideViewNdcAabb : Vector4.Zero; return new ClipFrameAssembly { Frame = frame, CellIdToSlot = cellIdToSlot, CellIdToViewSlots = cellIdToViewSlots, CellIdToViewSlices = cellIdToViewSlices, OutsideViewSlices = outsideViewSlices, OutdoorSlot = outdoorSlot, OutdoorVisible = outdoorVisible, TerrainMode = terrainMode, TerrainScissorNdcAabb = terrainScissor, HasOutsideView = outdoorVisible, OutsideViewNdcAabb = outsideViewNdcAabb, OutsidePlaneCount = terrainMode == TerrainClipMode.Planes ? outsideMaxPlaneCount : 0, PerCellPlaneCounts = perCellPlaneCounts, ScissorFallbacks = scissorFallbacks, }; } private static CellView ViewOf(ViewPolygon poly) { var view = new CellView(); view.Add(poly); return view; } private static Vector4 AabbOf(ViewPolygon poly) => new(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY); private static int[] ToSlots(ClipViewSlice[] slices) { var slots = new int[slices.Length]; for (int i = 0; i < slices.Length; i++) slots[i] = slices[i].Slot; return slots; } private static Vector4[] ToPlaneSpan(ClipPlaneSet set) { int n = set.Count; var planes = new Vector4[n]; for (int i = 0; i < n; i++) planes[i] = set.Planes[i]; return planes; } }