feat(render): Phase U.4 — unified gated draw pass (indoor root)

Wire the portal-visibility result through the clip pipeline: build a per-frame
ClipFrame (slot 0 no-clip, slot 1 OutsideView, slot 2..N per visible cell) +
cellIdToSlot from PortalVisibilityBuilder; call the (previously dormant)
EnvCellRenderer.Render for cell shells inside the clip bracket; assign per-instance
clip slots in WbDrawDispatcher (live-dynamic unclipped per retail, cell statics to
their cell slot, outdoor scenery to OutsideView, non-visible culled); gate/scissor/
skip terrain per OutsideView (empty ⇒ no terrain — the bleed fix). Emit ACDREAM_PROBE_VIS.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Erik 2026-05-30 17:59:21 +02:00
parent 864fc5f94e
commit 7993e064a0
6 changed files with 748 additions and 67 deletions

View file

@ -114,6 +114,30 @@ public sealed class ClipFrame : IDisposable
/// the reserved no-clip slot).</summary>
public int SlotCount => _slotCount;
/// <summary>
/// Phase U.4: reset this frame back to the NoClip state — exactly slot 0
/// (no-clip, count 0) and a terrain count of 0 — WITHOUT allocating a new
/// frame or new GL buffers. The single long-lived <c>_clipFrame</c> in
/// GameWindow is reset + re-packed every frame by <see cref="ClipFrameAssembler"/>,
/// then re-uploaded via <see cref="UploadShared"/> (which reuses the same SSBO /
/// UBO ids). This keeps the per-frame cost at one BufferData per buffer instead
/// of leaking a fresh pair of GL buffers each frame.
/// </summary>
public void Reset()
{
// Slot 0 = no-clip (count 0). Zero just the slot-0 region; the tail beyond
// _slotCount is never uploaded, so it needn't be cleared. AppendSlot writes
// each new slot's count + planes in full, so stale bytes there are
// overwritten before they can be uploaded.
if (_regionBytes.Length < CellClipStrideBytes)
EnsureRegionCapacity(CellClipStrideBytes);
Array.Clear(_regionBytes, 0, CellClipStrideBytes);
_slotCount = 1;
// Terrain back to count 0 (ungated) until SetTerrainClip is called again.
Array.Clear(_terrainBytes);
}
/// <summary>The shared mesh-clip SSBO id, or 0 before the first
/// <see cref="UploadShared"/>. Renderers may bind this directly if they don't
/// receive it via a parameter; <see cref="UploadShared"/> already binds it to