perf(pview): consolidate per-cell cell-particle pass into one union draw
DrawCellObjectLists drew cell particles PER visible cell, and each call (DrawRetailPViewCellParticles -> ParticleRenderer.Draw) re-walked the ENTIRE live particle set to filter by owner id — O(cells x particles). Measured via [CPU-PHASE] at dense Arwic this was the cellobjects sink (~5.4 ms CPU; the phase's GPU share is 0.01 ms — pure CPU). gpu is 0.5 ms; the dense town is ~96% CPU-bound (the earlier "6.7 ms GPU" was a moving/streaming transient). Static owners are disjoint per cell (InteriorEntityPartition.ByCell partitions by ParentCellId), so the UNION of survivors (= _allCellStatics, already accumulated in loop 1 for the batched entity draw) draws EXACTLY the same emitters in ONE pass: the callback gates on owner id, the renderer sorts globally back-to-front, and the per-cell slice was never used for clipping (scissor gate deleted in T3; DisableClipDistances). Runs after the batched static draw so emitters still depth-test against same-cell statics. Deletes the loop-2 re-cull and collapses the per-cell BuildDrawList allocations N -> 1 (also eases the GC spikes). Adversarial review (3 angles) confirmed emitter-set equivalence, no double- draw, equal-or-better compositing, and no lost per-cell side effect — and found the OLD code DOUBLE-DREW additive particles for multi-view-polygon cells (one DrawCellParticles per slice, same owner set each time; the #121 over-bright class). Consolidation draws each emitter once, fixing that latent bug. Pixels identical-or-better; draw-mechanism speed only. Build + full suite green (pview replay tests incl. HouseExitWalkReplay/TowerAscent/Issue130 pass). Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
290e731ce3
commit
9f51a4db18
1 changed files with 19 additions and 29 deletions
|
|
@ -70,10 +70,12 @@ public sealed class RetailPViewRenderer
|
|||
// apparatus (plan Task 5). Zero-alloc: GetTimestamp/GetElapsedTime + a direct-
|
||||
// call local fn (struct closure over _ts, never a delegate).
|
||||
long _ts = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
FrameProfiler.MarkGpu("_start"); // GPU timestamp baseline (=2; CPU-only phases diff ~0)
|
||||
void Phase(string n)
|
||||
{
|
||||
if (FrameProfiler.CpuPhaseEnabled)
|
||||
FrameProfiler.AddCpuPhase(n, System.Diagnostics.Stopwatch.GetElapsedTime(_ts).TotalMilliseconds);
|
||||
FrameProfiler.MarkGpu(n);
|
||||
_ts = System.Diagnostics.Stopwatch.GetTimestamp();
|
||||
}
|
||||
|
||||
|
|
@ -859,35 +861,23 @@ public sealed class RetailPViewRenderer
|
|||
DrawEntityBucket(ctx, _allCellStatics, _cellObjCells);
|
||||
}
|
||||
|
||||
// Loop 2: per-cell particles, AFTER the batched statics are in the depth
|
||||
// buffer. T3 (BR-5): particles gate through the SAME viewcone as their
|
||||
// owners — the callback receives the cone-surviving entity set, so an
|
||||
// emitter attached to a culled static no longer draws through the wall
|
||||
// (the candle-flames-through-walls fix). Re-cull per cell to rebuild the
|
||||
// owner set; the sphere tests are negligible next to the draw submission
|
||||
// just removed. Consumed synchronously within this iteration (scratch reuse).
|
||||
for (int i = pvFrame.OrderedVisibleCells.Count - 1; i >= 0; i--)
|
||||
{
|
||||
uint cellId = pvFrame.OrderedVisibleCells[i];
|
||||
if (!drawableCells.Contains(cellId))
|
||||
continue;
|
||||
|
||||
if (!partition.ByCell.TryGetValue(cellId, out var bucket) || bucket.Count == 0)
|
||||
continue;
|
||||
|
||||
_cellStaticScratch.Clear();
|
||||
foreach (var e in bucket)
|
||||
{
|
||||
EntitySphere(e, out var c, out float r);
|
||||
if (viewcone.SphereVisibleInCell(cellId, c, r))
|
||||
_cellStaticScratch.Add(e);
|
||||
}
|
||||
if (_cellStaticScratch.Count == 0)
|
||||
continue;
|
||||
|
||||
foreach (var slice in GetCellSlicesOrNoClip(clipAssembly, cellId))
|
||||
ctx.DrawCellParticles?.Invoke(new RetailPViewCellSliceContext(cellId, slice, _cellStaticScratch));
|
||||
}
|
||||
// Cell-particle pass — consolidated across ALL visible cells into ONE
|
||||
// draw. Was per-cell, and each call re-walked the ENTIRE live particle set
|
||||
// (DrawRetailPViewCellParticles → ParticleRenderer.Draw enumerates every
|
||||
// live emitter), i.e. O(cells × particles) — the dense-town cellobjects
|
||||
// sink (~5 ms at Arwic). Static owners are disjoint per cell, so the UNION
|
||||
// (= _allCellStatics, already accumulated above for the batched draw) draws
|
||||
// EXACTLY the same emitters: the callback gates on owner id (the cone-
|
||||
// surviving set), the renderer sorts globally back-to-front, and the per-
|
||||
// cell slice was never used for clipping (the scissor gate was deleted in
|
||||
// T3 — DrawRetailPViewCellParticles disables clip distances). Runs after
|
||||
// the batched static draw so emitters depth-test against the statics now in
|
||||
// the buffer (the statics-before-particles order). cellId/slice are unused
|
||||
// by the particle pass — pass NoClipSlice + the union owner list. This also
|
||||
// drops the per-cell BuildDrawList allocations (N → 1).
|
||||
if (_allCellStatics.Count > 0)
|
||||
ctx.DrawCellParticles?.Invoke(
|
||||
new RetailPViewCellSliceContext(0u, NoClipSlice, _allCellStatics));
|
||||
}
|
||||
|
||||
// T3 scratch lists (render thread only; cleared per use).
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue