perf(rendering): retire production entity partition
Route production meshes and attached particles from the exact retained PView frame ranges, while keeping the former WorldEntity partition only for standalone tests and explicit diagnostic/oracle probes. Copy retained source/count facts before arena release so world diagnostics no longer require the old partition. Record the accepted G4 visual gate and open the exact G5 production matrix. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
14b48cf553
commit
10ccce3f2d
11 changed files with 452 additions and 125 deletions
|
|
@ -1639,9 +1639,13 @@ port in any phase — no separate listing here.
|
||||||
> comparisons have zero mismatches. The first retained-path visual gate exposed
|
> comparisons have zero mismatches. The first retained-path visual gate exposed
|
||||||
> independent portal scheduling/private-view/ACE-revisit ownership regressions.
|
> independent portal scheduling/private-view/ACE-revisit ownership regressions.
|
||||||
> Their correction passed visually while the draw cutover was temporarily
|
> Their correction passed visually while the draw cutover was temporarily
|
||||||
> reverted, which proves the correction but not the retained source.
|
> reverted. The reapplied retained source subsequently passed the connected
|
||||||
> `20f9fadb` reapplies the cutover over those fixes; its connected portal/revisit
|
> portal/revisit matrix on 2026-07-25, including stable server objects and
|
||||||
> re-gate is pending before G5 old-path deletion and production-only profiling.
|
> paperdoll, distant interaction, and correct `/ls` plus spell-recall
|
||||||
|
> materialization. G4 is complete. G5 cleanup is active: normal production no
|
||||||
|
> longer builds `InteriorEntityPartition`; mesh and attached-particle ownership
|
||||||
|
> come from the retained frame product. The exact production-only
|
||||||
|
> performance/lifecycle matrix remains.
|
||||||
> The historical exact cutover
|
> The historical exact cutover
|
||||||
> rollback remains
|
> rollback remains
|
||||||
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
> `git revert ef1d263337997bb030eadb7b8e71d73dc659907a`. Slice H is complete:
|
||||||
|
|
|
||||||
|
|
@ -108,11 +108,13 @@ publication, retirement, upload, warmup, or resource backlog. Capped/uncapped
|
||||||
CPU p99 is 34.7%/30.4% below Slice A and maximum frame allocation is
|
CPU p99 is 34.7%/30.4% below Slice A and maximum frame allocation is
|
||||||
84.7%/82.2% lower. The user explicitly approved Slices F–L, including both
|
84.7%/82.2% lower. The user explicitly approved Slices F–L, including both
|
||||||
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. Slice F and
|
the F/G ECS and Slice J gameplay-owner gates, on 2026-07-24. Slice F and
|
||||||
G0–G4 are implemented and G4's automated gate has zero mismatches. The portal
|
G0–G4 are complete: G4's automated gate has zero mismatches and the user
|
||||||
scheduling/private-viewport/ACE-revisit correction passed visually while the
|
accepted the corrected retained-path portal/revisit matrix on 2026-07-25.
|
||||||
retained draw cutover was temporarily reverted, so `20f9fadb` reapplies the
|
G5 cleanup is active; normal production now routes meshes and attached
|
||||||
cutover over those fixes for the pending connected retained-path re-gate. G5
|
particles from the retained frame product without building the old
|
||||||
deletion and production-only profiling remain gated on that pass. Slice H is
|
`InteriorEntityPartition`. Focused route tests, 3,825 App tests / 3 skips,
|
||||||
|
and 8,334 solution tests / 5 skips pass. The exact production-only
|
||||||
|
performance/lifecycle matrix remains before G5 closeout. Slice H is
|
||||||
complete: retained UI/frame work, exact bounded light selection, and
|
complete: retained UI/frame work, exact bounded light selection, and
|
||||||
pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
pooled/borrowed/direct network I/O pass 8,292 Release tests / 5 skips and the
|
||||||
seven-checkpoint connected lifecycle/reconnect gate. Slice I is active under
|
seven-checkpoint connected lifecycle/reconnect gate. Slice I is active under
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,9 @@
|
||||||
|
|
||||||
**Date:** 2026-07-24
|
**Date:** 2026-07-24
|
||||||
|
|
||||||
**Status:** Slices A–E complete. Slices F–L explicitly approved 2026-07-24;
|
**Status:** Slices A–F and H are complete; G0–G4 and I0 are complete. Slices
|
||||||
Slice F is active.
|
F–L were explicitly approved 2026-07-24; G5 production-matrix closeout is
|
||||||
|
active.
|
||||||
|
|
||||||
**Scope:** Reconcile and sequence the existing Modern Pipeline (`MP`) and
|
**Scope:** Reconcile and sequence the existing Modern Pipeline (`MP`) and
|
||||||
Linux/headless (`LH`) tracks using the 2026-07-24 connected performance audit.
|
Linux/headless (`LH`) tracks using the 2026-07-24 connected performance audit.
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,8 @@
|
||||||
|
|
||||||
**Date:** 2026-07-24
|
**Date:** 2026-07-24
|
||||||
|
|
||||||
**Status:** approved 2026-07-24; Slice F complete, G4 retained-path re-gate
|
**Status:** approved 2026-07-24; Slice F and G4 complete, G5 retained-route
|
||||||
active, G5 prepared but not unblocked
|
cleanup active
|
||||||
|
|
||||||
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
**Program:** `docs/plans/2026-07-24-modern-runtime-architecture.md`
|
||||||
|
|
||||||
|
|
@ -28,8 +28,8 @@ retail-faithful gameplay
|
||||||
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
| G1 — scene-query replacement | complete | Exact `e346f8bb` passed capped and uncapped nine-stop routes plus uncapped dense Arwic: 29,392 + 29,025 + 1,522 same-frame candidate comparisons, zero mismatches, equal counts/digests at every checkpoint, exact binary/source identity, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g1-scene-query-candidate-gate.md`. |
|
||||||
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
| G2 — packed dispatcher input | complete | Exact `f9829d5f` passed capped and uncapped nine-stop routes plus dense Arwic. Candidate order, packed input, classified output, and selection each completed 16,879 + 17,282 + 770 comparisons with zero mismatch; journal/rejection counts were zero and shutdown graceful. Evidence: `docs/research/2026-07-25-slice-g2-packed-dispatcher-gate.md`. |
|
||||||
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
| G3 — retained classification/storage | complete | Exact `6a026c5a` passed capped and uncapped nine-stop routes plus dense Arwic with 17,069 + 16,827 + 757 exact comparisons in every channel, zero mismatch, warm cross-frame reuse, and graceful shutdown. Evidence: `docs/research/2026-07-25-slice-g3-retained-classification-gate.md`. |
|
||||||
| G4 — production cutover | re-gate active | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. The portal correction was visually accepted while the cutover was temporarily reverted by `2c848d41`; that proves the correction but cannot accept the retained draw source. Commit `20f9fadb` reapplies the cutover over the correction and is the current connected visual re-gate. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
| G4 — production cutover | complete | Exact cutover `ef1d263337997bb030eadb7b8e71d73dc659907a` makes the retained frame product the production entity source at the existing five retail PView stages. Exact `03b10183` passed capped/uncapped nine-stop and uncapped dense-Arwic automation with 46,599 total product comparisons and zero mismatch. After the independent portal/private-view/revisit corrections, the user accepted the connected retained-path visual matrix on 2026-07-25: server objects and paperdoll survive travel, distant use works, `/ls` and spell recalls both materialize through the retail purple haze, and neither character pop nor recall tail remains. Evidence: `docs/research/2026-07-25-slice-g4-production-cutover-automated-gate.md` and `docs/research/2026-07-25-portal-regression-closeout.md`. |
|
||||||
| G5 | prepared | Old-route deletion and the production-only reference matrix remain blocked only on the connected `20f9fadb` retained-path re-gate. An allocation-free retained-route particle-owner selector and steady RDP sample are prepared without changing the running gate binary. |
|
| G5 | cleanup active | Normal production no longer builds `InteriorEntityPartition`: mesh and attached-particle ownership now consume the exact retained route ranges. The legacy partition remains only for standalone tests and explicitly enabled diagnostic/oracle probes. Focused route tests, 3,825 App tests / 3 skips, and 8,334 complete-solution tests / 5 skips pass; the exact production-only performance/lifecycle matrix remains. |
|
||||||
|
|
||||||
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
The exact pre-F/G runtime rollback anchor remains `e7d9d6fa`. F0 is
|
||||||
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
non-drawing diagnostic infrastructure and therefore is not a visual-cutover
|
||||||
|
|
@ -653,9 +653,11 @@ correction is recorded in
|
||||||
`docs/research/2026-07-25-portal-regression-closeout.md`. The user's subsequent
|
`docs/research/2026-07-25-portal-regression-closeout.md`. The user's subsequent
|
||||||
pass occurred while exact cutover `ef1d2633` was temporarily reverted by
|
pass occurred while exact cutover `ef1d2633` was temporarily reverted by
|
||||||
`2c848d41`, so it proves those corrections but not the retained draw source.
|
`2c848d41`, so it proves those corrections but not the retained draw source.
|
||||||
`20f9fadb` reapplies the cutover over the accepted corrections. G4 acceptance,
|
`20f9fadb` reapplies the cutover over the accepted corrections. The user
|
||||||
old-route deletion, and the G5 production-only matrix remain gated on the
|
accepted that connected retained path on 2026-07-25, including stable objects
|
||||||
connected retained-path recheck.
|
and paperdoll through travel, distant interaction, and the corrected `/ls` and
|
||||||
|
spell-recall materialization. G4 is complete. G5 removes the old production
|
||||||
|
partition route and runs the production-only matrix.
|
||||||
|
|
||||||
### G5 — Physical performance and lifecycle gate
|
### G5 — Physical performance and lifecycle gate
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,8 @@
|
||||||
# Modern runtime Slice I — flat collision assets and zero-allocation physics
|
# Modern runtime Slice I — flat collision assets and zero-allocation physics
|
||||||
|
|
||||||
**Status:** I0 COMPLETE — retail oracle, representative graph fixtures, and
|
**Status:** I0 COMPLETE — retail oracle, representative graph fixtures, and
|
||||||
four-mover allocation baselines fixed; accepted G5 closeout now precedes I1
|
four-mover allocation baselines fixed; active G5 production-matrix closeout
|
||||||
|
precedes I1
|
||||||
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice I
|
**Parent:** `2026-07-24-modern-runtime-architecture.md`, Slice I
|
||||||
**Purpose:** remove parsed DAT object graphs and steady collision allocations
|
**Purpose:** remove parsed DAT object graphs and steady collision allocations
|
||||||
without changing one retail collision decision, float, comparison, or traversal
|
without changing one retail collision decision, float, comparison, or traversal
|
||||||
|
|
|
||||||
|
|
@ -2,10 +2,13 @@
|
||||||
|
|
||||||
## Status
|
## Status
|
||||||
|
|
||||||
Work in progress. The retained-scene cutover is running at `20f9fadb`, but G5
|
Work in progress. The retained-scene connected visual re-gate passed on
|
||||||
old-route deletion remains blocked on its connected portal/revisit visual
|
2026-07-25. Normal production now consumes retained frame-product ranges for
|
||||||
re-gate. This note records production-only evidence gathered without enabling
|
both mesh submission and attached-particle ownership and does not build
|
||||||
the referee or lifecycle automation.
|
`InteriorEntityPartition`. The legacy partition is retained only for
|
||||||
|
standalone tests and explicitly enabled diagnostic/oracle probes. This note
|
||||||
|
records production-only evidence gathered without enabling the referee or
|
||||||
|
lifecycle automation; the exact post-removal route matrix is still pending.
|
||||||
|
|
||||||
## First steady RDP sample
|
## First steady RDP sample
|
||||||
|
|
||||||
|
|
@ -36,8 +39,14 @@ mandatory after the visual re-gate.
|
||||||
|
|
||||||
`RenderFrameRouteOwnerSelector` derives attached particle owner IDs from the
|
`RenderFrameRouteOwnerSelector` derives attached particle owner IDs from the
|
||||||
exact retained PView route ranges. It supports exact-route replace/union and
|
exact retained PView route ranges. It supports exact-route replace/union and
|
||||||
all-range exclusion without allocation after warmup. This removes the final
|
all-range exclusion without allocation after warmup. It now supplies the
|
||||||
particle-routing reason for normal production to build
|
landscape-static, outside-dynamic, look-in, cell-static, and dynamic-last
|
||||||
`InteriorEntityPartition`; it is not wired into the running visual-gate binary.
|
particle passes. `RetailPViewRenderer` builds the former partition only when
|
||||||
Four focused tests and the 3,821-pass / 3-skip App Release suite are green
|
the retained frame product is unavailable in a standalone test or when an
|
||||||
through an isolated output path.
|
explicit legacy comparison/probe is enabled. The production frame result
|
||||||
|
copies retained source and route counts before releasing its arena, so
|
||||||
|
diagnostics do not require the partition either.
|
||||||
|
|
||||||
|
The focused route suite, 3,825 App tests / 3 skips, Release solution build,
|
||||||
|
and 8,334 complete-solution tests / 5 skips are green. The production-only
|
||||||
|
reference matrix below remains the G5 closeout gate.
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,12 @@ internal sealed class RetailPViewParticleClassifications
|
||||||
foreach (WorldEntity owner in owners)
|
foreach (WorldEntity owner in owners)
|
||||||
_outdoor.Add(owner.Id);
|
_outdoor.Add(owner.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void ReplaceOutdoor(IReadOnlySet<uint> ownerIds)
|
||||||
|
{
|
||||||
|
_outdoor.Clear();
|
||||||
|
_outdoor.UnionWith(ownerIds);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
|
|
@ -452,7 +458,7 @@ internal sealed class RetailPViewPassExecutor :
|
||||||
animatedEntityIds: frame.AnimatedEntityIds);
|
animatedEntityIds: frame.AnimatedEntityIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
_particleClassifications.ReplaceOutdoor(context.ParticleOwners);
|
_particleClassifications.ReplaceOutdoor(context.ParticleOwnerIds);
|
||||||
|
|
||||||
_diagnostics.EmitOutStageParticles(
|
_diagnostics.EmitOutStageParticles(
|
||||||
RenderingDiagnostics.ProbeOutStageEnabled,
|
RenderingDiagnostics.ProbeOutStageEnabled,
|
||||||
|
|
@ -539,15 +545,14 @@ internal sealed class RetailPViewPassExecutor :
|
||||||
{
|
{
|
||||||
if (_particles is null
|
if (_particles is null
|
||||||
|| _particleRenderer is null
|
|| _particleRenderer is null
|
||||||
|| context.CellEntities.Count == 0)
|
|| context.ParticleOwnerIds.Count == 0)
|
||||||
{
|
{
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
HashSet<uint> visible = _particleClassifications.Visible;
|
HashSet<uint> visible = _particleClassifications.Visible;
|
||||||
visible.Clear();
|
visible.Clear();
|
||||||
foreach (WorldEntity entity in context.CellEntities)
|
visible.UnionWith(context.ParticleOwnerIds);
|
||||||
visible.Add(entity.Id);
|
|
||||||
if (visible.Count == 0)
|
if (visible.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
@ -562,15 +567,14 @@ internal sealed class RetailPViewPassExecutor :
|
||||||
|
|
||||||
public void DrawDynamicsParticles(
|
public void DrawDynamicsParticles(
|
||||||
RetailPViewFrameInput frame,
|
RetailPViewFrameInput frame,
|
||||||
IReadOnlyList<WorldEntity> survivors)
|
IReadOnlySet<uint> ownerIds)
|
||||||
{
|
{
|
||||||
if (_particles is null || _particleRenderer is null || survivors.Count == 0)
|
if (_particles is null || _particleRenderer is null || ownerIds.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
HashSet<uint> dynamics = _particleClassifications.Dynamics;
|
HashSet<uint> dynamics = _particleClassifications.Dynamics;
|
||||||
dynamics.Clear();
|
dynamics.Clear();
|
||||||
foreach (WorldEntity entity in survivors)
|
dynamics.UnionWith(ownerIds);
|
||||||
dynamics.Add(entity.Id);
|
|
||||||
if (dynamics.Count == 0)
|
if (dynamics.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@ public sealed class RetailPViewRenderer
|
||||||
private static readonly ClipViewSlice NoClipSlice =
|
private static readonly ClipViewSlice NoClipSlice =
|
||||||
new(0, new Vector4(-1f, -1f, 1f, 1f), Array.Empty<Vector4>());
|
new(0, new Vector4(-1f, -1f, 1f, 1f), Array.Empty<Vector4>());
|
||||||
private static readonly ClipViewSlice[] NoClipSlices = { NoClipSlice };
|
private static readonly ClipViewSlice[] NoClipSlices = { NoClipSlice };
|
||||||
|
private static readonly IReadOnlySet<uint> NoParticleOwners =
|
||||||
|
new HashSet<uint>();
|
||||||
|
|
||||||
private readonly HashSet<uint> _oneCell = new(1);
|
private readonly HashSet<uint> _oneCell = new(1);
|
||||||
// Shell-batch scratch: all of a pass's cells collected for ONE batched
|
// Shell-batch scratch: all of a pass's cells collected for ONE batched
|
||||||
|
|
@ -50,7 +52,9 @@ public sealed class RetailPViewRenderer
|
||||||
|
|
||||||
// #131/#132: the late landscape phase's scene-particle owner survivors
|
// #131/#132: the late landscape phase's scene-particle owner survivors
|
||||||
// (statics + outside-stage dynamics passing the slice cone).
|
// (statics + outside-stage dynamics passing the slice cone).
|
||||||
private readonly List<WorldEntity> _lateParticleOwnerScratch = new();
|
private readonly HashSet<uint> _lateParticleOwnerScratch = new();
|
||||||
|
private readonly HashSet<uint> _cellParticleOwnerScratch = new();
|
||||||
|
private readonly HashSet<uint> _dynamicParticleOwnerScratch = new();
|
||||||
|
|
||||||
// MP-Alloc (2026-07-05): the frame's entity partition (ByCell/OutdoorStatic/
|
// MP-Alloc (2026-07-05): the frame's entity partition (ByCell/OutdoorStatic/
|
||||||
// Dynamics), reused across frames instead of `new`ing a Result (a Dictionary
|
// Dynamics), reused across frames instead of `new`ing a Result (a Dictionary
|
||||||
|
|
@ -58,6 +62,8 @@ public sealed class RetailPViewRenderer
|
||||||
// call. See InteriorEntityPartition.Partition(Result, ...) — clears in
|
// call. See InteriorEntityPartition.Partition(Result, ...) — clears in
|
||||||
// place and reuses each cell's list across frames when the cell stays
|
// place and reuses each cell's list across frames when the cell stays
|
||||||
// visible.
|
// visible.
|
||||||
|
// Slice G4: this is now a diagnostic/fallback oracle only. Normal
|
||||||
|
// production consumes the retained RenderFrameView routes directly.
|
||||||
private readonly InteriorEntityPartition.Result _partitionResult = new();
|
private readonly InteriorEntityPartition.Result _partitionResult = new();
|
||||||
|
|
||||||
// MP-Alloc (2026-07-05): DrawInside's drawable-cell set, reused across
|
// MP-Alloc (2026-07-05): DrawInside's drawable-cell set, reused across
|
||||||
|
|
@ -174,20 +180,6 @@ public sealed class RetailPViewRenderer
|
||||||
|
|
||||||
passes.PrepareCellBatches(ctx, prepareCells);
|
passes.PrepareCellBatches(ctx, prepareCells);
|
||||||
|
|
||||||
InteriorEntityPartition.Partition(
|
|
||||||
_partitionResult,
|
|
||||||
prepareCells,
|
|
||||||
ctx.LandblockEntries,
|
|
||||||
_partitionObserver);
|
|
||||||
var partition = _partitionResult;
|
|
||||||
RetailPViewFrameResult result = _frameResultScratch.Reset(
|
|
||||||
pvFrame,
|
|
||||||
clipAssembly,
|
|
||||||
drawableCells,
|
|
||||||
partition);
|
|
||||||
|
|
||||||
passes.EmitDiagnostics(ctx, result);
|
|
||||||
|
|
||||||
// T1 (fused BR-2/3): retail's frame order — static world, then the
|
// T1 (fused BR-2/3): retail's frame order — static world, then the
|
||||||
// aperture depth writes, then interior cells WHOLE far→near, then
|
// aperture depth writes, then interior cells WHOLE far→near, then
|
||||||
// per-cell statics, then ALL dynamics last (retail draws objects after
|
// per-cell statics, then ALL dynamics last (retail draws objects after
|
||||||
|
|
@ -218,6 +210,51 @@ public sealed class RetailPViewRenderer
|
||||||
_candidateObserver?.BeginPViewFrame();
|
_candidateObserver?.BeginPViewFrame();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
InteriorEntityPartition.Result? partition = null;
|
||||||
|
if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled)
|
||||||
|
{
|
||||||
|
InteriorEntityPartition.Partition(
|
||||||
|
_partitionResult,
|
||||||
|
prepareCells,
|
||||||
|
ctx.LandblockEntries,
|
||||||
|
_partitionObserver);
|
||||||
|
partition = _partitionResult;
|
||||||
|
}
|
||||||
|
|
||||||
|
RenderFrameDiagnosticCounts counts = frameViewBorrowed
|
||||||
|
? frameView.DiagnosticCounts
|
||||||
|
: LegacyDiagnosticCounts(partition!);
|
||||||
|
RenderProjectionCounts sourceCounts = frameViewBorrowed
|
||||||
|
? frameView.SourceDigest.Counts
|
||||||
|
: LegacySourceCounts(partition!);
|
||||||
|
RetailPViewFrameResult result = _frameResultScratch.Reset(
|
||||||
|
pvFrame,
|
||||||
|
clipAssembly,
|
||||||
|
drawableCells,
|
||||||
|
counts,
|
||||||
|
sourceCounts,
|
||||||
|
partition);
|
||||||
|
passes.EmitDiagnostics(ctx, result);
|
||||||
|
|
||||||
// #118: stage assignment for dynamics under an INTERIOR root. Retail
|
// #118: stage assignment for dynamics under an INTERIOR root. Retail
|
||||||
// draws the OUTSIDE world's objects inside the landscape stage —
|
// draws the OUTSIDE world's objects inside the landscape stage —
|
||||||
// PView::DrawCells runs LScape::draw FIRST (pc:432719), then the gated
|
// PView::DrawCells runs LScape::draw FIRST (pc:432719), then the gated
|
||||||
|
|
@ -235,7 +272,7 @@ public sealed class RetailPViewRenderer
|
||||||
// z-buffered equivalent of retail's painter-ordered outdoor pass (the
|
// z-buffered equivalent of retail's painter-ordered outdoor pass (the
|
||||||
// BR-2 punch-after-dynamics lesson, reverted 88be519).
|
// BR-2 punch-after-dynamics lesson, reverted 88be519).
|
||||||
_outsideStageDynamics.Clear();
|
_outsideStageDynamics.Clear();
|
||||||
if (!ctx.RootCell.IsOutdoorNode)
|
if (partition is not null && !ctx.RootCell.IsOutdoorNode)
|
||||||
{
|
{
|
||||||
foreach (var e in partition.Dynamics)
|
foreach (var e in partition.Dynamics)
|
||||||
{
|
{
|
||||||
|
|
@ -245,22 +282,6 @@ public sealed class RetailPViewRenderer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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(
|
DrawLandscapeThroughOutsideView(
|
||||||
ctx,
|
ctx,
|
||||||
passes,
|
passes,
|
||||||
|
|
@ -441,7 +462,7 @@ public sealed class RetailPViewRenderer
|
||||||
RetailPViewFrameInput ctx,
|
RetailPViewFrameInput ctx,
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result? partition,
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
||||||
in RenderFrameView frameView)
|
in RenderFrameView frameView)
|
||||||
{
|
{
|
||||||
|
|
@ -466,7 +487,7 @@ public sealed class RetailPViewRenderer
|
||||||
0,
|
0,
|
||||||
new Vector4(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY),
|
new Vector4(poly.MinX, poly.MinY, poly.MaxX, poly.MaxY),
|
||||||
cps.PlaneArray),
|
cps.PlaneArray),
|
||||||
Array.Empty<WorldEntity>()));
|
NoParticleOwners));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -494,8 +515,11 @@ public sealed class RetailPViewRenderer
|
||||||
passes.DrawTransparentCellShells(_oneCell);
|
passes.DrawTransparentCellShells(_oneCell);
|
||||||
|
|
||||||
_cellStaticScratch.Clear();
|
_cellStaticScratch.Clear();
|
||||||
if (partition.ByCell.TryGetValue(cellId, out var bucket))
|
if (partition is not null
|
||||||
|
&& partition.ByCell.TryGetValue(cellId, out var bucket))
|
||||||
|
{
|
||||||
_cellStaticScratch.AddRange(bucket);
|
_cellStaticScratch.AddRange(bucket);
|
||||||
|
}
|
||||||
|
|
||||||
// #131 ROOT CAUSE: DYNAMICS living in a look-in cell (the
|
// #131 ROOT CAUSE: DYNAMICS living in a look-in cell (the
|
||||||
// Holtburg hall-porch PORTAL, pCell 0xA9B4017A) draw NOWHERE
|
// Holtburg hall-porch PORTAL, pCell 0xA9B4017A) draw NOWHERE
|
||||||
|
|
@ -510,11 +534,31 @@ public sealed class RetailPViewRenderer
|
||||||
// cell is absent from the main cone), and their emitters ride
|
// cell is absent from the main cone), and their emitters ride
|
||||||
// the DrawCellParticles call below, not DrawDynamicsParticles
|
// the DrawCellParticles call below, not DrawDynamicsParticles
|
||||||
// (which only sees dynamics-last cone survivors).
|
// (which only sees dynamics-last cone survivors).
|
||||||
foreach (var e in partition.Dynamics)
|
if (partition is not null)
|
||||||
if (e.ParentCellId == cellId)
|
{
|
||||||
_cellStaticScratch.Add(e);
|
foreach (var e in partition.Dynamics)
|
||||||
|
if (e.ParentCellId == cellId)
|
||||||
|
_cellStaticScratch.Add(e);
|
||||||
|
}
|
||||||
|
|
||||||
if (_cellStaticScratch.Count > 0)
|
if (frameEntityPasses is not null)
|
||||||
|
{
|
||||||
|
RenderFrameRouteOwnerSelector.Replace(
|
||||||
|
_cellParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.LookInObject,
|
||||||
|
i,
|
||||||
|
cellId);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReplaceOwnerIds(
|
||||||
|
_cellParticleOwnerScratch,
|
||||||
|
_cellStaticScratch);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameEntityPasses is not null
|
||||||
|
|| _cellStaticScratch.Count > 0)
|
||||||
{
|
{
|
||||||
_candidateObserver?.ObservePViewBucket(
|
_candidateObserver?.ObservePViewBucket(
|
||||||
CurrentRenderPViewRoute.LookInObject,
|
CurrentRenderPViewRoute.LookInObject,
|
||||||
|
|
@ -536,7 +580,7 @@ public sealed class RetailPViewRenderer
|
||||||
// nested DrawCells draws objects WITH their emitters.
|
// nested DrawCells draws objects WITH their emitters.
|
||||||
foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId))
|
foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId))
|
||||||
passes.DrawCellParticles(ctx, new RetailPViewCellSliceContext(
|
passes.DrawCellParticles(ctx, new RetailPViewCellSliceContext(
|
||||||
cellId, slice, _cellStaticScratch));
|
cellId, slice, _cellParticleOwnerScratch));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -546,7 +590,7 @@ public sealed class RetailPViewRenderer
|
||||||
RetailPViewFrameInput ctx,
|
RetailPViewFrameInput ctx,
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result? partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone,
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
||||||
in RenderFrameView frameView)
|
in RenderFrameView frameView)
|
||||||
|
|
@ -580,11 +624,19 @@ public sealed class RetailPViewRenderer
|
||||||
passes.EmitClipRouteProbe(clipAssembly, slice, probeSliceIndex);
|
passes.EmitClipRouteProbe(clipAssembly, slice, probeSliceIndex);
|
||||||
|
|
||||||
_outdoorStaticScratch.Clear();
|
_outdoorStaticScratch.Clear();
|
||||||
foreach (var e in partition.OutdoorStatic)
|
if (partition is not null)
|
||||||
{
|
{
|
||||||
EntitySphere(e, out var c, out float r);
|
foreach (var e in partition.OutdoorStatic)
|
||||||
if (viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r))
|
{
|
||||||
_outdoorStaticScratch.Add(e);
|
EntitySphere(e, out var c, out float r);
|
||||||
|
if (viewcone.SphereVisibleInOutsideSlice(
|
||||||
|
probeSliceIndex,
|
||||||
|
c,
|
||||||
|
r))
|
||||||
|
{
|
||||||
|
_outdoorStaticScratch.Add(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
_candidateObserver?.ObservePViewBucket(
|
_candidateObserver?.ObservePViewBucket(
|
||||||
CurrentRenderPViewRoute.LandscapeOutdoorStatic,
|
CurrentRenderPViewRoute.LandscapeOutdoorStatic,
|
||||||
|
|
@ -636,21 +688,27 @@ public sealed class RetailPViewRenderer
|
||||||
|
|
||||||
_outdoorStaticScratch.Clear(); // late: dynamics survivors
|
_outdoorStaticScratch.Clear(); // late: dynamics survivors
|
||||||
_lateParticleOwnerScratch.Clear(); // late: statics + dynamics survivors
|
_lateParticleOwnerScratch.Clear(); // late: statics + dynamics survivors
|
||||||
foreach (var e in partition.OutdoorStatic)
|
if (partition is not null)
|
||||||
{
|
{
|
||||||
EntitySphere(e, out var c, out float r);
|
foreach (var e in partition.OutdoorStatic)
|
||||||
bool ownerPass = viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r);
|
{
|
||||||
if (ownerPass)
|
EntitySphere(e, out var c, out float r);
|
||||||
_lateParticleOwnerScratch.Add(e);
|
bool ownerPass = viewcone.SphereVisibleInOutsideSlice(
|
||||||
|
probeSliceIndex,
|
||||||
|
c,
|
||||||
|
r);
|
||||||
|
if (ownerPass)
|
||||||
|
_lateParticleOwnerScratch.Add(e.Id);
|
||||||
// #131 owner watchlist (throwaway): ACDREAM_DUMP_ENTITY ids
|
// #131 owner watchlist (throwaway): ACDREAM_DUMP_ENTITY ids
|
||||||
// double as an ENTITY-id watchlist here — one line per watched
|
// double as an ENTITY-id watchlist here — one line per watched
|
||||||
// outdoor-static owner per CHANGE of its cone verdict.
|
// outdoor-static owner per CHANGE of its cone verdict.
|
||||||
passes.EmitOutStageOwner(
|
passes.EmitOutStageOwner(
|
||||||
e,
|
e,
|
||||||
c,
|
c,
|
||||||
r,
|
r,
|
||||||
probeSliceIndex,
|
probeSliceIndex,
|
||||||
ownerPass);
|
ownerPass);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
foreach (var e in _outsideStageDynamics)
|
foreach (var e in _outsideStageDynamics)
|
||||||
{
|
{
|
||||||
|
|
@ -658,9 +716,24 @@ public sealed class RetailPViewRenderer
|
||||||
if (viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r))
|
if (viewcone.SphereVisibleInOutsideSlice(probeSliceIndex, c, r))
|
||||||
{
|
{
|
||||||
_outdoorStaticScratch.Add(e);
|
_outdoorStaticScratch.Add(e);
|
||||||
_lateParticleOwnerScratch.Add(e);
|
_lateParticleOwnerScratch.Add(e.Id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (frameEntityPasses is not null)
|
||||||
|
{
|
||||||
|
RenderFrameRouteOwnerSelector.Replace(
|
||||||
|
_lateParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
||||||
|
probeSliceIndex,
|
||||||
|
0);
|
||||||
|
RenderFrameRouteOwnerSelector.Union(
|
||||||
|
_lateParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
||||||
|
probeSliceIndex,
|
||||||
|
0);
|
||||||
|
}
|
||||||
passes.EmitOutStageRouting(
|
passes.EmitOutStageRouting(
|
||||||
probeSliceIndex,
|
probeSliceIndex,
|
||||||
_outsideStageDynamics,
|
_outsideStageDynamics,
|
||||||
|
|
@ -735,7 +808,10 @@ public sealed class RetailPViewRenderer
|
||||||
foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId))
|
foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId))
|
||||||
passes.DrawExitPortalMask(
|
passes.DrawExitPortalMask(
|
||||||
ctx,
|
ctx,
|
||||||
new RetailPViewCellSliceContext(cellId, slice, Array.Empty<WorldEntity>()));
|
new RetailPViewCellSliceContext(
|
||||||
|
cellId,
|
||||||
|
slice,
|
||||||
|
NoParticleOwners));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -795,13 +871,51 @@ public sealed class RetailPViewRenderer
|
||||||
private void DrawDynamicsLast(
|
private void DrawDynamicsLast(
|
||||||
RetailPViewFrameInput ctx,
|
RetailPViewFrameInput ctx,
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result? partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone,
|
||||||
bool rootIsOutdoor,
|
bool rootIsOutdoor,
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
||||||
in RenderFrameView frameView)
|
in RenderFrameView frameView)
|
||||||
{
|
{
|
||||||
if (partition.Dynamics.Count == 0)
|
if (partition is null)
|
||||||
|
{
|
||||||
|
RenderFrameRouteOwnerSelector.Replace(
|
||||||
|
_dynamicParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.DynamicLast,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
|
DrawEntityRouteOrLegacy(
|
||||||
|
ctx,
|
||||||
|
passes,
|
||||||
|
frameEntityPasses,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.DynamicLast,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Array.Empty<WorldEntity>(),
|
||||||
|
visibleCellIds: null);
|
||||||
|
|
||||||
|
// An owner routed through any pre-clear outside slice already had
|
||||||
|
// its alpha particles drawn there. Meshes may be submitted in both
|
||||||
|
// stages, but particles must be emitted exactly once.
|
||||||
|
RenderFrameRouteOwnerSelector.ExceptRoute(
|
||||||
|
_dynamicParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.LandscapeOutsideDynamic);
|
||||||
|
if (_dynamicParticleOwnerScratch.Count > 0)
|
||||||
|
{
|
||||||
|
passes.DrawDynamicsParticles(
|
||||||
|
ctx,
|
||||||
|
_dynamicParticleOwnerScratch);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (partition.Dynamics.Count == 0
|
||||||
|
&& frameEntityPasses is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_dynamicsScratch.Clear();
|
_dynamicsScratch.Clear();
|
||||||
|
|
@ -837,14 +951,18 @@ public sealed class RetailPViewRenderer
|
||||||
_dynamicsScratch.Add(e);
|
_dynamicsScratch.Add(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_dynamicsScratch.Count == 0)
|
if (_dynamicsScratch.Count == 0
|
||||||
|
&& frameEntityPasses is null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
_candidateObserver?.ObservePViewBucket(
|
if (_dynamicsScratch.Count > 0)
|
||||||
CurrentRenderPViewRoute.DynamicLast,
|
{
|
||||||
0,
|
_candidateObserver?.ObservePViewBucket(
|
||||||
0,
|
CurrentRenderPViewRoute.DynamicLast,
|
||||||
_dynamicsScratch);
|
0,
|
||||||
|
0,
|
||||||
|
_dynamicsScratch);
|
||||||
|
}
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
DrawEntityRouteOrLegacy(
|
DrawEntityRouteOrLegacy(
|
||||||
ctx,
|
ctx,
|
||||||
|
|
@ -867,12 +985,28 @@ public sealed class RetailPViewRenderer
|
||||||
// portals went invisible. Outside-stage dynamics are excluded here:
|
// portals went invisible. Outside-stage dynamics are excluded here:
|
||||||
// their emitters already drew in the landscape slice (alpha-blended
|
// their emitters already drew in the landscape slice (alpha-blended
|
||||||
// particles must not double-draw, unlike the depth-idempotent meshes).
|
// particles must not double-draw, unlike the depth-idempotent meshes).
|
||||||
_dynamicsParticleScratch.Clear();
|
if (frameEntityPasses is not null)
|
||||||
foreach (var e in _dynamicsScratch)
|
{
|
||||||
if (!_outsideStageDynamics.Contains(e))
|
RenderFrameRouteOwnerSelector.Replace(
|
||||||
_dynamicsParticleScratch.Add(e);
|
_dynamicParticleOwnerScratch,
|
||||||
if (_dynamicsParticleScratch.Count > 0)
|
in frameView,
|
||||||
passes.DrawDynamicsParticles(ctx, _dynamicsParticleScratch);
|
RenderFrameCandidateRoute.DynamicLast,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
RenderFrameRouteOwnerSelector.ExceptRoute(
|
||||||
|
_dynamicParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.LandscapeOutsideDynamic);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_dynamicParticleOwnerScratch.Clear();
|
||||||
|
foreach (var e in _dynamicsScratch)
|
||||||
|
if (!_outsideStageDynamics.Contains(e))
|
||||||
|
_dynamicParticleOwnerScratch.Add(e.Id);
|
||||||
|
}
|
||||||
|
if (_dynamicParticleOwnerScratch.Count > 0)
|
||||||
|
passes.DrawDynamicsParticles(ctx, _dynamicParticleOwnerScratch);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void DrawCellObjectLists(
|
private void DrawCellObjectLists(
|
||||||
|
|
@ -881,11 +1015,40 @@ public sealed class RetailPViewRenderer
|
||||||
PortalVisibilityFrame pvFrame,
|
PortalVisibilityFrame pvFrame,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
HashSet<uint> drawableCells,
|
HashSet<uint> drawableCells,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result? partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone,
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
||||||
in RenderFrameView frameView)
|
in RenderFrameView frameView)
|
||||||
{
|
{
|
||||||
|
if (partition is null)
|
||||||
|
{
|
||||||
|
RenderFrameRouteOwnerSelector.Replace(
|
||||||
|
_cellParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.CellStatic,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
|
||||||
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
|
DrawEntityRouteOrLegacy(
|
||||||
|
ctx,
|
||||||
|
passes,
|
||||||
|
frameEntityPasses,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.CellStatic,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
Array.Empty<WorldEntity>(),
|
||||||
|
visibleCellIds: null);
|
||||||
|
passes.DrawCellParticles(
|
||||||
|
ctx,
|
||||||
|
new RetailPViewCellSliceContext(
|
||||||
|
0u,
|
||||||
|
NoClipSlice,
|
||||||
|
_cellParticleOwnerScratch));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
// T1: per-cell STATIC object lists only (dat-baked 0x40 statics) —
|
// T1: per-cell STATIC object lists only (dat-baked 0x40 statics) —
|
||||||
// dynamics moved to DrawDynamicsLast. Far→near with the cells, after
|
// dynamics moved to DrawDynamicsLast. Far→near with the cells, after
|
||||||
// the shells (retail DrawCells epilogue: PortalList = cell's views →
|
// the shells (retail DrawCells epilogue: PortalList = cell's views →
|
||||||
|
|
@ -940,7 +1103,8 @@ public sealed class RetailPViewRenderer
|
||||||
// draw in DrawDynamicsLast. T3 (BR-5): each static was sphere-tested against
|
// draw in DrawDynamicsLast. T3 (BR-5): each static was sphere-tested against
|
||||||
// ITS cell's views above (the statics-through-walls fix is preserved by the
|
// ITS cell's views above (the statics-through-walls fix is preserved by the
|
||||||
// cull; only the draw is batched).
|
// cull; only the draw is batched).
|
||||||
if (_allCellStatics.Count > 0)
|
if (frameEntityPasses is not null
|
||||||
|
|| _allCellStatics.Count > 0)
|
||||||
{
|
{
|
||||||
_candidateObserver?.ObservePViewBucket(
|
_candidateObserver?.ObservePViewBucket(
|
||||||
CurrentRenderPViewRoute.CellStatic,
|
CurrentRenderPViewRoute.CellStatic,
|
||||||
|
|
@ -974,10 +1138,31 @@ public sealed class RetailPViewRenderer
|
||||||
// the buffer (the statics-before-particles order). cellId/slice are unused
|
// the buffer (the statics-before-particles order). cellId/slice are unused
|
||||||
// by the particle pass — pass NoClipSlice + the union owner list. This also
|
// by the particle pass — pass NoClipSlice + the union owner list. This also
|
||||||
// drops the per-cell BuildDrawList allocations (N → 1).
|
// drops the per-cell BuildDrawList allocations (N → 1).
|
||||||
if (_allCellStatics.Count > 0)
|
if (frameEntityPasses is not null
|
||||||
|
|| _allCellStatics.Count > 0)
|
||||||
|
{
|
||||||
|
if (frameEntityPasses is not null)
|
||||||
|
{
|
||||||
|
RenderFrameRouteOwnerSelector.Replace(
|
||||||
|
_cellParticleOwnerScratch,
|
||||||
|
in frameView,
|
||||||
|
RenderFrameCandidateRoute.CellStatic,
|
||||||
|
0,
|
||||||
|
0);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
ReplaceOwnerIds(
|
||||||
|
_cellParticleOwnerScratch,
|
||||||
|
_allCellStatics);
|
||||||
|
}
|
||||||
passes.DrawCellParticles(
|
passes.DrawCellParticles(
|
||||||
ctx,
|
ctx,
|
||||||
new RetailPViewCellSliceContext(0u, NoClipSlice, _allCellStatics));
|
new RetailPViewCellSliceContext(
|
||||||
|
0u,
|
||||||
|
NoClipSlice,
|
||||||
|
_cellParticleOwnerScratch));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawEntityRouteOrLegacy(
|
private static void DrawEntityRouteOrLegacy(
|
||||||
|
|
@ -1016,9 +1201,6 @@ public sealed class RetailPViewRenderer
|
||||||
// #118: dynamics assigned to the OUTSIDE stage this frame (interior roots
|
// #118: dynamics assigned to the OUTSIDE stage this frame (interior roots
|
||||||
// only) — outdoor-classified + exit-portal straddlers. Cleared per frame.
|
// only) — outdoor-classified + exit-portal straddlers. Cleared per frame.
|
||||||
private readonly List<WorldEntity> _outsideStageDynamics = new();
|
private readonly List<WorldEntity> _outsideStageDynamics = new();
|
||||||
// #121: cone-surviving dynamics whose emitters draw in the dynamics
|
|
||||||
// particle pass (survivors minus outside-stage). Cleared per use.
|
|
||||||
private readonly List<WorldEntity> _dynamicsParticleScratch = new();
|
|
||||||
// Dense-town FPS iteration-1 (cellobject batching): all visible cells'
|
// Dense-town FPS iteration-1 (cellobject batching): all visible cells'
|
||||||
// viewcone-surviving statics accumulated for ONE batched DrawEntityBucket,
|
// viewcone-surviving statics accumulated for ONE batched DrawEntityBucket,
|
||||||
// plus the union of their cell ids for the dispatcher's visibleCellIds gate.
|
// plus the union of their cell ids for the dispatcher's visibleCellIds gate.
|
||||||
|
|
@ -1026,6 +1208,70 @@ public sealed class RetailPViewRenderer
|
||||||
private readonly List<WorldEntity> _allCellStatics = new();
|
private readonly List<WorldEntity> _allCellStatics = new();
|
||||||
private readonly HashSet<uint> _cellObjCells = new();
|
private readonly HashSet<uint> _cellObjCells = new();
|
||||||
|
|
||||||
|
private bool LegacyPartitionDiagnosticsEnabled =>
|
||||||
|
_partitionObserver is not null
|
||||||
|
|| AcDream.Core.Rendering.RenderingDiagnostics.ProbeOutStageEnabled
|
||||||
|
|| AcDream.Core.Rendering.RenderingDiagnostics.ProbePhantomEnabled
|
||||||
|
|| AcDream.Core.Rendering.RenderingDiagnostics.ProbeFlapEnabled
|
||||||
|
|| AcDream.App.Streaming.EntityVanishProbe.Enabled;
|
||||||
|
|
||||||
|
internal static RenderFrameDiagnosticCounts LegacyDiagnosticCounts(
|
||||||
|
InteriorEntityPartition.Result partition)
|
||||||
|
{
|
||||||
|
int cellStaticCount = 0;
|
||||||
|
foreach (List<WorldEntity> bucket in partition.ByCell.Values)
|
||||||
|
cellStaticCount = checked(cellStaticCount + bucket.Count);
|
||||||
|
|
||||||
|
return new RenderFrameDiagnosticCounts(
|
||||||
|
partition.OutdoorStatic.Count,
|
||||||
|
cellStaticCount,
|
||||||
|
partition.Dynamics.Count,
|
||||||
|
TransformCount: 0,
|
||||||
|
OpaqueClassificationCount: 0,
|
||||||
|
AlphaClassificationCount: 0,
|
||||||
|
LightSetCount: 0,
|
||||||
|
SelectionPartCount: 0,
|
||||||
|
RouteCandidateCount:
|
||||||
|
checked(
|
||||||
|
partition.OutdoorStatic.Count
|
||||||
|
+ cellStaticCount
|
||||||
|
+ partition.Dynamics.Count),
|
||||||
|
EntityCandidateCount: 0,
|
||||||
|
MeshPartCount: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal static RenderProjectionCounts LegacySourceCounts(
|
||||||
|
InteriorEntityPartition.Result partition)
|
||||||
|
{
|
||||||
|
int cellStaticCount = 0;
|
||||||
|
foreach (List<WorldEntity> bucket in partition.ByCell.Values)
|
||||||
|
cellStaticCount = checked(cellStaticCount + bucket.Count);
|
||||||
|
int total = checked(
|
||||||
|
partition.OutdoorStatic.Count
|
||||||
|
+ cellStaticCount
|
||||||
|
+ partition.Dynamics.Count);
|
||||||
|
return new RenderProjectionCounts(
|
||||||
|
total,
|
||||||
|
partition.OutdoorStatic.Count,
|
||||||
|
cellStaticCount,
|
||||||
|
partition.Dynamics.Count,
|
||||||
|
ActiveAnimatedStatic: 0,
|
||||||
|
EquippedChild: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void ReplaceOwnerIds(
|
||||||
|
HashSet<uint> destination,
|
||||||
|
IReadOnlyList<WorldEntity> entities)
|
||||||
|
{
|
||||||
|
destination.Clear();
|
||||||
|
for (int index = 0; index < entities.Count; index++)
|
||||||
|
{
|
||||||
|
uint localEntityId = entities[index].Id;
|
||||||
|
if (localEntityId != 0)
|
||||||
|
destination.Add(localEntityId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// #118 stage assignment for a dynamic under an INTERIOR root: does it draw
|
/// #118 stage assignment for a dynamic under an INTERIOR root: does it draw
|
||||||
/// in the OUTSIDE (landscape) stage — before the gated depth clear and the
|
/// in the OUTSIDE (landscape) stage — before the gated depth clear and the
|
||||||
|
|
@ -1153,7 +1399,7 @@ public interface IRetailPViewPassExecutor
|
||||||
void DrawUnattachedSceneParticles(RetailPViewFrameInput frame);
|
void DrawUnattachedSceneParticles(RetailPViewFrameInput frame);
|
||||||
void FlushLandscapeAlpha();
|
void FlushLandscapeAlpha();
|
||||||
void DrawCellParticles(RetailPViewFrameInput frame, RetailPViewCellSliceContext context);
|
void DrawCellParticles(RetailPViewFrameInput frame, RetailPViewCellSliceContext context);
|
||||||
void DrawDynamicsParticles(RetailPViewFrameInput frame, IReadOnlyList<WorldEntity> survivors);
|
void DrawDynamicsParticles(RetailPViewFrameInput frame, IReadOnlySet<uint> ownerIds);
|
||||||
void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result);
|
void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1428,20 +1674,42 @@ public sealed class RetailPViewFrameResult
|
||||||
public PortalVisibilityFrame PortalFrame { get; private set; } = null!;
|
public PortalVisibilityFrame PortalFrame { get; private set; } = null!;
|
||||||
public ClipFrameAssembly ClipAssembly { get; private set; } = null!;
|
public ClipFrameAssembly ClipAssembly { get; private set; } = null!;
|
||||||
public HashSet<uint> DrawableCells { get; private set; } = null!;
|
public HashSet<uint> DrawableCells { get; private set; } = null!;
|
||||||
public InteriorEntityPartition.Result Partition { get; private set; } = null!;
|
internal RenderFrameDiagnosticCounts DiagnosticCounts { get; private set; }
|
||||||
|
internal RenderProjectionCounts SourceCounts { get; private set; }
|
||||||
|
internal InteriorEntityPartition.Result? DiagnosticPartition
|
||||||
|
{ get; private set; }
|
||||||
|
|
||||||
internal RetailPViewFrameResult Reset(
|
internal RetailPViewFrameResult Reset(
|
||||||
PortalVisibilityFrame portalFrame,
|
PortalVisibilityFrame portalFrame,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
HashSet<uint> drawableCells,
|
HashSet<uint> drawableCells,
|
||||||
InteriorEntityPartition.Result partition)
|
RenderFrameDiagnosticCounts diagnosticCounts,
|
||||||
|
RenderProjectionCounts sourceCounts,
|
||||||
|
InteriorEntityPartition.Result? diagnosticPartition)
|
||||||
{
|
{
|
||||||
PortalFrame = portalFrame;
|
PortalFrame = portalFrame;
|
||||||
ClipAssembly = clipAssembly;
|
ClipAssembly = clipAssembly;
|
||||||
DrawableCells = drawableCells;
|
DrawableCells = drawableCells;
|
||||||
Partition = partition;
|
DiagnosticCounts = diagnosticCounts;
|
||||||
|
SourceCounts = sourceCounts;
|
||||||
|
DiagnosticPartition = diagnosticPartition;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal RetailPViewFrameResult Reset(
|
||||||
|
PortalVisibilityFrame portalFrame,
|
||||||
|
ClipFrameAssembly clipAssembly,
|
||||||
|
HashSet<uint> drawableCells,
|
||||||
|
InteriorEntityPartition.Result diagnosticPartition) =>
|
||||||
|
Reset(
|
||||||
|
portalFrame,
|
||||||
|
clipAssembly,
|
||||||
|
drawableCells,
|
||||||
|
RetailPViewRenderer.LegacyDiagnosticCounts(
|
||||||
|
diagnosticPartition),
|
||||||
|
RetailPViewRenderer.LegacySourceCounts(
|
||||||
|
diagnosticPartition),
|
||||||
|
diagnosticPartition);
|
||||||
}
|
}
|
||||||
|
|
||||||
public readonly record struct RetailPViewLandscapeSliceContext(
|
public readonly record struct RetailPViewLandscapeSliceContext(
|
||||||
|
|
@ -1457,7 +1725,7 @@ public readonly record struct RetailPViewLandscapeSliceContext(
|
||||||
public readonly record struct RetailPViewLandscapeLateSliceContext(
|
public readonly record struct RetailPViewLandscapeLateSliceContext(
|
||||||
ClipViewSlice Slice,
|
ClipViewSlice Slice,
|
||||||
IReadOnlyList<WorldEntity> Dynamics,
|
IReadOnlyList<WorldEntity> Dynamics,
|
||||||
IReadOnlyList<WorldEntity> ParticleOwners)
|
IReadOnlySet<uint> ParticleOwnerIds)
|
||||||
{
|
{
|
||||||
internal RenderFrameEntityDrawRequest? EntityDraw { get; init; }
|
internal RenderFrameEntityDrawRequest? EntityDraw { get; init; }
|
||||||
}
|
}
|
||||||
|
|
@ -1465,4 +1733,4 @@ public readonly record struct RetailPViewLandscapeLateSliceContext(
|
||||||
public readonly record struct RetailPViewCellSliceContext(
|
public readonly record struct RetailPViewCellSliceContext(
|
||||||
uint CellId,
|
uint CellId,
|
||||||
ClipViewSlice Slice,
|
ClipViewSlice Slice,
|
||||||
IReadOnlyList<WorldEntity> CellEntities);
|
IReadOnlySet<uint> ParticleOwnerIds);
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,7 @@ internal sealed class WorldSceneDiagnosticsController : IWorldSceneDiagnostics
|
||||||
pviewResult?.PortalFrame,
|
pviewResult?.PortalFrame,
|
||||||
pviewResult?.ClipAssembly,
|
pviewResult?.ClipAssembly,
|
||||||
pviewResult?.DrawableCells,
|
pviewResult?.DrawableCells,
|
||||||
pviewResult?.Partition,
|
pviewResult?.DiagnosticPartition,
|
||||||
exteriorPortalFrame: null,
|
exteriorPortalFrame: null,
|
||||||
exteriorClipAssembly: null,
|
exteriorClipAssembly: null,
|
||||||
exteriorDrawableCells: null,
|
exteriorDrawableCells: null,
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
using AcDream.App.Rendering.Scene;
|
||||||
using AcDream.App.Rendering.Selection;
|
using AcDream.App.Rendering.Selection;
|
||||||
using AcDream.App.Rendering.Vfx;
|
using AcDream.App.Rendering.Vfx;
|
||||||
using AcDream.App.Streaming;
|
using AcDream.App.Streaming;
|
||||||
|
|
@ -218,11 +219,15 @@ internal sealed class WorldSceneRenderer : IWorldSceneFramePhase
|
||||||
// outdoor roots whose PView executor does not issue an interior
|
// outdoor roots whose PView executor does not issue an interior
|
||||||
// depth clear.
|
// depth clear.
|
||||||
depthClear = hasOutsideSlice;
|
depthClear = hasOutsideSlice;
|
||||||
if (pviewResult.Partition.ByCell.Count > 0 || hasOutsideSlice)
|
RenderProjectionCounts sourceCounts =
|
||||||
|
pviewResult.SourceCounts;
|
||||||
|
if (sourceCounts.IndoorCellStatic > 0 || hasOutsideSlice)
|
||||||
sceneParticles = "pviewScoped";
|
sceneParticles = "pviewScoped";
|
||||||
outdoorSceneryDrawn = pviewResult.Partition.OutdoorStatic.Count > 0
|
outdoorSceneryDrawn =
|
||||||
|
sourceCounts.OutdoorStatic > 0
|
||||||
&& hasOutsideSlice;
|
&& hasOutsideSlice;
|
||||||
liveDynamicDrawnCount = pviewResult.Partition.Dynamics.Count;
|
liveDynamicDrawnCount =
|
||||||
|
sourceCounts.LiveDynamicRoot;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -310,6 +310,34 @@ public sealed class RetailPViewPassExecutorTests
|
||||||
"passes.DrawCellParticles(ctx");
|
"passes.DrawCellParticles(ctx");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public void Production_uses_retained_routes_without_rebuilding_legacy_partition()
|
||||||
|
{
|
||||||
|
string source = RendererSource();
|
||||||
|
string drawInside = MethodBody(
|
||||||
|
source,
|
||||||
|
"public RetailPViewFrameResult DrawInside(");
|
||||||
|
|
||||||
|
Assert.Contains(
|
||||||
|
"if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled)",
|
||||||
|
drawInside,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
AssertAppearsInOrder(
|
||||||
|
drawInside,
|
||||||
|
"_sceneFrameProduct.BuildAndBorrow(",
|
||||||
|
"if (_sceneFrameProduct is null || LegacyPartitionDiagnosticsEnabled)",
|
||||||
|
"InteriorEntityPartition.Partition(");
|
||||||
|
|
||||||
|
Assert.Contains(
|
||||||
|
"RenderFrameRouteOwnerSelector.Replace(",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
Assert.Contains(
|
||||||
|
"RenderFrameRouteOwnerSelector.ExceptRoute(",
|
||||||
|
source,
|
||||||
|
StringComparison.Ordinal);
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public void Extracted_contracts_retain_no_window_callbacks_or_visibility_owner()
|
public void Extracted_contracts_retain_no_window_callbacks_or_visibility_owner()
|
||||||
{
|
{
|
||||||
|
|
@ -698,7 +726,10 @@ public sealed class RetailPViewPassExecutorTests
|
||||||
public void DrawUnattachedSceneParticles(RetailPViewFrameInput frame) => Operations.Add("unattached-particles");
|
public void DrawUnattachedSceneParticles(RetailPViewFrameInput frame) => Operations.Add("unattached-particles");
|
||||||
public void FlushLandscapeAlpha() => Operations.Add("landscape-alpha");
|
public void FlushLandscapeAlpha() => Operations.Add("landscape-alpha");
|
||||||
public void DrawCellParticles(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("cell-particles");
|
public void DrawCellParticles(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("cell-particles");
|
||||||
public void DrawDynamicsParticles(RetailPViewFrameInput frame, IReadOnlyList<WorldEntity> survivors) => Operations.Add("dynamics-particles");
|
public void DrawDynamicsParticles(
|
||||||
|
RetailPViewFrameInput frame,
|
||||||
|
IReadOnlySet<uint> ownerIds) =>
|
||||||
|
Operations.Add("dynamics-particles");
|
||||||
public void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result) => Operations.Add("diagnostics");
|
public void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result) => Operations.Add("diagnostics");
|
||||||
|
|
||||||
public void BeginEntityFrame(in RenderFrameView view) =>
|
public void BeginEntityFrame(in RenderFrameView view) =>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue