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

@ -19,9 +19,11 @@ namespace AcDream.App.Tests.Rendering.Walk;
/// <summary>
/// Campaign FW stage FW2: <see cref="WbDrawDispatcher.BuildOrderedMergeRuns"/>
/// (pure CPU merge-run legality) and <see cref="WbDrawDispatcher.SubmitOrderedStream"/>
/// (the same legality proven through actual recorded RHI calls against
/// <see cref="RecordingGpuDevice"/>).
/// (pure CPU merge-run legality) and, from Campaign FW stage FW3.4a,
/// <see cref="WbDrawDispatcher.PrepareOrderedStream"/> +
/// <see cref="WbDrawDispatcher.DrawOrderedRange"/> (the same legality proven
/// through actual recorded RHI calls against <see cref="RecordingGpuDevice"/>,
/// replacing the single <c>SubmitOrderedStream</c> call those two now split).
/// </summary>
public sealed class OrderPreservingSubmitterTests
{
@ -197,10 +199,24 @@ public sealed class OrderPreservingSubmitterTests
Assert.Equal(stream.Count, totalCommands);
}
// ── SubmitOrderedStream — recorded RHI calls against RecordingGpuDevice ─
// ── PrepareOrderedStream + DrawOrderedRange — recorded RHI calls against
// RecordingGpuDevice. Campaign FW3.4a replaced the single
// SubmitOrderedStream call with this pair (prepare the whole stream once,
// draw it via one or more ranges) — every test below that used to call
// SubmitOrderedStream now calls Prepare once and Draw the WHOLE stream as
// ONE range, which is exactly SubmitOrderedStream's old behavior; the
// "several ranges" and "bind once" shapes get their own tests further
// down since they have no FW2 analogue. ────────────────────────────────
private static void PrepareAndDrawWhole(WbDrawDispatcher dispatcher, DrawScope draw, OrderedDrawStream stream)
{
dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
if (stream.Count > 0)
dispatcher.DrawOrderedRange(draw.Pass, 0, stream.Count);
}
[Fact]
public void SubmitOrderedStream_AlternatingStateCommandsRecordOneDrawEachInOrder()
public void PrepareThenDraw_AlternatingStateCommandsRecordOneDrawEachInOrder()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -211,14 +227,14 @@ public sealed class OrderPreservingSubmitterTests
MakeCommand(2, translucency: TranslucencyKind.Opaque),
MakeCommand(3, translucency: TranslucencyKind.AlphaBlend));
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
Assert.Equal([(0, 1), (1, 1), (2, 1), (3, 1)], ranges);
}
[Fact]
public void SubmitOrderedStream_MergesAdjacentSameStateCommandsIntoOneMultiDrawIndirect()
public void PrepareThenDraw_MergesAdjacentSameStateCommandsIntoOneMultiDrawIndirect()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -226,14 +242,14 @@ public sealed class OrderPreservingSubmitterTests
OrderedDrawStream stream = StreamOf(
MakeCommand(0), MakeCommand(1), MakeCommand(2));
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
Assert.Equal([(0, 3)], ranges);
}
[Fact]
public void SubmitOrderedStream_CullModeChangeRecordsSeparateCullCallsAndSplitsTheDraw()
public void PrepareThenDraw_CullModeChangeRecordsSeparateCullCallsAndSplitsTheDraw()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -243,7 +259,7 @@ public sealed class OrderPreservingSubmitterTests
MakeCommand(1, cullMode: CullMode.None),
MakeCommand(2, cullMode: CullMode.Clockwise));
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
Assert.Equal([(0, 2), (2, 1)], DecodeDrawRanges(fx.Device));
@ -254,7 +270,7 @@ public sealed class OrderPreservingSubmitterTests
}
[Fact]
public void SubmitOrderedStream_StageChangeSplitsTheDrawEvenWithIdenticalMaterialState()
public void PrepareThenDraw_StageChangeSplitsTheDrawEvenWithIdenticalMaterialState()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -263,13 +279,13 @@ public sealed class OrderPreservingSubmitterTests
MakeCommand(0, stage: WalkDrawStage.Terrain),
MakeCommand(1, stage: WalkDrawStage.CellStatic));
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
}
[Fact]
public void SubmitOrderedStream_ADetailCategoryCommandRecordsItsOwnSoloDraw()
public void PrepareThenDraw_ADetailCategoryCommandRecordsItsOwnSoloDraw()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -279,13 +295,13 @@ public sealed class OrderPreservingSubmitterTests
MakeCommand(1, detailCategory: 1),
MakeCommand(2));
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
Assert.Equal([(0, 1), (1, 1), (2, 1)], DecodeDrawRanges(fx.Device));
}
[Fact]
public void SubmitOrderedStream_OpaqueRunUsesRenderPassZeroAndAlphaBlendRunUsesRenderPassOne()
public void PrepareThenDraw_OpaqueRunUsesRenderPassZeroAndAlphaBlendRunUsesRenderPassOne()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -294,7 +310,7 @@ public sealed class OrderPreservingSubmitterTests
MakeCommand(0, translucency: TranslucencyKind.Opaque),
MakeCommand(1, translucency: TranslucencyKind.AlphaBlend));
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
List<(GpuPushConstants Constants, int Start, int Count)> runs = DecodeRuns(fx.Device);
Assert.Equal(2, runs.Count);
@ -303,7 +319,7 @@ public sealed class OrderPreservingSubmitterTests
}
[Fact]
public void SubmitOrderedStream_ThrowsNotSupportedForAPortalPunchCommandBeforeAnyDraw()
public void PrepareOrderedStream_ThrowsNotSupportedForAPortalPunchCommandBeforeAnyDraw()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -312,32 +328,165 @@ public sealed class OrderPreservingSubmitterTests
MakeCommand(0, stage: WalkDrawStage.PortalPunch));
Assert.Throws<NotSupportedException>(
() => fx.Dispatcher.SubmitOrderedStream(
draw.Frame, draw.Pass, stream, Matrix4x4.Identity));
() => fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity));
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedStorageBind>());
}
[Fact]
public void SubmitOrderedStream_EmptyStreamRecordsNoDraws()
public void PrepareOrderedStream_EmptyStreamRecordsNoDraws()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
fx.Dispatcher.SubmitOrderedStream(
draw.Frame, draw.Pass, new OrderedDrawStream(), Matrix4x4.Identity);
fx.Dispatcher.PrepareOrderedStream(draw.Frame, new OrderedDrawStream(), Matrix4x4.Identity);
Assert.Empty(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
}
// ── Campaign FW3.4a — the shapes with no FW2 analogue: drawing the SAME
// prepared stream as several ranges, the bind-once optimization, the
// fail-loud range check, and the assert-don't-slice straddle guard. ───
/// <summary>
/// The whole point of the split: drawing the SAME stream as TWO ranges
/// (with the boundary between them supplied to Prepare, exactly as
/// WalkFrameDriver.Replay supplies its recorded mark positions) produces
/// the identical total recorded draw/cull/push-constant calls as drawing
/// it as one range — the range split changes nothing about what reaches
/// the GPU, only how many DrawOrderedRange calls got there.
/// </summary>
[Fact]
public void DrawOrderedRange_AsTwoRangesAtASegmentBoundary_MatchesOneRangeOverTheWholeStream()
{
OrderedDrawStream stream = StreamOf(
MakeCommand(0, translucency: TranslucencyKind.Opaque),
MakeCommand(1, translucency: TranslucencyKind.Opaque),
MakeCommand(2, translucency: TranslucencyKind.Opaque),
MakeCommand(3, translucency: TranslucencyKind.Opaque));
using var wholeFx = new DispatcherFixture();
using (DrawScope draw = wholeFx.BeginDraw())
{
wholeFx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
wholeFx.Dispatcher.DrawOrderedRange(draw.Pass, 0, stream.Count);
}
List<(int Start, int Count)> wholeRanges = DecodeDrawRanges(wholeFx.Device);
using var splitFx = new DispatcherFixture();
using (DrawScope draw = splitFx.BeginDraw())
{
// Command 2 is a segment boundary (mirrors a mark WalkFrameDriver
// would record there, e.g. a cell shell between two same-state
// segments) — without it, all four commands would merge into ONE
// run; the boundary forces two.
splitFx.Dispatcher.PrepareOrderedStream(
draw.Frame, stream, Matrix4x4.Identity, forcedBreaksAscending: [2]);
splitFx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 2);
splitFx.Dispatcher.DrawOrderedRange(draw.Pass, 2, 2);
}
List<(int Start, int Count)> splitRanges = DecodeDrawRanges(splitFx.Device);
// The split path draws two runs where the whole-range path drew one
// (the forced boundary is the only difference) — but every command
// reaches the GPU exactly once, in order, with identical coverage.
Assert.Equal([(0, 4)], wholeRanges);
Assert.Equal([(0, 2), (2, 2)], splitRanges);
}
/// <summary>
/// The FW3.4a perf shape itself: the nine per-instance storage binds plus
/// the warm-up pipeline bind happen on the FIRST DrawOrderedRange call in
/// a frame only — a second call over the same prepared payload issues no
/// further StorageBind calls, which is the whole reason this stage exists
/// (the old SubmitOrderedStream rebound everything on every call).
/// </summary>
[Fact]
public void DrawOrderedRange_SecondCallInTheSameFrame_BindsNoFurtherStorageSections()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
OrderedDrawStream stream = StreamOf(
MakeCommand(0, stage: WalkDrawStage.Terrain),
MakeCommand(1, stage: WalkDrawStage.CellStatic));
fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 1);
int boundAfterFirst = fx.Device.Calls.OfType<GpuRecordedStorageBind>().Count();
Assert.True(boundAfterFirst > 0);
fx.Dispatcher.DrawOrderedRange(draw.Pass, 1, 1);
int boundAfterSecond = fx.Device.Calls.OfType<GpuRecordedStorageBind>().Count();
Assert.Equal(boundAfterFirst, boundAfterSecond);
// Both commands still drew — the bind-once optimization changed
// nothing about draw coverage.
Assert.Equal([(0, 1), (1, 1)], DecodeDrawRanges(fx.Device));
}
/// <summary>
/// Fail-loud range check (mirrors DrawPreparedAlphaBatchRhi's): a range
/// outside what PrepareOrderedStream uploaded throws rather than drawing
/// garbage or silently clamping — including a draw attempted before ANY
/// Prepare call this frame.
/// </summary>
[Fact]
public void DrawOrderedRange_RangeExceedingThePreparedPayload_Throws()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
OrderedDrawStream stream = StreamOf(MakeCommand(0));
fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
Assert.Throws<ArgumentOutOfRangeException>(
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 2));
Assert.Throws<ArgumentOutOfRangeException>(
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 1, 1));
}
[Fact]
public void DrawOrderedRange_BeforeAnyPrepareCallThisFrame_Throws()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
Assert.Throws<ArgumentOutOfRangeException>(
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 1));
}
/// <summary>
/// The plan's "assert it" rule: a range that does not align with a merge
/// run boundary throws rather than silently slicing the run — proven
/// directly here (skipping the boundary a real WalkFrameDriver mark would
/// supply) since production code always supplies the boundary and would
/// never exercise this path.
/// </summary>
[Fact]
public void DrawOrderedRange_RangeStraddlingAMergeRun_Throws()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
// All four commands share stage/bucket/cull — ONE merge run [0, 4) —
// and no forced break is supplied, so a [0, 2) range straddles it.
OrderedDrawStream stream = StreamOf(
MakeCommand(0), MakeCommand(1), MakeCommand(2), MakeCommand(3));
fx.Dispatcher.PrepareOrderedStream(draw.Frame, stream, Matrix4x4.Identity);
Assert.Throws<InvalidOperationException>(
() => fx.Dispatcher.DrawOrderedRange(draw.Pass, 0, 2));
}
/// <summary>
/// Fail-loud invariant: whatever the state pattern, the recorded
/// MultiDrawIndirect calls' DrawCounts always sum to the stream's Count —
/// no command is ever silently skipped, and none is drawn twice.
/// </summary>
[Fact]
public void SubmitOrderedStream_TotalRecordedDrawCountAlwaysEqualsTheStreamCount()
public void PrepareThenDraw_TotalRecordedDrawCountAlwaysEqualsTheStreamCount()
{
using var fx = new DispatcherFixture();
using DrawScope draw = fx.BeginDraw();
@ -361,7 +510,7 @@ public sealed class OrderPreservingSubmitterTests
detailCategory: i == 5 ? 1u : 0u));
}
fx.Dispatcher.SubmitOrderedStream(draw.Frame, draw.Pass, stream, Matrix4x4.Identity);
PrepareAndDrawWhole(fx.Dispatcher, draw, stream);
List<(int Start, int Count)> ranges = DecodeDrawRanges(fx.Device);
int sum = ranges.Sum(r => r.Count);