feat(render) Campaign FW2: OrderedDrawStream + walk-order submitter
The walk-order submission layer over the existing RHI (plan section FW2): - OrderedDrawStream: append-only walk-ordered draw commands (GroupKey + transform + per-instance data + WalkDrawStage + cell provenance), struct-of-arrays with one lockstep Reset (#193 shape). The PortalPunch stage exists but has no FW2 submission path - the submitter throws on it; punch emission lands with FW3 wiring. - WbDrawDispatcher.OrderedStream partial: per-instance-first emission (the deferred-alpha shape - command i owns instance i, walk order survives into the indirect array), each SSBO section written once, then one DrawIndirectRangeRhi call per maximal merge run. Runs are built by pure-CPU BuildOrderedMergeRuns and may never span a stage, pipeline-bucket, or cull boundary; ValidateMergeRun re-checks every emitted run and throws (the campaign fail-loud rule). Nothing is sorted, reordered, or dropped: N commands in, N indirect commands out, covered exactly once. - WorldDepthContract: retail world depth verified verbatim from the decomp - Render::zfuncVal @0x00820e1c = 0x2, SetDepthBufferMode @0x005a2d10 writes the enum directly as D3DRS_ZFUNC so the value IS D3DCMP_LESS, applied by the surface-state applier @0x0059c80a with Z-write toggled by blend; the LESSEQUAL sites are GameSky::Draw-local. Seven world pipeline sites now cite the named constant (no value changes). - Plan updated: FW1 status block + gate amendment (the ten pose-stamped retail traces supersede re-expressing the old-builder replay fixtures; those retire with the old builder at FW4 and their scenario classes re-verify at the FW3/FW4 connected gates). Known FW2 scope notes recorded in the code: the building-detail overlay replay is production wiring (FW3); the _drawCullModes scratch may not interleave with a mid-flight RetailAlphaQueue scope (FW3 sequencing constraint). The pixel A/B equivalence proof rides FW3's cutover toggle where a walk-driven scene first exists. Suites: full Release build 0 warnings; Walk lane 154/1 skip; hermetic 6,714/0 (+27 new). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
77f5342b62
commit
e65644cb33
12 changed files with 1461 additions and 31 deletions
166
src/AcDream.App/Rendering/Walk/OrderedDrawStream.cs
Normal file
166
src/AcDream.App/Rendering/Walk/OrderedDrawStream.cs
Normal file
|
|
@ -0,0 +1,166 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW2 — retail's frame phases, in the order the walk
|
||||
/// visits them. <c>RetailPViewPassExecutor</c>'s packed route contract
|
||||
/// (<c>RenderFrameCandidateRoute</c>: LandscapeOutdoorStatic →
|
||||
/// LandscapeBuildingShell → LookInObject → LandscapeOutsideDynamic →
|
||||
/// CellStatic → DynamicLast — <c>WbDrawDispatcher.PackedOracle.cs:108/171</c>
|
||||
/// enforces in-order consumption today) is the closest existing analogue;
|
||||
/// this enum is the walk submitter's OWN phase vocabulary, and a merge run
|
||||
/// built by <see cref="WbDrawDispatcher.BuildOrderedMergeRuns"/> may never
|
||||
/// span two different stages — the submitter treats a stage boundary exactly
|
||||
/// like a material-state boundary (see
|
||||
/// docs/plans/2026-08-30-campaign-fw-frame-walk.md §FW2).
|
||||
/// </summary>
|
||||
internal enum WalkDrawStage : byte
|
||||
{
|
||||
/// <summary><c>LScape::draw</c> @0x00506330 /
|
||||
/// <c>LScape::grab_visible_cells</c> @0x00504EC0 — outdoor terrain.</summary>
|
||||
Terrain,
|
||||
|
||||
/// <summary>An indoor <c>PView::DrawCells</c> @0x005A4840 flood's static
|
||||
/// geometry: EnvCell shells plus the static meshes they contain.</summary>
|
||||
CellStatic,
|
||||
|
||||
/// <summary><c>DrawBuilding</c>'s (<c>RenderDeviceD3D::DrawBuilding</c>
|
||||
/// @0x0059f2a0) exterior shell pass.</summary>
|
||||
BuildingShell,
|
||||
|
||||
/// <summary>
|
||||
/// <c>DrawPortalPolyInternal</c>'s depth-only invisible portal-polygon
|
||||
/// panel — punch far-Z / seal own-depth. FW0 confirmed retail actually
|
||||
/// draws these every frame (not an acdream invention); the AD-117 stamps
|
||||
/// re-invented the mechanism at the wrong site.
|
||||
///
|
||||
/// <para><b>No FW2 submission path exists yet.</b>
|
||||
/// <see cref="WbDrawDispatcher.SubmitOrderedStream"/> throws
|
||||
/// <see cref="System.NotSupportedException"/> if a command carries this
|
||||
/// stage — punch geometry emission lands in FW3 with the world wiring.
|
||||
/// The stage exists now purely so stage-separation gates can exercise the
|
||||
/// boundary before the real emission path is built.</para>
|
||||
/// </summary>
|
||||
PortalPunch,
|
||||
|
||||
/// <summary><c>ConstructView(CBldPortal)</c> look-in static geometry —
|
||||
/// what an exterior building's window or doorway reveals of its own
|
||||
/// interior.</summary>
|
||||
LookInStatic,
|
||||
|
||||
/// <summary>Every non-static draw the walk visits last: entities,
|
||||
/// monsters, items, and the meshes particles ride.</summary>
|
||||
Dynamic,
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// One walk-ordered draw command. The nine fields after <see cref="Key"/> and
|
||||
/// <see cref="Transform"/> are exactly the per-instance data
|
||||
/// <c>WbDrawDispatcher.PrepareDeferredAlphaDraws</c>'s
|
||||
/// <c>DeferredAlphaInstance</c> carries — that method is the per-instance-first
|
||||
/// SSBO-layout template FW2's submitter follows — plus the walk provenance
|
||||
/// (<see cref="Stage"/>, <see cref="CellId"/>) the submitter needs to know
|
||||
/// where a merge run may and may not cross a boundary.
|
||||
/// </summary>
|
||||
/// <param name="Key">Mesh-subset and material identity: index range, texture
|
||||
/// slot/layer, translucency, foliage flags, cull mode. The same
|
||||
/// <see cref="GroupKey"/> the classic material-bucketed path groups instances
|
||||
/// by — FW2 does not bucket by it, only reads its fields per instance.</param>
|
||||
/// <param name="Transform">World transform. Storage binding 0
|
||||
/// (<c>StorageInstances</c>).</param>
|
||||
/// <param name="Stage">The retail frame phase this command belongs to. A
|
||||
/// merge run may never span two different stages.</param>
|
||||
/// <param name="CellId">Walk-order provenance: which cell's traversal emitted
|
||||
/// this command. Not bound to any GPU storage section today — carried for
|
||||
/// FW3's portal-punch wiring and for diagnostics.</param>
|
||||
/// <param name="ClipSlot">Storage binding 3 (<c>StorageClipSlots</c>).</param>
|
||||
/// <param name="Lights">Storage binding 5 (<c>StorageInstanceLightSets</c>).</param>
|
||||
/// <param name="IndoorFlag">Storage binding 6 (<c>StorageInstanceIndoor</c>).</param>
|
||||
/// <param name="Alpha">Storage binding 7 (<c>StorageInstanceAlpha</c>).</param>
|
||||
/// <param name="SelectionLighting">Storage binding 8
|
||||
/// (<c>StorageInstanceSelectionLighting</c>).</param>
|
||||
/// <param name="DetailCategory">Storage binding 9
|
||||
/// (<c>StorageInstanceDetailCategory</c>). A nonzero value forces this
|
||||
/// command into a solo merge run — mirrors the deferred-alpha detail break in
|
||||
/// <c>WbDrawDispatcher.DrawPreparedAlphaBatchRhi</c>.</param>
|
||||
internal readonly record struct OrderedDrawCommand(
|
||||
GroupKey Key,
|
||||
Matrix4x4 Transform,
|
||||
WalkDrawStage Stage,
|
||||
uint CellId,
|
||||
uint ClipSlot,
|
||||
WbDrawDispatcher.InstanceLightSet Lights,
|
||||
uint IndoorFlag,
|
||||
float Alpha,
|
||||
Vector2 SelectionLighting,
|
||||
uint DetailCategory);
|
||||
|
||||
/// <summary>
|
||||
/// Append-only, walk-ordered draw-command stream. Struct-of-arrays storage —
|
||||
/// one parallel list per <see cref="OrderedDrawCommand"/> field, the same
|
||||
/// shape as <see cref="WbDrawDispatcher.InstanceGroup"/>'s per-instance lists
|
||||
/// — so <see cref="WbDrawDispatcher.SubmitOrderedStream"/> can walk the stream
|
||||
/// by index instead of allocating one boxed command per instance.
|
||||
///
|
||||
/// <para>The stream carries no ordering logic of its own: reading it back is
|
||||
/// exactly the sequence <see cref="Append"/> was called in, unconditionally.
|
||||
/// That is the campaign invariant this type exists to make impossible to
|
||||
/// silently regress — see
|
||||
/// docs/plans/2026-08-30-campaign-fw-frame-walk.md §FW2's "a merge across a
|
||||
/// state or stage boundary is forbidden by construction (assert it)" rule.</para>
|
||||
/// </summary>
|
||||
internal sealed class OrderedDrawStream
|
||||
{
|
||||
public readonly List<GroupKey> Keys = new();
|
||||
public readonly List<Matrix4x4> Transforms = new();
|
||||
public readonly List<WalkDrawStage> Stages = new();
|
||||
public readonly List<uint> CellIds = new();
|
||||
public readonly List<uint> ClipSlots = new();
|
||||
public readonly List<WbDrawDispatcher.InstanceLightSet> Lights = new();
|
||||
public readonly List<uint> IndoorFlags = new();
|
||||
public readonly List<float> Alphas = new();
|
||||
public readonly List<Vector2> SelectionLighting = new();
|
||||
public readonly List<uint> DetailCategories = new();
|
||||
|
||||
/// <summary>Number of commands appended since the last <see cref="Reset"/>.</summary>
|
||||
public int Count => Keys.Count;
|
||||
|
||||
public void Append(in OrderedDrawCommand command)
|
||||
{
|
||||
Keys.Add(command.Key);
|
||||
Transforms.Add(command.Transform);
|
||||
Stages.Add(command.Stage);
|
||||
CellIds.Add(command.CellId);
|
||||
ClipSlots.Add(command.ClipSlot);
|
||||
Lights.Add(command.Lights);
|
||||
IndoorFlags.Add(command.IndoorFlag);
|
||||
Alphas.Add(command.Alpha);
|
||||
SelectionLighting.Add(command.SelectionLighting);
|
||||
DetailCategories.Add(command.DetailCategory);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Clears every parallel list together, in one method. The established
|
||||
/// #193 lesson (<c>WbDrawDispatcher.InstanceGroup.ClearPerInstanceData</c>
|
||||
/// carries the same remark): a stream with N parallel lists that resets
|
||||
/// them independently can leave one list stale relative to the others
|
||||
/// after a future field is added and its clear call forgotten — and a
|
||||
/// stale list that only ever grows leaks unboundedly. Ten lists, one
|
||||
/// reset call, so a new eleventh list has nowhere to hide from it.
|
||||
/// </summary>
|
||||
public void Reset()
|
||||
{
|
||||
Keys.Clear();
|
||||
Transforms.Clear();
|
||||
Stages.Clear();
|
||||
CellIds.Clear();
|
||||
ClipSlots.Clear();
|
||||
Lights.Clear();
|
||||
IndoorFlags.Clear();
|
||||
Alphas.Clear();
|
||||
SelectionLighting.Clear();
|
||||
DetailCategories.Clear();
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue