refactor(render): detach packed pview product from production

This commit is contained in:
Erik 2026-08-31 04:10:10 +02:00
parent ef12170a3c
commit c9fb7e7d4c
4 changed files with 54 additions and 180 deletions

View file

@ -15,8 +15,7 @@ public sealed class RetailPViewRenderer
{
private readonly InteriorEntityPartition.IObserver? _partitionObserver;
private readonly ICurrentRenderPViewObserver? _candidateObserver;
private readonly RenderScenePViewFrameProductController?
_sceneFrameProduct;
private readonly RenderSceneShadowRuntime? _renderSceneShadow;
private readonly PortalVisibilityFrame _mainPortalFrameScratch = new();
private readonly ClipFrameAssembly _clipAssemblyScratch = new();
private readonly ViewconeCuller _viewconeScratch = new();
@ -133,7 +132,7 @@ public sealed class RetailPViewRenderer
internal RetailPViewRenderer(
InteriorEntityPartition.IObserver? partitionObserver,
RenderScenePViewFrameProductController? sceneFrameProduct = null,
RenderSceneShadowRuntime? renderSceneShadow = null,
Walk.WalkBuildingRegistry? walkBuildings = null,
Walk.WalkLandscapeAssembler? walkLandscape = null,
CellVisibility? walkCellRegistry = null)
@ -146,7 +145,7 @@ public sealed class RetailPViewRenderer
: null;
_partitionObserver = partitionObserver;
_candidateObserver = partitionObserver as ICurrentRenderPViewObserver;
_sceneFrameProduct = sceneFrameProduct;
_renderSceneShadow = renderSceneShadow;
}
// T2 (BR-4): retail has NO distance constant on the flood-admission chain
@ -225,7 +224,7 @@ public sealed class RetailPViewRenderer
// required — the walk submits through WbDrawDispatcher.SubmitOrderedStream
// and needs a real GPU frame/encoder (RequireWalkSubmission), which no
// test IRetailPViewPassExecutor fake can supply. Whenever a concrete
// executor IS present, the packed entity-route product must ALSO be
// executor IS present, the retained render scene must ALSO be
// wired with all three walk registries — a scene product without the
// walk data (or vice versa) is a production miswiring, not a
// legacy/diagnostic shape, so it fails loud rather than silently
@ -237,12 +236,12 @@ public sealed class RetailPViewRenderer
&& _walkCellRegistry is not null
&& _walkWorldData is not null;
if (walkExecutor is not null
&& _sceneFrameProduct is not null
&& _renderSceneShadow is not null
&& !walkRegistriesReady)
{
throw new InvalidOperationException(
"RetailPViewRenderer has a concrete pass executor and a "
+ "RenderScenePViewFrameProductController but the walk registries "
+ "RenderSceneShadowRuntime but the walk registries "
+ "(WalkBuildingRegistry/WalkLandscapeAssembler/CellVisibility) are "
+ "not all wired — the Campaign FW3.2b-2 static cutover requires "
+ "every piece together; see FrameRootComposition's "
@ -250,7 +249,7 @@ public sealed class RetailPViewRenderer
}
bool walkActive =
walkExecutor is not null
&& _sceneFrameProduct is not null
&& _renderSceneShadow is not null
&& walkRegistriesReady;
// Campaign FW3.4a: THE ONE WALK. Builds walkContext/walkLandscape/
@ -320,7 +319,7 @@ public sealed class RetailPViewRenderer
}
_walkWorldData!.BeginFrame(
_sceneFrameProduct!.SceneQuery,
_renderSceneShadow!.Query,
ctx.PlayerLandblockId ?? 0u,
ctx.RenderCenterLbX,
ctx.RenderCenterLbY);
@ -496,16 +495,11 @@ public sealed class RetailPViewRenderer
ctx.ViewProjection,
_viewconeScratch);
// Packed frame routes no longer participate in production. These
// placeholders keep the standalone legacy/fake path self-contained
// until FW4 removes that shell completely.
IRenderFrameEntityPassExecutor? frameEntityPasses = null;
if (_sceneFrameProduct is not null)
{
frameEntityPasses = passes as IRenderFrameEntityPassExecutor
?? throw new InvalidOperationException(
"The production frame product requires a packed entity-pass executor.");
}
RenderFrameView frameView = default;
bool frameViewBorrowed = false;
bool entityFrameOpen = false;
_candidateObserver?.BeginPViewFrame();
try
{
@ -531,32 +525,11 @@ public sealed class RetailPViewRenderer
_rootFloodSetScratch.Add(cellId);
}
if (_sceneFrameProduct is not null)
{
frameView = _sceneFrameProduct.BuildAndBorrow(
pvFrame,
clipAssembly,
viewcone,
walkActive ? walkDriver!.LookInCellTurns : [],
walkActive ? walkDriver : null,
outsideStageFlood,
ctx.Cells,
ctx.AnimatedEntityIds,
ctx.RootCell.IsOutdoorNode,
// Non-walk frames (diagnostic fakes / legacy fallback)
// keep the pre-split admission: drawableCells WAS the
// flood in the legacy meaning.
walkActive ? _rootFloodSetScratch : drawableCells);
frameViewBorrowed = true;
frameEntityPasses!.BeginEntityFrame(in frameView);
entityFrameOpen = true;
}
// The retained scene product is the production object source.
// Rebuild the former WorldEntity partition only for the standalone
// fallback and explicitly enabled comparison/probe paths.
// The walk reads the retained scene directly. Rebuild the former
// WorldEntity partition only for the standalone fallback and
// explicitly enabled comparison/probe paths.
InteriorEntityPartition.Result? partition = null;
if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled)
if (!walkActive || LegacyPartitionDiagnosticsEnabled)
{
InteriorEntityPartition.Partition(
_partitionResult,
@ -568,11 +541,14 @@ public sealed class RetailPViewRenderer
partition = _partitionResult;
}
RenderFrameDiagnosticCounts counts = frameViewBorrowed
? frameView.DiagnosticCounts
RenderProjectionCounts retainedCounts = walkActive
? _renderSceneShadow!.Counts
: default;
RenderFrameDiagnosticCounts counts = walkActive
? WalkDiagnosticCounts(retainedCounts)
: LegacyDiagnosticCounts(partition!);
RenderProjectionCounts sourceCounts = frameViewBorrowed
? frameView.SourceDigest.Counts
RenderProjectionCounts sourceCounts = walkActive
? retainedCounts
: LegacySourceCounts(partition!);
// prepareCells is exactly "main flood look-in cells" — the cells
// this traversal actually reached, i.e. retail's in-view set.
@ -761,27 +737,14 @@ public sealed class RetailPViewRenderer
// Outdoor-cell unattached emitters drew in the landscape stage.
passes.DrawUnattachedSceneParticles(ctx, outdoorCells: false);
if (entityFrameOpen)
{
frameEntityPasses!.CompleteEntityFrame(in frameView);
entityFrameOpen = false;
}
_candidateObserver?.CompletePViewFrame();
_sceneFrameProduct?.CompleteProduction(in frameView);
return result;
}
catch
{
if (entityFrameOpen)
frameEntityPasses!.AbortEntityFrame();
_candidateObserver?.AbortPViewFrame();
throw;
}
finally
{
if (frameViewBorrowed)
_sceneFrameProduct!.Release(in frameView);
}
}
// R-A2: group the nearby building cells by BuildingId and run one per-building flood per group
@ -2408,6 +2371,27 @@ public sealed class RetailPViewRenderer
|| AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled
|| AcDream.App.Streaming.EntityVanishProbe.Enabled;
private static RenderFrameDiagnosticCounts WalkDiagnosticCounts(
RenderProjectionCounts source)
{
int dynamics = checked(
source.LiveDynamicRoot
+ source.ActiveAnimatedStatic
+ source.EquippedChild);
return new RenderFrameDiagnosticCounts(
source.OutdoorStatic,
source.IndoorCellStatic,
dynamics,
TransformCount: source.Total,
OpaqueClassificationCount: 0,
AlphaClassificationCount: 0,
LightSetCount: 0,
SelectionPartCount: 0,
RouteCandidateCount: source.Total,
EntityCandidateCount: source.Total,
MeshPartCount: 0);
}
internal static RenderFrameDiagnosticCounts LegacyDiagnosticCounts(
InteriorEntityPartition.Result partition)
{