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:
parent
6301e4dbea
commit
212f5a12e5
7 changed files with 1051 additions and 359 deletions
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue