perf(render) Campaign FW3.4a: one walk pass; prepare-once/draw-ranges; arena records

The FW3.4 dense-Arwic pair triggered the +/-20% stop rule (+33.5% CPU
p50, 14x frame allocation). This slice removes the three measured
costs without changing GPU command order (the referee suites assert
identical recorded call sequences):

- WalkFrameDriver: Collect (ONE walk per frame - no GPU work; leaf
  calls and flush points become a recorded event list; the driver
  absorbed the renderer collection pass and exposes the visited sets)
  + Replay (prepare the whole stream once, then replay events,
  interleaving DrawOrderedRange with leaf calls in the exact recorded
  order). RunFrame = Collect+Replay for existing callers.
- WbDrawDispatcher: SubmitOrderedStream split into PrepareOrderedStream
  (all sections + commands + merge runs uploaded once per frame) and
  DrawOrderedRange (bind-once latch; per-run pipeline + DrawIdOffset +
  DrawIndirectRangeRhi). Load-bearing correctness catch from the
  implementation round: merge runs take FORCED BREAKS at the recorded
  event marks - whole-stream merging must not fuse two segments that
  retail separates with a leaf GPU call (shell, punch); the straddle
  assert stays as a dead-code safety net.
- WalkProductionWorldData: WalkFrameStaticRecords carries an
  ArraySegment into a per-frame grow-only arena; the per-cell
  fresh-array copies (the 1.9 MB/frame alloc p50) are gone - zero
  steady-state allocation after warmup.

Suites (lead-verified): full Release build 0 warnings; hermetic
6,758/0; Walk lane 209/1; InstalledDat Walk conformance 40/1
untouched. Next: the dense-Arwic re-measure against the same-session
baseline.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Erik 2026-08-30 17:01:26 +02:00
parent 6301e4dbea
commit 212f5a12e5
7 changed files with 1051 additions and 359 deletions

View file

