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
|
|
@ -216,9 +216,9 @@ public sealed class WalkFrameDriverTests
|
|||
|
||||
var worldData = new FakeWorldData();
|
||||
worldData.CellStaticsByCell[0x100] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
worldData.CellStaticsByCell[0x101] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
|
||||
var leaf = new RecordingLeafRenderer(log);
|
||||
var trace = new RecordingTrace(log);
|
||||
|
|
@ -294,9 +294,9 @@ public sealed class WalkFrameDriverTests
|
|||
|
||||
var worldData = new FakeWorldData();
|
||||
worldData.CellStaticsByCell[0x100] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(101, 0, Vector3.Zero, [new MeshRef((uint)gfxObjA, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
worldData.CellStaticsByCell[0x101] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(102, 0, Vector3.Zero, [new MeshRef((uint)gfxObjB, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
|
||||
var leaf = new RecordingLeafRenderer(log);
|
||||
var trace = new RecordingTrace(log);
|
||||
|
|
@ -383,9 +383,9 @@ public sealed class WalkFrameDriverTests
|
|||
|
||||
var worldData = new FakeWorldData();
|
||||
worldData.ShellByBuilding[building] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(201, 0, Vector3.Zero, [new MeshRef((uint)shellGfxObj, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(201, 0, Vector3.Zero, [new MeshRef((uint)shellGfxObj, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
worldData.CellStaticsByCell[0x104] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(202, 0, Vector3.Zero, [new MeshRef((uint)interiorGfxObj, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(202, 0, Vector3.Zero, [new MeshRef((uint)interiorGfxObj, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
Matrix4x4 buildingWorld = Matrix4x4.CreateTranslation(10f, 0f, 0f);
|
||||
worldData.WorldTransformByBuilding[building] = buildingWorld;
|
||||
|
||||
|
|
@ -401,9 +401,10 @@ public sealed class WalkFrameDriverTests
|
|||
Assert.Equal(1, activeView.ViewCount);
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.BeginFrame(ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
walk.DrawBuilding(building, activeView, ctx, driver);
|
||||
driver.EndFrame();
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.Equal(
|
||||
new[] { "ALPHA:12.50", "PUNCH:4@v0", "SHELL:00000104", "FLUSH:1:LookInStatic", "FLUSH:1:BuildingShell" },
|
||||
|
|
@ -433,7 +434,7 @@ public sealed class WalkFrameDriverTests
|
|||
var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData());
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.BeginFrame(ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => ((IWalkEventSink)driver).Emit(WalkEvent.DrawCells(0, [0x100u])));
|
||||
|
|
@ -450,18 +451,82 @@ public sealed class WalkFrameDriverTests
|
|||
var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData());
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.BeginFrame(ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(
|
||||
() => driver.BeginFrame(
|
||||
ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0));
|
||||
ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0));
|
||||
|
||||
driver.EndFrame();
|
||||
// EndFrame cleared the open-frame guard: BeginFrame is usable again.
|
||||
driver.BeginFrame(ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
driver.EndFrame();
|
||||
}
|
||||
|
||||
// ── Campaign FW3.4a fail-loud: Replay without a completed Collect (no
|
||||
// BeginFrame/EndFrame at all, or BeginFrame with no matching EndFrame)
|
||||
// has nothing recorded to draw — throw rather than silently drawing
|
||||
// nothing, which would look like an empty frame instead of a misuse. ──
|
||||
|
||||
[Fact]
|
||||
public void Replay_WithNoPrecedingCollect_Throws()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData());
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
Assert.Throws<InvalidOperationException>(() => driver.Replay(draw.Frame, draw.Pass));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Replay_WhileCollectIsStillOpen_Throws()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var ctx = new TestContext();
|
||||
var driver = new WalkFrameDriver(fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData());
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
|
||||
Assert.Throws<InvalidOperationException>(() => driver.Replay(draw.Frame, draw.Pass));
|
||||
}
|
||||
|
||||
// ── Deliverable: Collect + Replay called as the SPLIT PAIR (never
|
||||
// RunFrame) — the shape RetailPViewRenderer uses, since it must run other
|
||||
// frame work (PrepareCellBatches/BuildAndBorrow) between the two. Proves
|
||||
// the pair alone — with no GPU work happening until Replay — reproduces
|
||||
// the same turn order RunFrame's combined call would. ──────────────────
|
||||
|
||||
[Fact]
|
||||
public void CollectThenReplay_AsSeparateCalls_PerformsNoGpuWorkUntilReplay()
|
||||
{
|
||||
using var fx = new DispatcherFixture();
|
||||
var log = new List<string>();
|
||||
var ctx = new TestContext();
|
||||
var driver = new WalkFrameDriver(
|
||||
fx.Dispatcher, new RecordingLeafRenderer(log), new FakeWorldData(), new RecordingTrace(log));
|
||||
var walk = new RetailFrameWalk();
|
||||
// Outdoor root (camera cell low word < 0x100): a minimal, no-op
|
||||
// landscape — LScape::draw still runs its sky/terrain turn against
|
||||
// it even though nothing is published to walk cells/buildings for.
|
||||
var landscape = new WalkLandscape { MidWidth = 1, Blocks = new WalkLandBlock?[1] };
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.Collect(
|
||||
walk, cameraCellId: 0u, cameraCell: null, landscape, ctx,
|
||||
Matrix4x4.Identity, cameraWorldPosition: Vector3.Zero, activeTerrainSliceCount: 1);
|
||||
|
||||
// No GPU calls at all yet — Collect is CPU-only.
|
||||
Assert.Empty(log);
|
||||
Assert.Empty(fx.Device.Calls);
|
||||
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.Equal(new[] { "SKY", "TERRAIN:0" }, log);
|
||||
}
|
||||
|
||||
// ── Deliverable: an outdoor landscape-cell turn with no building appends
|
||||
// straight to the stream (no shell call — outdoor cells have no EnvCell
|
||||
// shell), and the accumulated content flushes at frame end. ───────────
|
||||
|
|
@ -478,16 +543,18 @@ public sealed class WalkFrameDriverTests
|
|||
var ctx = new TestContext();
|
||||
var worldData = new FakeWorldData();
|
||||
worldData.OutdoorStaticsByCell[0x8C040005u] = new WalkFrameStaticRecords(
|
||||
[MakeRecord(301, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)])], 0x8C04u);
|
||||
new[] { MakeRecord(301, 0, Vector3.Zero, [new MeshRef((uint)gfxObj, Matrix4x4.Identity)]) }, 0x8C04u);
|
||||
|
||||
var driver = new WalkFrameDriver(
|
||||
fx.Dispatcher, new RecordingLeafRenderer(log), worldData, new RecordingTrace(log));
|
||||
|
||||
using DrawScope draw = fx.BeginDraw();
|
||||
driver.BeginFrame(ctx, draw.Frame, draw.Pass, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
driver.BeginFrame(ctx, Matrix4x4.Identity, Vector3.Zero, activeTerrainSliceCount: 0);
|
||||
((IWalkEventSink)driver).OnLandscapeCellTurn(0x8C040005u);
|
||||
Assert.Empty(log); // accumulates in the stream; nothing flushed yet
|
||||
Assert.Empty(log); // no GPU work at Collect time; nothing recorded to the log yet
|
||||
driver.EndFrame();
|
||||
Assert.Empty(log); // still nothing — EndFrame closes Collect, it does not Replay
|
||||
driver.Replay(draw.Frame, draw.Pass);
|
||||
|
||||
Assert.Equal(new[] { "FLUSH:1:OutdoorStatic" }, log);
|
||||
GpuRecordedMultiDrawIndirect mdi = Assert.Single(fx.Device.Calls.OfType<GpuRecordedMultiDrawIndirect>());
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue