refactor(render): detach packed pview product from production
This commit is contained in:
parent
ef12170a3c
commit
c9fb7e7d4c
4 changed files with 54 additions and 180 deletions
|
|
@ -347,20 +347,13 @@ internal sealed class FrameRootCompositionPhase
|
|||
live.EntityEffects,
|
||||
d.Log);
|
||||
IWorldSceneFramePhase? worldSceneRenderer = null;
|
||||
CurrentRenderSceneOracle? currentRenderSceneOracle =
|
||||
interaction.RetainedUi?.Screenshots is not null
|
||||
&& d.Options.AutomationArtifactDirectory is not null
|
||||
? new CurrentRenderSceneOracle()
|
||||
: null;
|
||||
RenderSceneShadowComparisonController? renderSceneShadowComparison =
|
||||
currentRenderSceneOracle is not null
|
||||
&& live.RenderSceneShadow is not null
|
||||
? new RenderSceneShadowComparisonController(
|
||||
live.RenderSceneShadow,
|
||||
currentRenderSceneOracle,
|
||||
message => d.Log("[UI-PROBE] " + message),
|
||||
acknowledgeDirty: false)
|
||||
: null;
|
||||
// Campaign FW4: the retail walk reads RenderSceneShadowRuntime
|
||||
// directly. The old current-partition oracle, shadow comparison, and
|
||||
// packed PView frame product no longer participate in production or
|
||||
// automation; their null snapshot slots remain until the diagnostic
|
||||
// contract cleanup later in this campaign.
|
||||
CurrentRenderSceneOracle? currentRenderSceneOracle = null;
|
||||
RenderSceneShadowComparisonController? renderSceneShadowComparison = null;
|
||||
RenderScenePViewFrameProductController? renderFrameProduct = null;
|
||||
{
|
||||
// Campaign V slice V6j: the world scene is composed on BOTH arms.
|
||||
|
|
@ -487,17 +480,8 @@ internal sealed class FrameRootCompositionPhase
|
|||
live.SkyRenderer,
|
||||
content.ParticleSystem,
|
||||
live.ParticleRenderer);
|
||||
renderFrameProduct =
|
||||
live.RenderSceneShadow is not null
|
||||
? new RenderScenePViewFrameProductController(
|
||||
live.RenderSceneShadow,
|
||||
currentRenderSceneOracle,
|
||||
message => d.Log("[UI-PROBE] " + message),
|
||||
live.DrawDispatcher!)
|
||||
: null;
|
||||
// After G4 the retained product is the production object source.
|
||||
// The old dispatcher/selection observer is intentionally detached;
|
||||
// automation still compares the independently built PView route list.
|
||||
// The retained scene is the walk's production object source. The
|
||||
// old dispatcher/selection observer remains detached.
|
||||
live.DrawDispatcher!.SetCurrentRenderSceneObserver(null);
|
||||
live.SelectionScene.SetCurrentRenderSceneObserver(null);
|
||||
worldSceneRenderer = new WorldSceneRenderer(
|
||||
|
|
@ -512,7 +496,7 @@ internal sealed class FrameRootCompositionPhase
|
|||
new WorldScenePViewRenderer(
|
||||
new RetailPViewRenderer(
|
||||
currentRenderSceneOracle,
|
||||
renderFrameProduct,
|
||||
live.RenderSceneShadow,
|
||||
// Campaign FW3.2b-2: the walk's production world
|
||||
// data, published/retired with each landblock by the
|
||||
// render publisher (FW3.1).
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -43,12 +43,7 @@ public sealed class FrameRootCompositionTests
|
|||
IReadOnlyList<CompiledCall> calls = CompiledCallGraph.Read(compose);
|
||||
|
||||
int resources = CallIndex(calls, typeof(RenderFrameResourceController), ".ctor");
|
||||
int comparison = CallIndex(
|
||||
calls,
|
||||
typeof(RenderSceneShadowComparisonController),
|
||||
".ctor",
|
||||
resources + 1);
|
||||
int scene = CallIndex(calls, typeof(WorldSceneRenderer), ".ctor", comparison + 1);
|
||||
int scene = CallIndex(calls, typeof(WorldSceneRenderer), ".ctor", resources + 1);
|
||||
int automation = CallIndex(
|
||||
calls,
|
||||
typeof(WorldLifecycleAutomationController),
|
||||
|
|
|
|||
|
|
@ -197,95 +197,6 @@ public sealed class RetailPViewPassExecutorTests
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawInside_production_product_supplies_the_ordered_entity_routes()
|
||||
{
|
||||
// Campaign FW3.2b-2: no cell-static entity here (adjudicated
|
||||
// 2026-08-30) — CellStatic no longer flows through the packed
|
||||
// product at all (WalkFrameDriver owns every cell static now, via
|
||||
// OrderedDrawStream, not this route); a static entity registered on
|
||||
// the LEGACY side with nothing on the packed side would report a
|
||||
// permanent MismatchCount, which is not what this test is proving.
|
||||
// The route-consumption-order assertion below still exercises the
|
||||
// one route that DID survive the cutover (DynamicLast).
|
||||
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
||||
WorldEntity dynamic = Entity(
|
||||
31u,
|
||||
serverGuid: 0x80000031u,
|
||||
parentCellId: interior.CellId);
|
||||
uint landblockId = interior.CellId & 0xFFFF0000u;
|
||||
var oracle = new CurrentRenderSceneOracle();
|
||||
using var shadow = new RenderSceneShadowRuntime(
|
||||
RenderSceneGeneration.FromRaw(1));
|
||||
RenderProjectionRecord dynamicRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
||||
RenderProjectionClass.LiveDynamicRoot,
|
||||
RenderOwnerIncarnation.FromRaw(dynamic.Id),
|
||||
landblockId,
|
||||
interior.CellId,
|
||||
dynamic,
|
||||
spatiallyVisible: true);
|
||||
shadow.Journal.Register(in dynamicRecord);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var product = new RenderScenePViewFrameProductController(
|
||||
shadow,
|
||||
oracle);
|
||||
var renderer = new RetailPViewRenderer(oracle, product);
|
||||
using var executor = new RecordingExecutor();
|
||||
|
||||
renderer.DrawInside(
|
||||
Frame(interior, [dynamic]),
|
||||
executor);
|
||||
|
||||
AssertAppearsInOrder(
|
||||
string.Join('|', executor.Operations),
|
||||
"entity-frame-begin",
|
||||
"entity-route:DynamicLast:0:00000000",
|
||||
"entity-frame-complete");
|
||||
Assert.Equal(0, product.Snapshot.MismatchCount);
|
||||
Assert.Equal(
|
||||
product.Snapshot.ExpectedDigest,
|
||||
product.Snapshot.ActualDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawInside_production_product_uses_retained_routes_without_legacy_partition_when_diagnostics_are_disabled()
|
||||
{
|
||||
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
||||
WorldEntity dynamic = Entity(
|
||||
32u,
|
||||
serverGuid: 0x80000032u,
|
||||
parentCellId: interior.CellId);
|
||||
uint landblockId = interior.CellId & 0xFFFF0000u;
|
||||
using var shadow = new RenderSceneShadowRuntime(
|
||||
RenderSceneGeneration.FromRaw(1));
|
||||
RenderProjectionRecord dynamicRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
||||
RenderProjectionClass.LiveDynamicRoot,
|
||||
RenderOwnerIncarnation.FromRaw(dynamic.Id),
|
||||
landblockId,
|
||||
interior.CellId,
|
||||
dynamic,
|
||||
spatiallyVisible: true);
|
||||
shadow.Journal.Register(in dynamicRecord);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var product = new RenderScenePViewFrameProductController(shadow);
|
||||
var renderer = new RetailPViewRenderer(null, product);
|
||||
using var executor = new RecordingExecutor();
|
||||
|
||||
renderer.DrawInside(Frame(interior, [dynamic]), executor);
|
||||
|
||||
AssertAppearsInOrder(
|
||||
string.Join('|', executor.Operations),
|
||||
"entity-frame-begin",
|
||||
"entity-route:DynamicLast:0:00000000",
|
||||
"entity-frame-complete");
|
||||
Assert.Equal(0uL, product.Snapshot.ComparisonCount);
|
||||
Assert.Equal(1, product.Snapshot.ActualCandidateCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch()
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue