feat(render) Campaign FW3.2a: the walk-to-draw population layer
The piece that turns walk-visited static content into draws, with no production frame wiring (FW3.2b roots the frame): - TryClassifyBatch: ONE shared per-batch classify core (the #426 untextured gate, #188 opacity promotion, texture resolve, foliage classification, in the exact original order) extracted from ClassifyBatches; the classic and packed classifiers now call it - behavior-identical, proven by the full hermetic + InstalledDat + Core Wb suites. - ClassifyEntityForWalk / WalkClassifiedBatch: the per-entity seam yielding per-batch keys + instance data WITHOUT InstanceGroup bucketing, plus the per-part selection data (picking stays alive on the walk path - the survey's unlisted-consumer fix). - WalkStaticStreamPopulator: per-entity walk-ordered opaque appends (under depth Less, opaque order is pixel-relevant only for coplanar surfaces, which retail resolves first-drawn-wins in ITS order - never material-grouped), translucent instances to the SAME RetailAlphaQueue via SubmitWalkAlphaInstance (identical viewer distances; walk-order submission improves retail's tie fidelity), selection parts published per entity. - SubmitOrderedStream now owns _orderedDrawCullModes, retiring the FW2-recorded alpha-scope interleaving constraint; DrawIndirectRangeRhi takes an optional cull array (all existing call sites unchanged). The referee test was verified to FAIL against the old shared-scratch behavior. - WalkDrawStage.OutdoorStatic added for the landscape turn. Suites: full Release build 0 warnings; Walk lane 195/1 skip; hermetic 6,747/0 (the two failures the implementation round reported were transient - both pass in isolation and in the full run). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
b10ad662b0
commit
81c6531727
9 changed files with 1264 additions and 116 deletions
|
|
@ -22,6 +22,20 @@ internal enum WalkDrawStage : byte
|
|||
/// <c>LScape::grab_visible_cells</c> @0x00504EC0 — outdoor terrain.</summary>
|
||||
Terrain,
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2a: the outdoor static objects (scenery,
|
||||
/// buildings-as-decor, landblock statics —
|
||||
/// <c>RenderProjectionClass.OutdoorStatic</c>) <c>LScape::draw</c> visits
|
||||
/// for each landblock alongside its terrain mesh — retail's landscape
|
||||
/// walk draws a landblock's ground and its static content in the same
|
||||
/// pass, distinct from the ground mesh itself (<see cref="Terrain"/>) and
|
||||
/// from a building's own exterior shell (<see cref="BuildingShell"/>).
|
||||
/// The packed route's analogue is
|
||||
/// <c>RenderFrameCandidateRoute.LandscapeOutdoorStatic</c>
|
||||
/// (<c>WbDrawDispatcher.PackedOracle.cs</c>).
|
||||
/// </summary>
|
||||
OutdoorStatic,
|
||||
|
||||
/// <summary>An indoor <c>PView::DrawCells</c> @0x005A4840 flood's static
|
||||
/// geometry: EnvCell shells plus the static meshes they contain.</summary>
|
||||
CellStatic,
|
||||
|
|
|
|||
137
src/AcDream.App/Rendering/Walk/WalkStaticStreamPopulator.cs
Normal file
137
src/AcDream.App/Rendering/Walk/WalkStaticStreamPopulator.cs
Normal file
|
|
@ -0,0 +1,137 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering.Walk;
|
||||
|
||||
/// <summary>
|
||||
/// Campaign FW stage FW3.2a: the walk→draw population layer. Turns one
|
||||
/// cell's already-classified static content into <see cref="OrderedDrawCommand"/>
|
||||
/// appends (opaque, walk order), per-instance deferred-alpha submissions
|
||||
/// (translucent), and selection-scene publications (picking) — WITHOUT any
|
||||
/// production frame wiring. <c>WorldSceneRenderer</c> does not call this
|
||||
/// class yet; FW3.2b roots it into the real frame.
|
||||
///
|
||||
/// <para><b>Why per-entity append instead of the classic material grouping</b>:
|
||||
/// under <see cref="WorldDepthContract"/>'s depth-compare Less, opaque draw
|
||||
/// order is pixel-relevant ONLY for coplanar surfaces (a rug on a floor, a
|
||||
/// shell over terrain) — and retail resolves those by first-drawn-wins in
|
||||
/// ITS walk/cell-content order, not by any texture/material grouping. So
|
||||
/// this populator appends one <see cref="OrderedDrawCommand"/> per (entity,
|
||||
/// opaque batch) in the SAME order the caller's <c>records</c> span presents
|
||||
/// them — never material-grouped, never re-sorted. Translucent batches go to
|
||||
/// the SAME <see cref="RetailAlphaQueue"/> the classic/packed paths already
|
||||
/// use (global far→near re-sort still applies; walk-order submission only
|
||||
/// improves retail's submission-order tie-break fidelity for coincident
|
||||
/// distances — <c>WbDrawDispatcher.DeferTransparentGroups</c>'s own doc
|
||||
/// comment cites the same <c>CShadowPart::insertion_sort</c> stability this
|
||||
/// preserves).</para>
|
||||
///
|
||||
/// <para>Reads no retained scene state itself: every method takes the target
|
||||
/// <see cref="OrderedDrawStream"/> and a caller-supplied span of already
|
||||
/// -queried <see cref="RenderProjectionRecord"/>s (from
|
||||
/// <c>RenderSceneQuery.CopyCellStaticsTo</c> / <c>CopyIndexTo</c>). Owns only
|
||||
/// two small per-call scratch lists — the classify seam's per-entity output
|
||||
/// — reused across records to keep this hot path allocation-free after
|
||||
/// warmup.</para>
|
||||
/// </summary>
|
||||
internal sealed class WalkStaticStreamPopulator
|
||||
{
|
||||
private readonly WbDrawDispatcher _dispatcher;
|
||||
private readonly List<WbDrawDispatcher.WalkClassifiedBatch> _batchScratch = new();
|
||||
private readonly List<WbDrawDispatcher.WalkClassifiedSelectionPart> _selectionScratch = new();
|
||||
|
||||
internal WalkStaticStreamPopulator(WbDrawDispatcher dispatcher)
|
||||
{
|
||||
_dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Populates <paramref name="stream"/> from one indoor cell's static
|
||||
/// content (<c>RenderProjectionClass.IndoorCellStatic</c> —
|
||||
/// <c>RenderSceneQuery.CopyCellStaticsTo</c>). <paramref name="stage"/>
|
||||
/// is caller-selected per the walk turn this cell's content belongs to
|
||||
/// — <see cref="WalkDrawStage.CellStatic"/> for an ordinary
|
||||
/// <c>PView::DrawCells</c> flood, <see cref="WalkDrawStage.LookInStatic"/>
|
||||
/// for a <c>ConstructView(CBldPortal)</c> look-in, or
|
||||
/// <see cref="WalkDrawStage.BuildingShell"/> for a building's own
|
||||
/// exterior shell content.
|
||||
/// </summary>
|
||||
internal void PopulateCell(
|
||||
OrderedDrawStream stream,
|
||||
WalkDrawStage stage,
|
||||
uint cellId,
|
||||
ReadOnlySpan<RenderProjectionRecord> records,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(stream);
|
||||
for (int i = 0; i < records.Length; i++)
|
||||
{
|
||||
ClassifyAndAppend(
|
||||
stream, stage, cellId, in records[i], tupleLandblockId,
|
||||
cameraWorldPosition, viewProjection);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The landscape entry point: outdoor static content
|
||||
/// (<c>RenderProjectionClass.OutdoorStatic</c> —
|
||||
/// <c>RenderSceneQuery.CopyIndexTo(RenderSceneIndex.OutdoorStatic, ...)</c>)
|
||||
/// at <see cref="WalkDrawStage.OutdoorStatic"/>, the terrain-adjacent
|
||||
/// stage <c>LScape::draw</c> visits a landblock's static objects at
|
||||
/// alongside its ground mesh (see that stage value's own doc comment).
|
||||
/// <paramref name="cellId"/> is the OUTDOOR landblock id — outdoor
|
||||
/// statics have no EnvCell of their own, so this is walk-order
|
||||
/// provenance only, not a clip-slot key.
|
||||
/// </summary>
|
||||
internal void PopulateOutdoorStatics(
|
||||
OrderedDrawStream stream,
|
||||
uint cellId,
|
||||
ReadOnlySpan<RenderProjectionRecord> records,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection) =>
|
||||
PopulateCell(
|
||||
stream, WalkDrawStage.OutdoorStatic, cellId, records,
|
||||
tupleLandblockId, cameraWorldPosition, viewProjection);
|
||||
|
||||
private void ClassifyAndAppend(
|
||||
OrderedDrawStream stream,
|
||||
WalkDrawStage stage,
|
||||
uint cellId,
|
||||
in RenderProjectionRecord record,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition,
|
||||
Matrix4x4 viewProjection)
|
||||
{
|
||||
_batchScratch.Clear();
|
||||
_selectionScratch.Clear();
|
||||
_dispatcher.ClassifyEntityForWalk(
|
||||
in record, tupleLandblockId, _batchScratch, _selectionScratch);
|
||||
|
||||
for (int i = 0; i < _batchScratch.Count; i++)
|
||||
{
|
||||
WbDrawDispatcher.WalkClassifiedBatch batch = _batchScratch[i];
|
||||
if (batch.IsOpaque)
|
||||
{
|
||||
stream.Append(new OrderedDrawCommand(
|
||||
batch.Key, batch.Transform, stage, cellId, batch.ClipSlot,
|
||||
batch.Lights, batch.IndoorFlag, batch.Alpha,
|
||||
batch.SelectionLighting, batch.DetailCategory));
|
||||
}
|
||||
else
|
||||
{
|
||||
_dispatcher.SubmitWalkAlphaInstance(
|
||||
in batch, cameraWorldPosition, viewProjection);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _selectionScratch.Count; i++)
|
||||
{
|
||||
WbDrawDispatcher.WalkClassifiedSelectionPart part = _selectionScratch[i];
|
||||
_dispatcher.PublishWalkSelectionPart(in part);
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue