diff --git a/src/AcDream.App/Rendering/GameWindow.cs b/src/AcDream.App/Rendering/GameWindow.cs index c4fb7ce..15b76bb 100644 --- a/src/AcDream.App/Rendering/GameWindow.cs +++ b/src/AcDream.App/Rendering/GameWindow.cs @@ -11069,6 +11069,9 @@ public sealed class GameWindow : IDisposable currentEnvCellIds.Add(id); gl.Disable(EnableCap.Blend); _envCellRenderer!.Render(AcDream.App.Rendering.Wb.WbRenderPass.Opaque, currentEnvCellIds); + // TEMP A8.F triage (strip after): opaque wall-render stats BEFORE the transparent loop overwrites them. + if (AcDream.Core.Rendering.RenderingDiagnostics.ProbeEnvCellEnabled && _a8OpaqueDumped.Add(cameraCell.CellId)) + Console.WriteLine($"[opaque] camCell=0x{cameraCell.CellId:X8} cells={_envCellRenderer.Stats.CellsRendered} tris={_envCellRenderer.Stats.TrianglesDrawn} filterCnt={currentEnvCellIds.Count}"); // Phase A8.F (#2): translucent cell geometry clipped to each cell's portal-chain // region (stencil BIT 2). Opaque cells already clip correctly via depth (left @@ -11378,6 +11381,9 @@ public sealed class GameWindow : IDisposable private readonly HashSet<(uint cellId, ulong gfxObjId)> _phaseA8AuditLogged = new(); + // TEMP A8.F triage (strip after): one-shot-per-camCell guard for the [opaque] wall-render probe. + private static readonly System.Collections.Generic.HashSet _a8OpaqueDumped = new(); + private void EmitEnvCellProbe(int ourBldgs, int otherBldgs, int filterCnt) { if (!AcDream.Core.Rendering.RenderingDiagnostics.ProbeEnvCellEnabled) return; diff --git a/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs b/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs index fbc68a7..fdd0f59 100644 --- a/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs +++ b/src/AcDream.App/Rendering/PortalVisibilityBuilder.cs @@ -41,6 +41,12 @@ public static class PortalVisibilityBuilder private const int MaxReprocessPerCell = 4; private const float PortalSideEpsilon = 0.01f; // matches CellVisibility.PointInCellEpsilon + // TEMP diagnostic (Phase A8.F visual-gate triage; strip after): ACDREAM_A8_DUMP_PV=1 dumps the + // local→NDC→clipped portal geometry for the first 2 Build calls per distinct camera cell. + private static readonly bool s_pvDump = + Environment.GetEnvironmentVariable("ACDREAM_A8_DUMP_PV") == "1"; + private static readonly Dictionary s_pvDumpCount = new(); + /// Resolve a full cell id to its LoadedCell, or null if not loaded. /// Optional: true if a cell id is in the camera building's cell /// set. When provided, a neighbour OUTSIDE the set routes to CrossBuildingViews instead of @@ -64,6 +70,18 @@ public static class PortalVisibilityBuilder var queue = new Queue(); queue.Enqueue(cameraCell); + bool pvDump = false; + if (s_pvDump) + { + lock (s_pvDumpCount) + { + s_pvDumpCount.TryGetValue(cameraCell.CellId, out int dc); + if (dc < 2) { s_pvDumpCount[cameraCell.CellId] = dc + 1; pvDump = true; } + } + if (pvDump) + Console.WriteLine($"[pv-dump] camCell=0x{cameraCell.CellId:X8} portals={cameraCell.Portals.Count} polyLists={cameraCell.PortalPolygons.Count} vp[M11={viewProj.M11:F3} M22={viewProj.M22:F3} M33={viewProj.M33:F3} M34={viewProj.M34:F3} M43={viewProj.M43:F3} M44={viewProj.M44:F3}]"); + } + while (queue.Count > 0) { var cell = queue.Dequeue(); @@ -80,15 +98,21 @@ public static class PortalVisibilityBuilder var poly = cell.PortalPolygons[i]; if (poly == null || poly.Length < 3) continue; + bool dx = pvDump && cell.Portals[i].OtherCellId == 0xFFFF; + // Portal-side test: only traverse a portal the camera is on the interior side of // (mirrors CellVisibility.GetVisibleCells + retail's 'seen' flag). Culls back-facing // portals so we never feed a degenerate/wrong-facing projection downstream. if (i < cell.ClipPlanes.Count && !CameraOnInteriorSide(cell, i, cameraPos)) + { + if (dx) Console.WriteLine($"[pv-dump] EXIT-CULLED(side) cell=0x{cell.CellId:X8} p{i} localN={poly.Length} hasClipPlane={(i < cell.ClipPlanes.Count)}"); continue; + } // Project to NDC, then normalize to CCW for the CCW-only ScreenPolygonClip // (ProjectToNdc preserves input winding; portal dat polygons may be CW). Vector2[] portalNdc = PortalProjection.ProjectToNdc(poly, cell.WorldTransform, viewProj); + if (dx) Console.WriteLine($"[pv-dump] EXIT-PROJ cell=0x{cell.CellId:X8} p{i} localN={poly.Length} ndcN={portalNdc.Length} local0=({poly[0].X:F2},{poly[0].Y:F2},{poly[0].Z:F2}) ndc=[{string.Join(" ", System.Array.ConvertAll(portalNdc, v => $"({v.X:F2},{v.Y:F2})"))}]"); if (portalNdc.Length < 3) continue; EnsureCcw(portalNdc); @@ -99,12 +123,21 @@ public static class PortalVisibilityBuilder var clipped = ScreenPolygonClip.Intersect(portalNdc, vp.Vertices); if (clipped.Length >= 3) clippedRegion.Add(new ViewPolygon(clipped)); } + if (dx) Console.WriteLine($"[pv-dump] EXIT-CLIP cell=0x{cell.CellId:X8} p{i} currentViewPolys={currentView.Polygons.Count} clipResult={clippedRegion.Count}"); if (clippedRegion.Count == 0) continue; // portal not visible through this chain var portal = cell.Portals[i]; if (portal.OtherCellId == 0xFFFF) { + if (pvDump) + { + Console.WriteLine($"[pv-dump] EXIT cell=0x{cell.CellId:X8} p{i} localN={poly.Length} ndcN={portalNdc.Length} clipPolys={clippedRegion.Count}"); + Console.WriteLine($"[pv-dump] local=[{string.Join(" ", System.Array.ConvertAll(poly, v => $"({v.X:F2},{v.Y:F2},{v.Z:F2})"))}]"); + Console.WriteLine($"[pv-dump] ndc=[{string.Join(" ", System.Array.ConvertAll(portalNdc, v => $"({v.X:F3},{v.Y:F3})"))}]"); + foreach (var cp in clippedRegion) + Console.WriteLine($"[pv-dump] clipped({cp.Vertices.Length})=[{string.Join(" ", System.Array.ConvertAll((Vector2[])cp.Vertices, v => $"({v.X:F3},{v.Y:F3})"))}]"); + } // Exit portal -> outdoors visible through this (clipped) opening. foreach (var cp in clippedRegion) frame.OutsideView.Add(cp); continue; @@ -135,6 +168,9 @@ public static class PortalVisibilityBuilder } } + if (pvDump) + Console.WriteLine($"[pv-dump] OUTSIDEVIEW polys={frame.OutsideView.Polygons.Count} bfsCellViews={frame.CellViews.Count} crossBldg={frame.CrossBuildingViews.Count}"); + return frame; }