Reapply "perf(rendering): draw retained frame product"

This reverts commit 2c848d4167.
This commit is contained in:
Erik 2026-07-25 08:36:11 +02:00
parent 823936ec31
commit 20f9fadb12
13 changed files with 1333 additions and 267 deletions

View file

@ -76,13 +76,6 @@ public sealed class RetailPViewRenderer
InteriorEntityPartition.IObserver? partitionObserver,
RenderScenePViewFrameProductController? sceneFrameProduct = null)
{
if (sceneFrameProduct is not null
&& partitionObserver is not ICurrentRenderPViewObserver)
{
throw new ArgumentException(
"The scene frame-product referee requires the current PView observer.",
nameof(partitionObserver));
}
_partitionObserver = partitionObserver;
_candidateObserver = partitionObserver as ICurrentRenderPViewObserver;
_sceneFrameProduct = sceneFrameProduct;
@ -212,6 +205,16 @@ public sealed class RetailPViewRenderer
ctx.ViewProjection,
_viewconeScratch);
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
{
@ -242,32 +245,73 @@ public sealed class RetailPViewRenderer
}
}
DrawLandscapeThroughOutsideView(ctx, passes, clipAssembly, partition, viewcone);
if (_sceneFrameProduct is not null)
{
frameView = _sceneFrameProduct.BuildAndBorrow(
pvFrame,
clipAssembly,
viewcone,
_lookInFrames,
drawableCells,
ctx.Cells,
ctx.AnimatedEntityIds,
ctx.RootCell.IsOutdoorNode);
frameViewBorrowed = true;
frameEntityPasses!.BeginEntityFrame(in frameView);
entityFrameOpen = true;
}
DrawLandscapeThroughOutsideView(
ctx,
passes,
clipAssembly,
partition,
viewcone,
frameEntityPasses,
in frameView);
passes.UseIndoorMembershipOnlyRouting();
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
DrawEnvCellShells(passes, pvFrame);
DrawCellObjectLists(ctx, passes, pvFrame, clipAssembly, drawableCells, partition, viewcone);
DrawDynamicsLast(ctx, passes, partition, viewcone, ctx.RootCell.IsOutdoorNode);
_candidateObserver?.CompletePViewFrame();
_sceneFrameProduct?.BuildAndCompare(
DrawCellObjectLists(
ctx,
passes,
pvFrame,
clipAssembly,
viewcone,
_lookInFrames,
drawableCells,
ctx.Cells,
ctx.AnimatedEntityIds,
ctx.PlayerLandblockId ?? 0,
partition,
viewcone,
frameEntityPasses,
in frameView);
DrawDynamicsLast(
ctx,
passes,
partition,
viewcone,
ctx.RootCell.IsOutdoorNode,
ctx.CameraWorldPosition);
frameEntityPasses,
in frameView);
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
@ -397,7 +441,9 @@ public sealed class RetailPViewRenderer
RetailPViewFrameInput ctx,
IRetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
InteriorEntityPartition.Result partition)
InteriorEntityPartition.Result partition,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView)
{
if (_lookInFrames.Count == 0)
return;
@ -475,7 +521,16 @@ public sealed class RetailPViewRenderer
i,
cellId,
_cellStaticScratch);
passes.DrawEntityBucket(ctx, _cellStaticScratch, _oneCell);
DrawEntityRouteOrLegacy(
ctx,
passes,
frameEntityPasses,
in frameView,
RenderFrameCandidateRoute.LookInObject,
i,
cellId,
_cellStaticScratch,
_oneCell);
// The cell-particles pass for look-in cells — retail's
// nested DrawCells draws objects WITH their emitters.
@ -492,7 +547,9 @@ public sealed class RetailPViewRenderer
IRetailPViewPassExecutor passes,
ClipFrameAssembly clipAssembly,
InteriorEntityPartition.Result partition,
ViewconeCuller viewcone)
ViewconeCuller viewcone,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView)
{
if (clipAssembly.OutsideViewSlices.Length == 0)
return;
@ -534,15 +591,37 @@ public sealed class RetailPViewRenderer
probeSliceIndex,
0,
_outdoorStaticScratch);
RenderFrameEntityDrawRequest? entityDraw =
frameEntityPasses is null
? null
: new RenderFrameEntityDrawRequest(
frameView,
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
probeSliceIndex,
0,
ctx.PlayerLandblockId ?? 0);
probeSliceIndex++;
passes.DrawLandscapeSlice(ctx, new RetailPViewLandscapeSliceContext(slice, _outdoorStaticScratch));
passes.DrawLandscapeSlice(
ctx,
new RetailPViewLandscapeSliceContext(
slice,
_outdoorStaticScratch)
{
EntityDraw = entityDraw,
});
}
// #124: far-building look-ins draw HERE — still inside the landscape
// stage (their punches mark against the terrain/exterior depth just
// drawn), strictly BEFORE the depth clear + seals below, matching
// retail's LScape::draw placement (DrawCells pc:432719 vs 432732/432785).
DrawBuildingLookIns(ctx, passes, clipAssembly, partition);
DrawBuildingLookIns(
ctx,
passes,
clipAssembly,
partition,
frameEntityPasses,
in frameView);
// LATE phase (per slice): outside-stage dynamics' meshes (#118 — drawn
// pre-clear so the seal protects their aperture pixels; AFTER the
@ -591,9 +670,25 @@ public sealed class RetailPViewRenderer
probeSliceIndex,
0,
_outdoorStaticScratch);
RenderFrameEntityDrawRequest? entityDraw =
frameEntityPasses is null
? null
: new RenderFrameEntityDrawRequest(
frameView,
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
probeSliceIndex,
0,
ctx.PlayerLandblockId ?? 0);
probeSliceIndex++;
passes.DrawLandscapeSliceLate(ctx, new RetailPViewLandscapeLateSliceContext(
slice, _outdoorStaticScratch, _lateParticleOwnerScratch));
passes.DrawLandscapeSliceLate(
ctx,
new RetailPViewLandscapeLateSliceContext(
slice,
_outdoorStaticScratch,
_lateParticleOwnerScratch)
{
EntityDraw = entityDraw,
});
}
// #131: UNATTACHED emitters (AttachedObjectId == 0 — portal swirls,
@ -702,7 +797,9 @@ public sealed class RetailPViewRenderer
IRetailPViewPassExecutor passes,
InteriorEntityPartition.Result partition,
ViewconeCuller viewcone,
bool rootIsOutdoor)
bool rootIsOutdoor,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView)
{
if (partition.Dynamics.Count == 0)
return;
@ -749,7 +846,16 @@ public sealed class RetailPViewRenderer
0,
_dynamicsScratch);
passes.UseIndoorMembershipOnlyRouting();
passes.DrawEntityBucket(ctx, _dynamicsScratch, visibleCellIds: null);
DrawEntityRouteOrLegacy(
ctx,
passes,
frameEntityPasses,
in frameView,
RenderFrameCandidateRoute.DynamicLast,
0,
0,
_dynamicsScratch,
visibleCellIds: null);
// #121: dynamics' attached emitters (portal swirls, creature effects)
// gate through the SAME cone-surviving owner set as their meshes —
@ -776,7 +882,9 @@ public sealed class RetailPViewRenderer
ClipFrameAssembly clipAssembly,
HashSet<uint> drawableCells,
InteriorEntityPartition.Result partition,
ViewconeCuller viewcone)
ViewconeCuller viewcone,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView)
{
// T1: per-cell STATIC object lists only (dat-baked 0x40 statics) —
// dynamics moved to DrawDynamicsLast. Far→near with the cells, after
@ -840,7 +948,16 @@ public sealed class RetailPViewRenderer
0,
_allCellStatics);
passes.UseIndoorMembershipOnlyRouting();
passes.DrawEntityBucket(ctx, _allCellStatics, _cellObjCells);
DrawEntityRouteOrLegacy(
ctx,
passes,
frameEntityPasses,
in frameView,
RenderFrameCandidateRoute.CellStatic,
0,
0,
_allCellStatics,
_cellObjCells);
}
// Cell-particle pass — consolidated across ALL visible cells into ONE
@ -863,6 +980,35 @@ public sealed class RetailPViewRenderer
new RetailPViewCellSliceContext(0u, NoClipSlice, _allCellStatics));
}
private static void DrawEntityRouteOrLegacy(
RetailPViewFrameInput frame,
IRetailPViewPassExecutor passes,
IRenderFrameEntityPassExecutor? frameEntityPasses,
in RenderFrameView frameView,
RenderFrameCandidateRoute route,
int routeIndex,
uint cellId,
IReadOnlyList<WorldEntity> legacyEntities,
HashSet<uint>? visibleCellIds)
{
if (frameEntityPasses is not null)
{
frameEntityPasses.DrawEntityRoute(
frame.Camera,
in frameView,
route,
routeIndex,
cellId,
frame.PlayerLandblockId ?? 0);
return;
}
passes.DrawEntityBucket(
frame,
legacyEntities,
visibleCellIds);
}
// T3 scratch lists (render thread only; cleared per use).
private readonly List<WorldEntity> _outdoorStaticScratch = new();
private readonly List<WorldEntity> _cellStaticScratch = new();
@ -1300,7 +1446,10 @@ public sealed class RetailPViewFrameResult
public readonly record struct RetailPViewLandscapeSliceContext(
ClipViewSlice Slice,
IReadOnlyList<WorldEntity> OutdoorEntities);
IReadOnlyList<WorldEntity> OutdoorEntities)
{
internal RenderFrameEntityDrawRequest? EntityDraw { get; init; }
}
/// <summary>#131/#132: the late landscape phase's per-slice payload —
/// outside-stage dynamics to mesh-draw, plus the full scene-particle owner
@ -1308,7 +1457,10 @@ public readonly record struct RetailPViewLandscapeSliceContext(
public readonly record struct RetailPViewLandscapeLateSliceContext(
ClipViewSlice Slice,
IReadOnlyList<WorldEntity> Dynamics,
IReadOnlyList<WorldEntity> ParticleOwners);
IReadOnlyList<WorldEntity> ParticleOwners)
{
internal RenderFrameEntityDrawRequest? EntityDraw { get; init; }
}
public readonly record struct RetailPViewCellSliceContext(
uint CellId,