refactor(render): publish the walk visible-cell set

This commit is contained in:
Erik 2026-08-31 04:15:55 +02:00
parent c9fb7e7d4c
commit 877e935ac4
4 changed files with 29 additions and 46 deletions

View file

@ -442,14 +442,18 @@ public sealed class RetailPViewRenderer
int terrainUploadCount = checked(1 + clipAssembly.OutsideViewSlices.Length * 2); int terrainUploadCount = checked(1 + clipAssembly.OutsideViewSlices.Length * 2);
passes.PrepareClipFrame(terrainUploadCount); passes.PrepareClipFrame(terrainUploadCount);
// #124: look-in cells need prepared shell batches + their statics routed // Production prepares exactly the walk's visited-cell set. The
// into partition.ByCell (consumed ONLY by DrawBuildingLookIns — the main // legacy builder's main-flood/look-in union survives only for
// cell-object pass iterates pvFrame.OrderedVisibleCells, which never // standalone fake executors until the shell-deletion slice.
// contains them). drawableCells itself stays the MAIN flood: it feeds the
// seals, the outside-stage predicate, and the frame result.
var prepareCells = drawableCells; var prepareCells = drawableCells;
_lookInCellIds.Clear(); _lookInCellIds.Clear();
if (_lookInFrames.Count > 0 || walkActive) if (walkActive)
{
_lookInPrepareScratch.Clear();
_lookInPrepareScratch.UnionWith(walkDriver!.VisitedCells);
prepareCells = _lookInPrepareScratch;
}
else if (_lookInFrames.Count > 0)
{ {
_lookInPrepareScratch.Clear(); _lookInPrepareScratch.Clear();
_lookInPrepareScratch.UnionWith(drawableCells); _lookInPrepareScratch.UnionWith(drawableCells);
@ -461,13 +465,6 @@ public sealed class RetailPViewRenderer
_lookInCellIds.Add(c); _lookInCellIds.Add(c);
} }
} }
if (walkActive)
{
// The walk's own flood/look-in cell set — unioned in (never
// aliased with drawableCells, which the outside-stage and seal
// predicates below still need scoped to the OLD flood only).
_lookInPrepareScratch.UnionWith(walkDriver!.VisitedCells);
}
prepareCells = _lookInPrepareScratch; prepareCells = _lookInPrepareScratch;
} }
@ -2876,20 +2873,11 @@ public sealed class RetailPViewFrameResult
public HashSet<uint> DrawableCells { get; private set; } = null!; public HashSet<uint> DrawableCells { get; private set; } = null!;
/// <summary> /// <summary>
/// Every cell this completed view actually reached: the main flood /// The production retail walk's exact visited-cell set. This is the one
/// (<see cref="DrawableCells"/>) plus the building look-in cells. This is /// visibility answer consumed by EnvCell preparation, particles, lights,
/// retail's per-cell <c>in_view</c> answer for effect consumers — /// and directional-shadow filtering.
/// <c>CPhysicsObj::ShouldDrawParticles</c> @0x0050FE60 gates on
/// <c>cell-&gt;IsInView()</c>, and a cell entered through a building portal
/// (<c>PView::ConstructView</c> @0x005A57B0, installed by
/// <c>RenderDeviceD3D::DrawBuilding</c> @0x0059F2A0) is drawn by the same
/// <c>PView::DrawCells</c> traversal as a flooded cell, so retail marks it
/// in view identically. acdream's look-in adaptation keeps those cells out
/// of <see cref="DrawableCells"/> (seals / outside-stage predicate stay
/// main-flood scoped, #124); particle and light visibility must consume
/// THIS set or look-in rooms render with frozen emitters and dark lights.
/// </summary> /// </summary>
public HashSet<uint> InViewCells { get; private set; } = null!; public HashSet<uint> VisibleCells { get; private set; } = null!;
internal RenderFrameDiagnosticCounts DiagnosticCounts { get; private set; } internal RenderFrameDiagnosticCounts DiagnosticCounts { get; private set; }
internal RenderProjectionCounts SourceCounts { get; private set; } internal RenderProjectionCounts SourceCounts { get; private set; }
@ -2900,7 +2888,7 @@ public sealed class RetailPViewFrameResult
PortalVisibilityFrame portalFrame, PortalVisibilityFrame portalFrame,
ClipFrameAssembly clipAssembly, ClipFrameAssembly clipAssembly,
HashSet<uint> drawableCells, HashSet<uint> drawableCells,
HashSet<uint> inViewCells, HashSet<uint> visibleCells,
RenderFrameDiagnosticCounts diagnosticCounts, RenderFrameDiagnosticCounts diagnosticCounts,
RenderProjectionCounts sourceCounts, RenderProjectionCounts sourceCounts,
InteriorEntityPartition.Result? diagnosticPartition) InteriorEntityPartition.Result? diagnosticPartition)
@ -2908,7 +2896,7 @@ public sealed class RetailPViewFrameResult
PortalFrame = portalFrame; PortalFrame = portalFrame;
ClipAssembly = clipAssembly; ClipAssembly = clipAssembly;
DrawableCells = drawableCells; DrawableCells = drawableCells;
InViewCells = inViewCells; VisibleCells = visibleCells;
DiagnosticCounts = diagnosticCounts; DiagnosticCounts = diagnosticCounts;
SourceCounts = sourceCounts; SourceCounts = sourceCounts;
DiagnosticPartition = diagnosticPartition; DiagnosticPartition = diagnosticPartition;

View file