@ -227,16 +227,22 @@ public sealed class RetailPViewRenderer
&& _sceneFrameProduct is not null
&& walkRegistriesReady;
// Campaign FW3.2b-2 pre-walk collection pass: run the production walk
// EVENTS-ONLY (no leaf draws) to learn the flood cell set it will draw
// this frame, BEFORE prepareCells is finalized below — so EnvCellRenderer
// prepares batches for every shell the driver will draw later in this
// same DrawInside call. The same WalkProductionFrameContext + camera
// cell resolved here are reused for the real driven run further down
// (plan §FW3 "FW3.2b-2 — the production rooting", item 1).
Walk.WalkProductionFrameContext? walkContext = null;
Walk.WalkLandscape? walkLandscape = null;
Walk.WalkCell? walkCameraCell = null;
// Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
// walkCameraCell exactly as the pre-FW3.4a pre-walk collection pass
// did, then drives WalkFrameDriver.Collect — a SINGLE RetailFrameWalk
// pass that both learns the flood/visited-cell set (needed below,
// BEFORE prepareCells is finalized, so EnvCellRenderer prepares
// batches for every shell the driver will draw later in this same
// DrawInside call) and records the walk's draw events for Replay
// further down, in DrawWalkDrivenStatics. The former SECOND walk pass
// (a dedicated set-collecting sink, run again through this same
// driver machinery just to submit) is gone — see WalkFrameDriver's
// own doc comment for the FW3.4 perf numbers that motivated this.
// _walkWorldData.BeginFrame precedes Collect deliberately: Collect's
// stream appends classify records immediately (WalkStaticStreamPopulator
// runs at append time, not at Replay time), so the world data must
// already be rebuilt for this frame before the walk starts.
Walk.WalkFrameDriver? walkDriver = null;
if (walkActive)
{
Matrix4x4 view = ctx.CameraView;
@ -247,7 +253,7 @@ public sealed class RetailPViewRenderer
// reached before the world pass has published its scope.
float viewportWidth = attachment?.Width ?? 1024f;
float viewportHeight = attachment?.Height ?? 720f;
walkContext = new Walk.WalkProductionFrameContext(
var walkContext = new Walk.WalkProductionFrameContext(
_walkCellRegistry!,
_walkBuildings!,
ctx.ViewerEyePos,
@ -256,8 +262,9 @@ public sealed class RetailPViewRenderer
viewportWidth,
viewportHeight);
_walkLandscape!.SetViewer(ctx.ViewerCellId, ctx.ViewerEyePos);
walkLandscape = _walkLandscape.Landscape;
Walk.WalkLandscape walkLandscape = _walkLandscape.Landscape;
Walk.WalkCell? walkCameraCell = null;
if ((ctx.ViewerCellId & 0xFFFFu) >= 0x100)
{
walkCameraCell = _walkCellRegistry!.TryGetCell(ctx.ViewerCellId, out LoadedCell? loaded)
@ -274,9 +281,42 @@ public sealed class RetailPViewRenderer
}
}
_walkVisitedScratch.Reset();
_frameWalk.WalkFrame(
ctx.ViewerCellId, walkCameraCell, walkLandscape, walkContext, _walkVisitedScratch);
int activeTerrainSliceCount = clipAssembly.OutsideViewSlices.Length;
if (ctx.RootCell.IsOutdoorNode && activeTerrainSliceCount != 1)
{
throw new InvalidOperationException(
"walk static cutover: an outdoor root's clip assembly produced "
+ $"{activeTerrainSliceCount} outside-view slices, not the expected 1 — "
+ "WalkFrameDriver's landscape turn assumes the outdoor root's default "
+ "full-screen view (plan §FW3 item 2c's pinned assumption; assert "
+ "rather than silently coercing to 1).");
}
_walkWorldData!.BeginFrame(
_sceneFrameProduct!.SceneQuery,
ctx.PlayerLandblockId ?? 0u,
ctx.RenderCenterLbX,
ctx.RenderCenterLbY);
Action clearInteriorDepth = () =>
{
// Retail PView::DrawCells 0x005A4872 drains the landscape
// alpha list immediately before the gated full depth clear —
// mirrors DrawLandscapeThroughOutsideView's own pre-clear
// drain.
passes.FlushLandscapeAlpha();
passes.ClearInteriorDepth();
};
Action drawExitSeals = () =>
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
var leafRenderer = new WalkProductionLeafRenderer(
walkExecutor!, ctx, clipAssembly, clearInteriorDepth, drawExitSeals);
walkDriver = new Walk.WalkFrameDriver(walkExecutor!.Dispatcher, leafRenderer, _walkWorldData);
walkDriver.Collect(
_frameWalk, ctx.ViewerCellId, walkCameraCell, walkLandscape, walkContext,
ctx.ViewProjection, ctx.CameraWorldPosition, activeTerrainSliceCount);
}
// #124: look-in cells need prepared shell batches + their statics routed
@ -303,7 +343,7 @@ public sealed class RetailPViewRenderer
// 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(_walkVisitedScratch.Cells);
_lookInPrepareScratch.UnionWith(walkDriver!.VisitedCells);
}
prepareCells = _lookInPrepareScratch;
}
@ -438,16 +478,9 @@ public sealed class RetailPViewRenderer
// retail turn). The OLD visibility (pvFrame/clipAssembly/
// viewcone, already built above) keeps running unchanged to
// feed the surviving dynamic routes only (plan §FW3 item 1's
// dual-compute split).
DrawWalkDrivenStatics(
ctx,
walkExecutor!,
clipAssembly,
pvFrame,
drawableCells,
walkContext!,
walkCameraCell,
walkLandscape!);
// dual-compute split). Campaign FW3.4a: the walk itself
// already ran (Collect, above) — this is Replay only.
DrawWalkDrivenStatics(ctx, walkExecutor!, walkDriver!);
passes.UseIndoorMembershipOnlyRouting();
DrawLandscapeDynamicsPhase(
ctx,
@ -1089,24 +1122,26 @@ public sealed class RetailPViewRenderer
}
// Campaign FW3.2b-2: the one RetailFrameWalk instance shared by the
// diagnostic shadow probe, the production pre-walk collection pass, and
// the real driven run — WalkFrame calls are never concurrent/re-entrant
// within a single-threaded render loop, so one shared instance is safe
// and avoids re-allocating the walk's own PView scratch per call site.
// diagnostic shadow probe and the real Collect run (WalkFrameDriver.Collect,
// driven from DrawInside's walkActive block) — WalkFrame calls are never
// concurrent/re-entrant within a single-threaded render loop, so one
// shared instance is safe and avoids re-allocating the walk's own PView
// scratch per call site. Campaign FW3.4a retired the THIRD role this
// field used to serve (a dedicated pre-walk collection pass) — Collect
// now gathers the same visited sets itself, on WalkFrameDriver, as a
// side effect of the one walk it already runs.
private readonly Walk.RetailFrameWalk _frameWalk = new();
// Campaign FW3.2b-2: the pre-walk collection pass's reusable sink (see
// DrawInside's walkActive block) — reset and re-driven once per frame.
private readonly WalkVisitedSetCollector _walkVisitedScratch = new();
/// <summary>Campaign FW3.2b-2 (the I5 dual-shadow pattern, extended for
/// production use): an events-only <see cref="Walk.IWalkEventSink"/> that
/// collects the SETS a driven run would touch, without doing any leaf
/// drawing itself — the shadow probe's original role, now ALSO the
/// pre-walk collection pass's role (plan §FW3 item 1: the walk's flood
/// cell set for the <c>prepareCells</c> union, the visited building list
/// and landscape-cell turn ids for re-sourcing particle owners once the
/// walk owns the static routes those owners used to ride).</summary>
/// <summary>Campaign FW3.2b-2 (the I5 dual-shadow pattern): an
/// events-only <see cref="Walk.IWalkEventSink"/> that collects the SETS a
/// driven run would touch, without doing any leaf drawing itself. Used
/// ONLY by <see cref="RunWalkShadowProbe"/> now — Campaign FW3.4a moved
/// the production role (the walk's flood cell set for the
/// <c>prepareCells</c> union, the visited building list and
/// landscape-cell turn ids for particle re-sourcing) onto
/// <see cref="Walk.WalkFrameDriver"/> itself, which gathers the same sets
/// as a side effect of the one walk <see cref="Walk.WalkFrameDriver.Collect"/>
/// already runs, instead of a second dedicated pass.</summary>
private sealed class WalkVisitedSetCollector : Walk.IWalkEventSink
{
public readonly HashSet<uint> Cells = new();
@ -1142,78 +1177,27 @@ public sealed class RetailPViewRenderer
public void OnBuildingTurn(Walk.WalkBuilding building) => Buildings.Add(building);
}
/// <summary>Campaign FW3.2b-2 — THE PRODUCTION ROOTING. Runs the real
/// <see cref="Walk.RetailFrameWalk"/> through
/// <see cref="Walk.WalkFrameDriver"/> over
/// <see cref="WbDrawDispatcher.SubmitOrderedStream"/>: terrain/sky, every
/// building's shell + punch + look-in cell statics, and the interior
/// root's own flood shells + statics (with retail's depth-clear/exit-seal
/// turn, via <see cref="DrawExitPortalMasks"/> bound as the driver's own
/// seal action) all draw here, in walk order — replacing
/// <summary>Campaign FW3.2b-2 — THE PRODUCTION ROOTING; Campaign FW3.4a —
/// REPLAY ONLY. <paramref name="driver"/> already ran its Collect pass
/// earlier in <see cref="DrawInside"/> (before <c>PrepareCellBatches</c>);
/// this method's job is now just <see cref="Walk.WalkFrameDriver.Replay"/>
/// (terrain/sky, every building's shell + punch + look-in cell statics,
/// and the interior root's own flood shells + statics, with retail's
/// depth-clear/exit-seal turn — all in walk order, replacing
/// <see cref="DrawLandscapeThroughOutsideView"/>'s static half,
/// <see cref="DrawExitPortalMasks"/>'s old top-level call,
/// <see cref="DrawEnvCellShells"/>, and <see cref="DrawCellObjectLists"/>'s
/// static half for this frame (plan §FW3 "FW3.2b-2 — the production
/// rooting", item 2). Also re-sources the particle owners the routes it
/// just replaced used to ride (item 4).</summary>
/// static half for this frame) plus re-sourcing the particle owners the
/// routes it replaced used to ride, from the driver's own visited
/// sets (plan §FW3 "FW3.2b-2 — the production rooting", items 2 and
/// 4).</summary>
private void DrawWalkDrivenStatics(
RetailPViewFrameInput ctx,
RetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
PortalVisibilityFrame pvFrame,
HashSet<uint> drawableCells,
Walk.WalkProductionFrameContext walkContext,
Walk.WalkCell? cameraCell,
Walk.WalkLandscape landscape)
Walk.WalkFrameDriver driver)
{
_walkWorldData!.BeginFrame(
_sceneFrameProduct!.SceneQuery,
ctx.PlayerLandblockId ?? 0u,
ctx.RenderCenterLbX,
ctx.RenderCenterLbY);
Action clearInteriorDepth = () =>
{
// Retail PView::DrawCells 0x005A4872 drains the landscape alpha
// list immediately before the gated full depth clear — mirrors
// DrawLandscapeThroughOutsideView's own pre-clear drain (this
// action only ever fires for an INTERIOR root; see
// IWalkFrameLeafRenderer.ClearInteriorDepth's own doc comment for
// why the driver never invokes it outdoors).
passes.FlushLandscapeAlpha();
passes.ClearInteriorDepth();
};
Action drawExitSeals = () =>
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
var leafRenderer = new WalkProductionLeafRenderer(
passes, ctx, clipAssembly, clearInteriorDepth, drawExitSeals);
var driver = new Walk.WalkFrameDriver(passes.Dispatcher, leafRenderer, _walkWorldData);
var (frame, encoder) = passes.RequireWalkSubmission();
int activeTerrainSliceCount = clipAssembly.OutsideViewSlices.Length;
if (ctx.RootCell.IsOutdoorNode && activeTerrainSliceCount != 1)
{
throw new InvalidOperationException(
"walk static cutover: an outdoor root's clip assembly produced "
+ $"{activeTerrainSliceCount} outside-view slices, not the expected 1 — "
+ "WalkFrameDriver's landscape turn assumes the outdoor root's default "
+ "full-screen view (plan §FW3 item 2c's pinned assumption; assert "
+ "rather than silently coercing to 1).");
}
driver.RunFrame(
_frameWalk,
ctx.ViewerCellId,
cameraCell,
landscape,
walkContext,
frame,
encoder,
ctx.ViewProjection,
ctx.CameraWorldPosition,
activeTerrainSliceCount);
driver.Replay(frame, encoder);
// Landscape-stage particle owners: the union of every outdoor-static
// record from a landscape cell the walk visited this frame, plus
@ -1222,10 +1206,10 @@ public sealed class RetailPViewRenderer
// @0x0050FE60, so this is MORE retail-faithful than the old per-
// slice sphere filter it replaces).
_staticParticleUnionScratch.Clear();
foreach (uint cellId in _walkVisitedScratch.LandscapeCellIds)
UnionRecordOwners(_walkWorldData.GetOutdoorStatics(cellId), _staticParticleUnionScratch);
foreach (Walk.WalkBuilding building in _walkVisitedScratch.Buildings)
UnionRecordOwners(_walkWorldData.GetBuildingShellStatics(building), _staticParticleUnionScratch);
foreach (uint cellId in driver.VisitedLandscapeCellIds)
UnionRecordOwners(_walkWorldData!.GetOutdoorStatics(cellId), _staticParticleUnionScratch);
foreach (Walk.WalkBuilding building in driver.VisitedBuildings)
UnionRecordOwners(_walkWorldData!.GetBuildingShellStatics(building), _staticParticleUnionScratch);
if (_staticParticleUnionScratch.Count > 0)
{
passes.DrawLandscapeStaticParticles(
@ -1242,11 +1226,11 @@ public sealed class RetailPViewRenderer
// DrawBuildingLookInDynamics so a static owner is never submitted
// twice.
_cellParticleOwnerScratch.Clear();
foreach (uint cellId in _walkVisitedScratch.Cells)
foreach (uint cellId in driver.VisitedCells)
{
if (_lookInCellIds.Contains(cellId))
continue;
UnionRecordOwners(_walkWorldData.GetCellStatics(cellId), _cellParticleOwnerScratch);
UnionRecordOwners(_walkWorldData!.GetCellStatics(cellId), _cellParticleOwnerScratch);
}
if (_cellParticleOwnerScratch.Count > 0)
{