@ -217,6 +217,8 @@ internal sealed class WorldSceneRenderer : IPreparedWorldSceneFramePhase
// this phase only enforces it. // this phase only enforces it.
_passes.DrawFlatTerrain(in camera, roots.PlayerLandblockId); _passes.DrawFlatTerrain(in camera, roots.PlayerLandblockId);
terrainDrawn = true; terrainDrawn = true;
if (_passes.TerrainVisibleCellIds is { } flatTerrainCells)
_particleVisibility.MarkVisibleCells(flatTerrainCells);
} }
else else
{ {
@ -254,14 +256,11 @@ internal sealed class WorldSceneRenderer : IPreparedWorldSceneFramePhase
camera.Camera.View, camera.Camera.View,
_diagnostics.CameraCellResolution)); _diagnostics.CameraCellResolution));
// Effect visibility consumes InViewCells (main flood look-in // One visibility answer: the walk's exact visited-cell set
// cells), not the flood-only DrawableCells: retail's // feeds effects and the frame environment (point lights and
// ShouldDrawParticles @0x0050FE60 asks cell->IsInView(), and a // directional-shadow filtering).
// look-in cell drawn via DrawBuilding @0x0059F2A0 is in view _particleVisibility.MarkVisibleCells(pviewResult.VisibleCells);
// exactly like a flooded cell. Flood-only scoping froze _frames.ObserveDrawableCells(pviewResult.VisibleCells);
// emitters and darkened lights in visible adjacent rooms.
_particleVisibility.MarkVisibleCells(pviewResult.InViewCells);
_frames.ObserveDrawableCells(pviewResult.InViewCells);
_diagnostics.EmitPViewInput( _diagnostics.EmitPViewInput(
pviewResult.PortalFrame, pviewResult.PortalFrame,
camera.ViewProjection, camera.ViewProjection,
@ -441,8 +440,6 @@ internal sealed class WorldSceneRenderer : IPreparedWorldSceneFramePhase
private void CompleteWorldFrame() private void CompleteWorldFrame()
{ {
_alpha.EndFrame(); _alpha.EndFrame();
if (_passes.TerrainVisibleCellIds is { } visibleTerrainCells)
_particleVisibility.MarkVisibleCells(visibleTerrainCells);
_particleVisibility.CompleteFrame(); _particleVisibility.CompleteFrame();
} }

View file

@ -271,9 +271,8 @@ public sealed class LightManager
/// geometrically closer than the player's own room's torches and win the cap, /// geometrically closer than the player's own room's torches and win the cap,
/// leaving the visible room dark. Scoping candidacy to the frame's actual /// leaving the visible room dark. Scoping candidacy to the frame's actual
/// visible cells (the render already computes this — callers pass last frame's /// visible cells (the render already computes this — callers pass last frame's
/// <c>RetailPViewFrameResult.InViewCells</c>, the main flood PLUS building /// retail-walk visited-cell set, one frame of latency, to avoid re-threading
/// look-in cells, one frame of latency, to avoid re-threading a mid-render /// a mid-render callback) removes
/// callback; flood-only scoping darkened look-in rooms' lanterns) removes
/// non-visible cells from contention before the /// non-visible cells from contention before the
/// cap ever applies. The distance-sort anchor stays the PLAYER either way — this /// cap ever applies. The distance-sort anchor stays the PLAYER either way — this
/// parameter only narrows candidacy, it does not change the sort (the #176 /// parameter only narrows candidacy, it does not change the sort (the #176

View file

@ -180,6 +180,7 @@ public sealed class WorldSceneRendererTests
"flat:clip", "flat:clip",
"flat:sky", "flat:sky",
"flat:terrain", "flat:terrain",
"visibility:mark",
"flat:entities", "flat:entities",
"frame:clear-cells", "frame:clear-cells",
"passes:disable-clip", "passes:disable-clip",
@ -188,7 +189,6 @@ public sealed class WorldSceneRendererTests
"diagnostics:signature:OutdoorRoot", "diagnostics:signature:OutdoorRoot",
"diagnostics:draw", "diagnostics:draw",
"alpha:end", "alpha:end",
"visibility:mark",
"visibility:complete", "visibility:complete",
"selection:complete", "selection:complete",
], ],
@ -206,7 +206,7 @@ public sealed class WorldSceneRendererTests
} }
[Fact] [Fact]
public void PViewWorld_UsesOnePViewProductAndPublishesItsDrawableCells() public void PViewWorld_PublishesTheWalkVisibleCellsOnce()
{ {
var root = new LoadedCell var root = new LoadedCell
{ {
@ -233,7 +233,6 @@ public sealed class WorldSceneRendererTests
"diagnostics:signature:RetailPViewInside", "diagnostics:signature:RetailPViewInside",
"diagnostics:draw", "diagnostics:draw",
"alpha:end", "alpha:end",
"visibility:mark",
"visibility:complete", "visibility:complete",
"selection:complete", "selection:complete",
], ],
@ -267,8 +266,8 @@ public sealed class WorldSceneRendererTests
// @0x0050FE60), and a cell entered through a building portal // @0x0050FE60), and a cell entered through a building portal
// (DrawBuilding @0x0059F2A0 -> PView::ConstructView @0x005A57B0) is // (DrawBuilding @0x0059F2A0 -> PView::ConstructView @0x005A57B0) is
// drawn by the same traversal as a flooded cell. The particle gate and // drawn by the same traversal as a flooded cell. The particle gate and
// the light-candidate scope must therefore receive InViewCells (flood // light-candidate scope must therefore receive the walk's one visited
// plus look-ins), not the flood-only DrawableCells. // set, including look-ins.
Assert.Contains(0x01010003u, rig.Visibility.MarkedCells); Assert.Contains(0x01010003u, rig.Visibility.MarkedCells);
Assert.NotNull(rig.Frames.ObservedCells); Assert.NotNull(rig.Frames.ObservedCells);
Assert.Contains(0x01010003u, rig.Frames.ObservedCells!); Assert.Contains(0x01010003u, rig.Frames.ObservedCells!);