From 2c848d4167780eb6a5db6e08e54ec5569680c683 Mon Sep 17 00:00:00 2001 From: Erik Date: Sat, 25 Jul 2026 06:28:31 +0200 Subject: [PATCH] Revert "perf(rendering): draw retained frame product" This reverts commit ef1d263337997bb030eadb7b8e71d73dc659907a. --- .../Composition/FrameRootComposition.cs | 18 +- .../LivePresentationComposition.cs | 14 +- .../Rendering/RetailPViewPassExecutor.cs | 76 +--- .../Rendering/RetailPViewRenderer.cs | 216 ++-------- .../Rendering/Scene/Arch/ArchRenderScene.cs | 59 +-- .../Rendering/Scene/RenderSceneContracts.cs | 4 - .../Scene/RenderScenePViewFrameProduct.cs | 326 +++------------ .../Scene/RenderSceneShadowRuntime.cs | 10 +- .../Wb/WbDrawDispatcher.PackedOracle.cs | 381 +++++------------- .../Rendering/Wb/WbDrawDispatcher.cs | 133 ++---- .../Rendering/ArchRenderSceneTests.cs | 56 --- .../RenderScenePViewFrameProductTests.cs | 136 ------- .../Rendering/RetailPViewPassExecutorTests.cs | 173 +------- 13 files changed, 268 insertions(+), 1334 deletions(-) diff --git a/src/AcDream.App/Composition/FrameRootComposition.cs b/src/AcDream.App/Composition/FrameRootComposition.cs index 5bf6f4d8..d7c8bad1 100644 --- a/src/AcDream.App/Composition/FrameRootComposition.cs +++ b/src/AcDream.App/Composition/FrameRootComposition.cs @@ -374,6 +374,12 @@ internal sealed class FrameRootCompositionPhase && d.Options.AutomationArtifactDirectory is not null ? new CurrentRenderSceneOracle() : null; + if ((currentRenderSceneOracle is null) + != (live.RenderSceneShadow is null)) + { + throw new InvalidOperationException( + "Lifecycle automation must compose the current-path referee and shadow render scene together."); + } RenderSceneShadowComparisonController? renderSceneShadowComparison = currentRenderSceneOracle is not null && live.RenderSceneShadow is not null @@ -384,18 +390,18 @@ internal sealed class FrameRootCompositionPhase acknowledgeDirty: false) : null; RenderScenePViewFrameProductController? renderFrameProduct = - live.RenderSceneShadow is not null + currentRenderSceneOracle is not null + && 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. - live.DrawDispatcher.SetCurrentRenderSceneObserver(null); - live.SelectionScene.SetCurrentRenderSceneObserver(null); + live.DrawDispatcher.SetCurrentRenderSceneObserver( + currentRenderSceneOracle); + live.SelectionScene.SetCurrentRenderSceneObserver( + currentRenderSceneOracle); var worldSceneRenderer = new WorldSceneRenderer( renderFrameResources, renderLoginState, diff --git a/src/AcDream.App/Composition/LivePresentationComposition.cs b/src/AcDream.App/Composition/LivePresentationComposition.cs index 23881f65..38f8bb02 100644 --- a/src/AcDream.App/Composition/LivePresentationComposition.cs +++ b/src/AcDream.App/Composition/LivePresentationComposition.cs @@ -200,11 +200,15 @@ internal sealed class LivePresentationCompositionPhase { CompositionAcquisitionScope.CompositionAcquisitionLease< RenderSceneShadowRuntime>? renderSceneShadowLease = null; - renderSceneShadowLease = scope.Acquire( - "render scene", - () => new RenderSceneShadowRuntime( - RenderSceneGeneration.FromRaw(1)), - static value => value.Dispose()); + if (interaction.RetainedUi?.Screenshots is not null + && d.Options.AutomationArtifactDirectory is not null) + { + renderSceneShadowLease = scope.Acquire( + "shadow render scene", + () => new RenderSceneShadowRuntime( + RenderSceneGeneration.FromRaw(1)), + static value => value.Dispose()); + } RenderSceneShadowRuntime? renderSceneShadow = renderSceneShadowLease?.Resource; diff --git a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs index ea873c97..0f6c73f9 100644 --- a/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs +++ b/src/AcDream.App/Rendering/RetailPViewPassExecutor.cs @@ -1,5 +1,4 @@ using System.Numerics; -using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Sky; using AcDream.App.Rendering.Wb; using AcDream.Core.Rendering; @@ -80,33 +79,8 @@ internal interface IOutdoorSceneParticleOwnerSource IReadOnlySet OutdoorSceneParticleEntityIds { get; } } -internal readonly record struct RenderFrameEntityDrawRequest( - RenderFrameView View, - RenderFrameCandidateRoute Route, - int RouteIndex, - uint CellId, - uint TupleLandblockId); - -internal interface IRenderFrameEntityPassExecutor -{ - void BeginEntityFrame(in RenderFrameView view); - - bool DrawEntityRoute( - ICamera camera, - in RenderFrameView view, - RenderFrameCandidateRoute route, - int routeIndex, - uint cellId, - uint tupleLandblockId); - - void CompleteEntityFrame(in RenderFrameView view); - - void AbortEntityFrame(); -} - internal sealed class RetailPViewPassExecutor : IRetailPViewPassExecutor, - IRenderFrameEntityPassExecutor, IOutdoorSceneParticleOwnerSource { private readonly GL _gl; @@ -173,30 +147,6 @@ internal sealed class RetailPViewPassExecutor : _particleClassifications.BeginFrame(); } - public void BeginEntityFrame(in RenderFrameView view) => - _entities.BeginPackedProductionFrame(in view); - - public bool DrawEntityRoute( - ICamera camera, - in RenderFrameView view, - RenderFrameCandidateRoute route, - int routeIndex, - uint cellId, - uint tupleLandblockId) => - _entities.DrawPackedProductionRoute( - camera, - in view, - route, - routeIndex, - cellId, - tupleLandblockId); - - public void CompleteEntityFrame(in RenderFrameView view) => - _entities.CompletePackedProductionFrame(in view); - - public void AbortEntityFrame() => - _entities.AbortPackedProductionFrame(); - public void AbortFrame() { List? failures = null; @@ -382,18 +332,7 @@ internal sealed class RetailPViewPassExecutor : _terrainDiagnostics.Complete(); DisableClipDistances(); - if (context.EntityDraw is RenderFrameEntityDrawRequest request) - { - RenderFrameView drawView = request.View; - _entities.DrawPackedProductionRoute( - frame.Camera, - in drawView, - request.Route, - request.RouteIndex, - request.CellId, - request.TupleLandblockId); - } - else if (context.OutdoorEntities.Count > 0) + if (context.OutdoorEntities.Count > 0) { var sceneryEntry = ( frame.PlayerLandblockId ?? 0u, @@ -424,18 +363,7 @@ internal sealed class RetailPViewPassExecutor : _clipFrame.BindTerrainClip(_gl); DisableClipDistances(); - if (context.EntityDraw is RenderFrameEntityDrawRequest request) - { - RenderFrameView drawView = request.View; - _entities.DrawPackedProductionRoute( - frame.Camera, - in drawView, - request.Route, - request.RouteIndex, - request.CellId, - request.TupleLandblockId); - } - else if (context.Dynamics.Count > 0) + if (context.Dynamics.Count > 0) { var dynamicsEntry = ( frame.PlayerLandblockId ?? 0u, diff --git a/src/AcDream.App/Rendering/RetailPViewRenderer.cs b/src/AcDream.App/Rendering/RetailPViewRenderer.cs index c9997e72..6e6e44c3 100644 --- a/src/AcDream.App/Rendering/RetailPViewRenderer.cs +++ b/src/AcDream.App/Rendering/RetailPViewRenderer.cs @@ -76,6 +76,13 @@ 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; @@ -205,16 +212,6 @@ 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 { @@ -245,73 +242,32 @@ 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( - ctx, - passes, - clipAssembly, - partition, - viewcone, - frameEntityPasses, - in frameView); + DrawLandscapeThroughOutsideView(ctx, passes, clipAssembly, partition, viewcone); passes.UseIndoorMembershipOnlyRouting(); DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells); DrawEnvCellShells(passes, pvFrame); - DrawCellObjectLists( - ctx, - passes, + DrawCellObjectLists(ctx, passes, pvFrame, clipAssembly, drawableCells, partition, viewcone); + DrawDynamicsLast(ctx, passes, partition, viewcone, ctx.RootCell.IsOutdoorNode); + + _candidateObserver?.CompletePViewFrame(); + _sceneFrameProduct?.BuildAndCompare( pvFrame, clipAssembly, + viewcone, + _lookInFrames, drawableCells, - partition, - viewcone, - frameEntityPasses, - in frameView); - DrawDynamicsLast( - ctx, - passes, - partition, - viewcone, + ctx.Cells, + ctx.AnimatedEntityIds, + ctx.PlayerLandblockId ?? 0, ctx.RootCell.IsOutdoorNode, - frameEntityPasses, - in frameView); - - if (entityFrameOpen) - { - frameEntityPasses!.CompleteEntityFrame(in frameView); - entityFrameOpen = false; - } - _candidateObserver?.CompletePViewFrame(); - _sceneFrameProduct?.CompleteProduction(in frameView); + ctx.CameraWorldPosition); 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 @@ -441,9 +397,7 @@ public sealed class RetailPViewRenderer RetailPViewFrameInput ctx, IRetailPViewPassExecutor passes, ClipFrameAssembly clipAssembly, - InteriorEntityPartition.Result partition, - IRenderFrameEntityPassExecutor? frameEntityPasses, - in RenderFrameView frameView) + InteriorEntityPartition.Result partition) { if (_lookInFrames.Count == 0) return; @@ -521,16 +475,7 @@ public sealed class RetailPViewRenderer i, cellId, _cellStaticScratch); - DrawEntityRouteOrLegacy( - ctx, - passes, - frameEntityPasses, - in frameView, - RenderFrameCandidateRoute.LookInObject, - i, - cellId, - _cellStaticScratch, - _oneCell); + passes.DrawEntityBucket(ctx, _cellStaticScratch, _oneCell); // The cell-particles pass for look-in cells — retail's // nested DrawCells draws objects WITH their emitters. @@ -547,9 +492,7 @@ public sealed class RetailPViewRenderer IRetailPViewPassExecutor passes, ClipFrameAssembly clipAssembly, InteriorEntityPartition.Result partition, - ViewconeCuller viewcone, - IRenderFrameEntityPassExecutor? frameEntityPasses, - in RenderFrameView frameView) + ViewconeCuller viewcone) { if (clipAssembly.OutsideViewSlices.Length == 0) return; @@ -591,37 +534,15 @@ 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) - { - EntityDraw = entityDraw, - }); + passes.DrawLandscapeSlice(ctx, new RetailPViewLandscapeSliceContext(slice, _outdoorStaticScratch)); } // #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, - frameEntityPasses, - in frameView); + DrawBuildingLookIns(ctx, passes, clipAssembly, partition); // LATE phase (per slice): outside-stage dynamics' meshes (#118 — drawn // pre-clear so the seal protects their aperture pixels; AFTER the @@ -670,25 +591,9 @@ 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) - { - EntityDraw = entityDraw, - }); + passes.DrawLandscapeSliceLate(ctx, new RetailPViewLandscapeLateSliceContext( + slice, _outdoorStaticScratch, _lateParticleOwnerScratch)); } // #131: UNATTACHED emitters (AttachedObjectId == 0 — portal swirls, @@ -797,9 +702,7 @@ public sealed class RetailPViewRenderer IRetailPViewPassExecutor passes, InteriorEntityPartition.Result partition, ViewconeCuller viewcone, - bool rootIsOutdoor, - IRenderFrameEntityPassExecutor? frameEntityPasses, - in RenderFrameView frameView) + bool rootIsOutdoor) { if (partition.Dynamics.Count == 0) return; @@ -846,16 +749,7 @@ public sealed class RetailPViewRenderer 0, _dynamicsScratch); passes.UseIndoorMembershipOnlyRouting(); - DrawEntityRouteOrLegacy( - ctx, - passes, - frameEntityPasses, - in frameView, - RenderFrameCandidateRoute.DynamicLast, - 0, - 0, - _dynamicsScratch, - visibleCellIds: null); + passes.DrawEntityBucket(ctx, _dynamicsScratch, visibleCellIds: null); // #121: dynamics' attached emitters (portal swirls, creature effects) // gate through the SAME cone-surviving owner set as their meshes — @@ -882,9 +776,7 @@ public sealed class RetailPViewRenderer ClipFrameAssembly clipAssembly, HashSet drawableCells, InteriorEntityPartition.Result partition, - ViewconeCuller viewcone, - IRenderFrameEntityPassExecutor? frameEntityPasses, - in RenderFrameView frameView) + ViewconeCuller viewcone) { // T1: per-cell STATIC object lists only (dat-baked 0x40 statics) — // dynamics moved to DrawDynamicsLast. Far→near with the cells, after @@ -948,16 +840,7 @@ public sealed class RetailPViewRenderer 0, _allCellStatics); passes.UseIndoorMembershipOnlyRouting(); - DrawEntityRouteOrLegacy( - ctx, - passes, - frameEntityPasses, - in frameView, - RenderFrameCandidateRoute.CellStatic, - 0, - 0, - _allCellStatics, - _cellObjCells); + passes.DrawEntityBucket(ctx, _allCellStatics, _cellObjCells); } // Cell-particle pass — consolidated across ALL visible cells into ONE @@ -980,35 +863,6 @@ 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 legacyEntities, - HashSet? 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 _outdoorStaticScratch = new(); private readonly List _cellStaticScratch = new(); @@ -1446,10 +1300,7 @@ public sealed class RetailPViewFrameResult public readonly record struct RetailPViewLandscapeSliceContext( ClipViewSlice Slice, - IReadOnlyList OutdoorEntities) -{ - internal RenderFrameEntityDrawRequest? EntityDraw { get; init; } -} + IReadOnlyList OutdoorEntities); /// #131/#132: the late landscape phase's per-slice payload — /// outside-stage dynamics to mesh-draw, plus the full scene-particle owner @@ -1457,10 +1308,7 @@ public readonly record struct RetailPViewLandscapeSliceContext( public readonly record struct RetailPViewLandscapeLateSliceContext( ClipViewSlice Slice, IReadOnlyList Dynamics, - IReadOnlyList ParticleOwners) -{ - internal RenderFrameEntityDrawRequest? EntityDraw { get; init; } -} + IReadOnlyList ParticleOwners); public readonly record struct RetailPViewCellSliceContext( uint CellId, diff --git a/src/AcDream.App/Rendering/Scene/Arch/ArchRenderScene.cs b/src/AcDream.App/Rendering/Scene/Arch/ArchRenderScene.cs index 07f7f3a2..48ef28d2 100644 --- a/src/AcDream.App/Rendering/Scene/Arch/ArchRenderScene.cs +++ b/src/AcDream.App/Rendering/Scene/Arch/ArchRenderScene.cs @@ -10,8 +10,7 @@ namespace AcDream.App.Rendering.Scene.Arch; /// projection. Arch identities never leave this namespace; callers only see /// generation-checked acdream value types. /// -/// Slice F introduced this owner in non-drawing shadow mode; Slice G supplies -/// its borrowed frame product to the production renderer. +/// Slice F1 deliberately leaves this owner unbound and non-drawing. /// internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource { @@ -36,7 +35,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource private ArchWorld _world; private RenderProjectionCounts _counts; private ulong _lastAppliedJournalSequence; - private ulong _indexRevision = 1; private bool _disposed; public ArchRenderScene(RenderSceneGeneration initialGeneration) @@ -227,7 +225,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource _counts = default; _lastAppliedJournalSequence = 0; Generation = replacementGeneration; - AdvanceIndexRevision(); } public void Dispose() @@ -277,13 +274,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource _dirty.Count); } - ulong IRenderSceneQuerySource.GetIndexRevision( - RenderSceneGeneration generation) - { - EnsureQueryGeneration(generation); - return _indexRevision; - } - bool IRenderSceneQuerySource.TryGet( RenderSceneGeneration generation, RenderProjectionId id, @@ -601,12 +591,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource in RenderProjectionRecord prior, in RenderProjectionRecord current) { - if (IndexMembershipEquals(in prior, in current)) - { - SynchronizeDirtyIndex(in current); - return; - } - RemoveFromIndices(in prior); AddToIndices(in current); } @@ -651,7 +635,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource _lightCandidates.Add(record.Id); if (record.DirtyMask != RenderDirtyMask.None) _dirty.Add(record.Id); - AdvanceIndexRevision(); } private void RemoveFromIndices(in RenderProjectionRecord record) @@ -667,46 +650,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource _dirty.Remove(record.Id); RemoveCell(_cellStatics, record.Residency.FullCellId, record.Id); RemoveCell(_cellDynamics, record.Residency.FullCellId, record.Id); - AdvanceIndexRevision(); - } - - private static bool IndexMembershipEquals( - in RenderProjectionRecord left, - in RenderProjectionRecord right) - { - const RenderProjectionFlags indexedFlags = - RenderProjectionFlags.Translucent - | RenderProjectionFlags.Selectable - | RenderProjectionFlags.LightCandidate - | RenderProjectionFlags.PortalStraddling - | RenderProjectionFlags.SpatiallyResident; - - return left.ProjectionClass == right.ProjectionClass - && left.Source.ParentCellId == right.Source.ParentCellId - && left.Residency.FullCellId == right.Residency.FullCellId - && (left.Flags & indexedFlags) == (right.Flags & indexedFlags) - && left.MeshSet.MeshCount == right.MeshSet.MeshCount - && left.SortKey == right.SortKey; - } - - private void SynchronizeDirtyIndex( - in RenderProjectionRecord record) - { - if (record.DirtyMask == RenderDirtyMask.None) - _dirty.Remove(record.Id); - else - _dirty.Add(record.Id); - } - - private void AdvanceIndexRevision() - { - if (_indexRevision == ulong.MaxValue) - { - throw new InvalidOperationException( - "Render-scene index revision space was exhausted."); - } - - _indexRevision++; } private static bool IsDynamic(RenderProjectionClass projectionClass) => diff --git a/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs b/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs index 057d1fe7..abd4ce67 100644 --- a/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs +++ b/src/AcDream.App/Rendering/Scene/RenderSceneContracts.cs @@ -455,7 +455,6 @@ internal interface IRenderSceneQuerySource { RenderProjectionCounts GetCounts(RenderSceneGeneration generation); RenderSceneIndexCounts GetIndexCounts(RenderSceneGeneration generation); - ulong GetIndexRevision(RenderSceneGeneration generation); bool TryGet( RenderSceneGeneration generation, @@ -504,9 +503,6 @@ internal readonly struct RenderSceneQuery public RenderSceneIndexCounts IndexCounts => Source.GetIndexCounts(Generation); - public ulong IndexRevision => - Source.GetIndexRevision(Generation); - public bool TryGet( RenderProjectionId id, out RenderProjectionRecord record) => diff --git a/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs b/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs index bbd368bd..e573bc09 100644 --- a/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs +++ b/src/AcDream.App/Rendering/Scene/RenderScenePViewFrameProduct.cs @@ -113,15 +113,16 @@ internal interface IRenderFrameProductSnapshotSource } /// -/// Owns the same-frame PView product. Diagnostic composition may compare it -/// with the retired current-path observer; production borrows the exact same -/// arena and supplies its ordered ranges directly to the dispatcher. +/// G1 compare-only owner. It builds the candidate frame product directly from +/// the incremental scene after the accepted PView traversal, borrows it in the +/// same render frame, and compares route membership against the current-path +/// observer. It never supplies a production draw input. /// internal sealed class RenderScenePViewFrameProductController : IRenderFrameProductSnapshotSource { private readonly RenderSceneShadowRuntime _shadow; - private readonly CurrentRenderSceneOracle? _current; + private readonly CurrentRenderSceneOracle _current; private readonly WbDrawDispatcher? _dispatcher; private readonly RenderScenePViewFrameBuilder _builder = new(); private readonly RenderFrameExchange _exchange = new(); @@ -154,12 +155,12 @@ internal sealed class RenderScenePViewFrameProductController : public RenderScenePViewFrameProductController( RenderSceneShadowRuntime shadow, - CurrentRenderSceneOracle? current = null, + CurrentRenderSceneOracle current, Action? log = null, WbDrawDispatcher? dispatcher = null) { _shadow = shadow ?? throw new ArgumentNullException(nameof(shadow)); - _current = current; + _current = current ?? throw new ArgumentNullException(nameof(current)); _log = log; _dispatcher = dispatcher; Snapshot = RenderFrameProductComparisonSnapshot.Disabled with @@ -170,56 +171,6 @@ internal sealed class RenderScenePViewFrameProductController : public RenderFrameProductComparisonSnapshot Snapshot { get; private set; } - private CurrentRenderSceneOracle Current => - _current - ?? throw new InvalidOperationException( - "Current-path comparison was requested without a referee."); - - public RenderFrameView BuildAndBorrow( - PortalVisibilityFrame portalFrame, - ClipFrameAssembly clipAssembly, - ViewconeCuller viewcone, - IReadOnlyList lookInFrames, - HashSet drawableCells, - IRetailPViewCellSource cells, - HashSet? animatedEntityIds, - bool rootIsOutdoor) - { - ArgumentNullException.ThrowIfNull(portalFrame); - ArgumentNullException.ThrowIfNull(clipAssembly); - ArgumentNullException.ThrowIfNull(viewcone); - ArgumentNullException.ThrowIfNull(lookInFrames); - ArgumentNullException.ThrowIfNull(drawableCells); - ArgumentNullException.ThrowIfNull(cells); - - ulong frameSequence = checked(++_productFrameSequence); - RenderSceneQuery scene = _shadow.Query; - RenderSceneDigest sourceDigest = _current is null - ? new RenderSceneDigest( - scene.Generation, - scene.Counts, - default) - : _shadow.BuildDigest(); - var input = new RenderScenePViewBuildInput( - scene, - sourceDigest, - portalFrame, - clipAssembly, - viewcone, - lookInFrames, - drawableCells, - cells, - animatedEntityIds, - rootIsOutdoor); - _builder.Build(_exchange, frameSequence, in input); - return _exchange.BorrowLatest( - scene.Generation, - frameSequence); - } - - public void Release(in RenderFrameView view) => - _exchange.Release(in view); - public void BuildAndCompare( PortalVisibilityFrame portalFrame, ClipFrameAssembly clipAssembly, @@ -232,7 +183,18 @@ internal sealed class RenderScenePViewFrameProductController : bool rootIsOutdoor, Vector3 cameraWorldPosition = default) { - RenderFrameView view = BuildAndBorrow( + ArgumentNullException.ThrowIfNull(portalFrame); + ArgumentNullException.ThrowIfNull(clipAssembly); + ArgumentNullException.ThrowIfNull(viewcone); + ArgumentNullException.ThrowIfNull(lookInFrames); + ArgumentNullException.ThrowIfNull(drawableCells); + ArgumentNullException.ThrowIfNull(cells); + + ulong frameSequence = checked(++_productFrameSequence); + RenderSceneQuery scene = _shadow.Query; + var input = new RenderScenePViewBuildInput( + scene, + _shadow.BuildDigest(), portalFrame, clipAssembly, viewcone, @@ -241,6 +203,10 @@ internal sealed class RenderScenePViewFrameProductController : cells, animatedEntityIds, rootIsOutdoor); + _builder.Build(_exchange, frameSequence, in input); + RenderFrameView view = _exchange.BorrowLatest( + scene.Generation, + frameSequence); try { Compare( @@ -250,92 +216,26 @@ internal sealed class RenderScenePViewFrameProductController : } finally { - Release(in view); + _exchange.Release(in view); } } - public void CompleteProduction(in RenderFrameView view) - { - _packedClassification = - _dispatcher?.PackedClassificationSnapshot ?? default; - if (_current is null) - { - _shadow.ClearDirty(); - Snapshot = Snapshot with - { - Enabled = true, - ProductFrameSequence = view.FrameSequence, - ActualCandidateCount = view.RouteCandidates.Length, - PackedClassificationProjectionCount = - _packedClassification.ProjectionCount, - PackedClassificationStaticRebuildCount = - _packedClassification.StaticRebuildCount, - PackedClassificationSameFrameReuseCount = - _packedClassification.SameFrameReuseCount, - PackedClassificationCrossFrameReuseCount = - _packedClassification.CrossFrameReuseCount, - PackedClassificationAnimatedCount = - _packedClassification.AnimatedClassificationCount, - PackedClassificationRetiredCount = - _packedClassification.RetiredProjectionCount, - PackedClassificationRetainedPayloadBytes = - _packedClassification.RetainedPayloadBytes, - ProductCounts = view.DiagnosticCounts, - }; - return; - } - - CandidateComparison candidates = - CompareCandidateMembership(in view); - if (candidates.Successful) - _shadow.ClearDirty(); - Snapshot = Snapshot with - { - Enabled = true, - ProductFrameSequence = view.FrameSequence, - CurrentPViewFrameSequence = - Current.Snapshot.CompletedPViewFrameSequence, - ComparisonCount = _comparisonCount, - SuccessfulComparisonCount = _successfulComparisonCount, - ExpectedCandidateCount = _expected.Count, - ActualCandidateCount = _actual.Count, - MismatchCount = _mismatchCount, - FirstMismatch = _firstMismatch, - ExpectedDigest = candidates.ExpectedDigest, - ActualDigest = candidates.ActualDigest, - PackedClassificationProjectionCount = - _packedClassification.ProjectionCount, - PackedClassificationStaticRebuildCount = - _packedClassification.StaticRebuildCount, - PackedClassificationSameFrameReuseCount = - _packedClassification.SameFrameReuseCount, - PackedClassificationCrossFrameReuseCount = - _packedClassification.CrossFrameReuseCount, - PackedClassificationAnimatedCount = - _packedClassification.AnimatedClassificationCount, - PackedClassificationRetiredCount = - _packedClassification.RetiredProjectionCount, - PackedClassificationRetainedPayloadBytes = - _packedClassification.RetainedPayloadBytes, - ProductCounts = view.DiagnosticCounts, - }; - } - - private CandidateComparison CompareCandidateMembership( - in RenderFrameView view) + private void Compare( + in RenderFrameView view, + uint tupleLandblockId, + Vector3 cameraWorldPosition) { _comparisonCount = checked(_comparisonCount + 1); _expected.Clear(); _actual.Clear(); IReadOnlyList expected = - Current.PViewCandidates; + _current.PViewCandidates; if (_expected.Capacity < expected.Count) _expected.Capacity = expected.Count; - for (int index = 0; index < expected.Count; index++) + for (int i = 0; i < expected.Count; i++) { - CurrentRenderPViewCandidateFingerprint candidate = - expected[index]; + CurrentRenderPViewCandidateFingerprint candidate = expected[i]; CurrentRenderProjectionFingerprint projection = candidate.Projection; _expected.Add(new CandidateKey( @@ -345,15 +245,11 @@ internal sealed class RenderScenePViewFrameProductController : ExpectedId(in projection))); } - ReadOnlySpan ranges = - view.RouteRanges; - ReadOnlySpan records = - view.RouteCandidates; + ReadOnlySpan ranges = view.RouteRanges; + ReadOnlySpan records = view.RouteCandidates; if (_actual.Capacity < records.Length) _actual.Capacity = records.Length; - for (int rangeIndex = 0; - rangeIndex < ranges.Length; - rangeIndex++) + for (int rangeIndex = 0; rangeIndex < ranges.Length; rangeIndex++) { RenderFrameCandidateRange range = ranges[rangeIndex]; int end = checked(range.Offset + range.Count); @@ -389,32 +285,10 @@ internal sealed class RenderScenePViewFrameProductController : { _firstMismatch = mismatch; _log?.Invoke( - "[render-frame-product] first mismatch: " - + mismatch); + "[render-frame-product] first mismatch: " + mismatch); } } - return new CandidateComparison( - mismatch is null, - expectedDigest, - actualDigest); - } - - private void Compare( - in RenderFrameView view, - uint tupleLandblockId, - Vector3 cameraWorldPosition) - { - CandidateComparison candidates = - CompareCandidateMembership(in view); - RenderSceneHash128 expectedDigest = - candidates.ExpectedDigest; - RenderSceneHash128 actualDigest = - candidates.ActualDigest; - string? mismatch = candidates.Successful - ? null - : _firstMismatch; - PackedInputComparison packed = ComparePackedInput(in view, tupleLandblockId); ClassifiedOutputComparison classified = @@ -438,7 +312,7 @@ internal sealed class RenderScenePViewFrameProductController : Enabled: true, ProductFrameSequence: view.FrameSequence, CurrentPViewFrameSequence: - Current.Snapshot.CompletedPViewFrameSequence, + _current.Snapshot.CompletedPViewFrameSequence, ComparisonCount: _comparisonCount, SuccessfulComparisonCount: _successfulComparisonCount, ExpectedCandidateCount: _expected.Count, @@ -521,7 +395,7 @@ internal sealed class RenderScenePViewFrameProductController : _packedClassification = dispatcher.PackedClassificationSnapshot; IReadOnlyList expected = - Current.DispatcherSubmissions; + _current.DispatcherSubmissions; IReadOnlyList actual = dispatcher.PackedDispatcherSubmissions; RenderSceneHash128 expectedDigest = @@ -552,7 +426,7 @@ internal sealed class RenderScenePViewFrameProductController : } IReadOnlyList - expectedSelection = Current.SelectionParts; + expectedSelection = _current.SelectionParts; IReadOnlyList actualSelection = dispatcher.PackedSelectionParts; BuildSelectionKeys( @@ -754,7 +628,7 @@ internal sealed class RenderScenePViewFrameProductController : _packedActual.Clear(); IReadOnlyList expected = - Current.DispatcherCandidates; + _current.DispatcherCandidates; if (_packedExpected.Capacity < expected.Count) _packedExpected.Capacity = expected.Count; for (int index = 0; index < expected.Count; index++) @@ -876,10 +750,10 @@ internal sealed class RenderScenePViewFrameProductController : private string? ComparePackedInputKeys(int actualDrawCount) { - if (Current.Snapshot.DispatcherDrawCount != actualDrawCount) + if (_current.Snapshot.DispatcherDrawCount != actualDrawCount) { return $"field=drawCount expected=" - + $"{Current.Snapshot.DispatcherDrawCount} " + + $"{_current.Snapshot.DispatcherDrawCount} " + $"actual={actualDrawCount}"; } if (_packedExpected.Count != _packedActual.Count) @@ -1017,11 +891,6 @@ internal sealed class RenderScenePViewFrameProductController : RenderSceneHash128 ExpectedDigest, RenderSceneHash128 ActualDigest); - private readonly record struct CandidateComparison( - bool Successful, - RenderSceneHash128 ExpectedDigest, - RenderSceneHash128 ActualDigest); - private readonly record struct ClassifiedOutputComparison( bool Successful, int ExpectedDrawCount, @@ -1124,19 +993,11 @@ internal sealed class RenderScenePViewFrameBuilder private RenderProjectionRecord[] _outdoor = []; private RenderProjectionRecord[] _dynamics = []; private RenderProjectionRecord[] _cell = []; - private RenderProjectionRecord[] _dirty = []; private RenderProjectionRecord[] _survivors = []; private RenderProjectionRecord[] _cellRoute = []; - private readonly Dictionary - _outdoorPositions = []; - private readonly Dictionary - _dynamicPositions = []; private int _outdoorCount; private int _dynamicCount; private int _cellRouteCount; - private int _dirtyCount; - private RenderSceneGeneration _indexGeneration; - private ulong _indexRevision; public void Build( RenderFrameExchange exchange, @@ -1162,7 +1023,6 @@ internal sealed class RenderScenePViewFrameBuilder BuildCellStaticRoute(writer, in input); BuildDynamicLastRoute(writer, in input); writer.Publish(); - AcknowledgeCachedDirtyRecords(); } catch { @@ -1174,97 +1034,21 @@ internal sealed class RenderScenePViewFrameBuilder private void LoadSceneIndices(RenderSceneQuery scene) { RenderSceneIndexCounts counts = scene.IndexCounts; - ulong revision = scene.IndexRevision; - if (scene.Generation != _indexGeneration - || revision != _indexRevision) - { - EnsureCapacity(ref _outdoor, counts.OutdoorStatic); - _outdoorCount = scene.CopyIndexTo( - RenderSceneIndex.OutdoorStatic, - _outdoor); - _outdoorCount = CompactAndSort( - _outdoor, - _outdoorCount); - BuildPositionIndex( - _outdoor, - _outdoorCount, - _outdoorPositions); + EnsureCapacity(ref _outdoor, counts.OutdoorStatic); + _outdoorCount = scene.CopyIndexTo( + RenderSceneIndex.OutdoorStatic, + _outdoor); + _outdoorCount = CompactAndSort( + _outdoor, + _outdoorCount); - EnsureCapacity(ref _dynamics, counts.Dynamic); - _dynamicCount = scene.CopyIndexTo( - RenderSceneIndex.Dynamic, - _dynamics); - _dynamicCount = CompactAndSort( - _dynamics, - _dynamicCount); - BuildPositionIndex( - _dynamics, - _dynamicCount, - _dynamicPositions); - - _indexGeneration = scene.Generation; - _indexRevision = revision; - } - - EnsureCapacity(ref _dirty, counts.Dirty); - _dirtyCount = scene.CopyIndexTo( - RenderSceneIndex.Dirty, - _dirty); - for (int index = 0; index < _dirtyCount; index++) - { - RenderProjectionRecord record = _dirty[index]; - if (_outdoorPositions.TryGetValue( - record.Id, - out int outdoorPosition)) - { - _outdoor[outdoorPosition] = record; - } - if (_dynamicPositions.TryGetValue( - record.Id, - out int dynamicPosition)) - { - _dynamics[dynamicPosition] = record; - } - } - } - - private void AcknowledgeCachedDirtyRecords() - { - for (int index = 0; index < _dirtyCount; index++) - { - RenderProjectionId id = _dirty[index].Id; - if (_outdoorPositions.TryGetValue( - id, - out int outdoorPosition)) - { - _outdoor[outdoorPosition] = - _outdoor[outdoorPosition] with - { - DirtyMask = RenderDirtyMask.None, - }; - } - if (_dynamicPositions.TryGetValue( - id, - out int dynamicPosition)) - { - _dynamics[dynamicPosition] = - _dynamics[dynamicPosition] with - { - DirtyMask = RenderDirtyMask.None, - }; - } - } - } - - private static void BuildPositionIndex( - RenderProjectionRecord[] records, - int count, - Dictionary positions) - { - positions.Clear(); - positions.EnsureCapacity(count); - for (int index = 0; index < count; index++) - positions.Add(records[index].Id, index); + EnsureCapacity(ref _dynamics, counts.Dynamic); + _dynamicCount = scene.CopyIndexTo( + RenderSceneIndex.Dynamic, + _dynamics); + _dynamicCount = CompactAndSort( + _dynamics, + _dynamicCount); } private void BuildOutdoorRoutes( diff --git a/src/AcDream.App/Rendering/Scene/RenderSceneShadowRuntime.cs b/src/AcDream.App/Rendering/Scene/RenderSceneShadowRuntime.cs index aad7adbd..25ab889f 100644 --- a/src/AcDream.App/Rendering/Scene/RenderSceneShadowRuntime.cs +++ b/src/AcDream.App/Rendering/Scene/RenderSceneShadowRuntime.cs @@ -50,12 +50,10 @@ internal interface IRenderSceneShadowSnapshotSource } /// -/// Owner of the derived render projection. Slice F introduced it as a -/// diagnostic shadow; Slice G keeps the same generation/incarnation journal -/// boundary while lending its frame product to production drawing. Accepted -/// world/live lifecycle edges append to one ordered journal, and the update -/// thread drains it only after current-frame animation, network, and -/// attachment reconciliation. +/// Diagnostic-only owner of Slice F's non-drawing render projection. The +/// normal runtime never constructs this type. Accepted world/live lifecycle +/// edges append to one ordered journal, and the update thread drains it only +/// after current-frame animation, network, and attachment reconciliation. /// internal sealed class RenderSceneShadowRuntime : IDisposable { diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.PackedOracle.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.PackedOracle.cs index b04df854..00b19924 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.PackedOracle.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.PackedOracle.cs @@ -1,5 +1,4 @@ using System.Numerics; -using AcDream.Core.Rendering; using AcDream.App.Rendering.Scene; using AcDream.App.Rendering.Selection; using AcDream.Core.Lighting; @@ -11,10 +10,10 @@ using DatReaderWriter.Enums; namespace AcDream.App.Rendering.Wb; /// -/// Scene-frame classification and production submission. The retained group -/// storage is also consumed by the G2/G3 compare oracle, while production -/// routes reuse the dispatcher's exact mesh, texture, light, translucency, -/// selection, upload, and draw owners. +/// G2 compare-only classification of the scene-built frame product. This file +/// deliberately shares the production dispatcher's exact mesh, texture, +/// light, translucency, selection, grouping, and ordering owners while keeping +/// its group storage separate. It never uploads or draws. /// public sealed unsafe partial class WbDrawDispatcher { @@ -36,10 +35,6 @@ public sealed unsafe partial class WbDrawDispatcher private long _packedGroupFrame; private long _nextPackedGroupRegistration = 1; private int _nextPackedInstanceSubmissionOrder; - private bool _packedProductionFrameOpen; - private RenderSceneGeneration _packedProductionGeneration; - private ulong _packedProductionFrameSequence; - private int _packedProductionNextRange; internal IReadOnlyList PackedDispatcherSubmissions => _packedSubmissions; @@ -56,28 +51,104 @@ public sealed unsafe partial class WbDrawDispatcher uint tupleLandblockId, Vector3 cameraWorldPosition) { - if (_packedProductionFrameOpen) + if (_packedGroupFrame == long.MaxValue) { throw new InvalidOperationException( - "The packed dispatcher oracle cannot replace an active production frame."); + "Packed dispatcher frame identity was exhausted."); } - BeginPackedFrameStorage(in view); + _packedGroupFrame++; + PruneInstanceGroupsUnusedBeforeFrame( + _packedGroups, + _packedRetiredGroupKeys, + _packedGroupFrame - 1); + _packedEntityById.Clear(); + _packedSubmissions.Clear(); + _packedSelectionParts.Clear(); + _packedSelectionKeys.Clear(); + _packedClassificationCache.BeginFrame(view.Generation); + + ReadOnlySpan entities = + view.EntityCandidates; + for (int i = 0; i < entities.Length; i++) + { + if (!_packedEntityById.TryAdd( + entities[i].Projection.Id, + entities[i])) + { + throw new InvalidOperationException( + $"Packed dispatcher received duplicate projection " + + $"{entities[i].Projection.Id}."); + } + } + + ReadOnlySpan meshParts = view.MeshParts; + ReadOnlySpan routeCandidates = + view.RouteCandidates; ReadOnlySpan ranges = view.RouteRanges; for (int rangeIndex = 0; rangeIndex < ranges.Length; rangeIndex++) { - PackedRangeClassification classified = - ClassifyPackedRange( - in view, - rangeIndex, - tupleLandblockId, - publishSelection: false); + _nextPackedInstanceSubmissionOrder = 0; + uint anyVao = 0; + foreach (InstanceGroup group in _packedGroups.Values) + group.ClearPerInstanceData(); + + RenderFrameCandidateRange range = ranges[rangeIndex]; + int end = checked(range.Offset + range.Count); + if ((uint)range.Offset > (uint)routeCandidates.Length + || (uint)end > (uint)routeCandidates.Length) + { + throw new InvalidOperationException( + $"Packed dispatcher route {rangeIndex} exceeds " + + "candidate storage."); + } + + for (int routeIndex = range.Offset; + routeIndex < end; + routeIndex++) + { + RenderProjectionRecord projection = + routeCandidates[routeIndex]; + if ((projection.Flags & RenderProjectionFlags.Draw) == 0) + continue; + if (!_packedEntityById.TryGetValue( + projection.Id, + out RenderFrameEntityCandidate source)) + { + throw new InvalidOperationException( + $"Packed route references absent entity " + + $"{projection.Id}."); + } + + int meshEnd = checked( + source.MeshPartOffset + source.MeshPartCount); + if ((uint)source.MeshPartOffset + > (uint)meshParts.Length + || (uint)meshEnd > (uint)meshParts.Length) + { + throw new InvalidOperationException( + $"Packed entity {projection.Id} exceeds mesh " + + "part storage."); + } + + RenderInstanceCandidate candidate = + RenderInstanceCandidate.FromFrame( + in source, + tupleLandblockId); + ClassifyPackedEntity( + in projection, + in candidate, + meshParts.Slice( + source.MeshPartOffset, + source.MeshPartCount), + ref anyVao); + } bool deferTransparent = ShouldDeferPackedTransparent( - classified.AnyVao, + anyVao, _alphaQueue?.IsCollecting == true); InstanceLayoutCounts counts = PartitionInstanceGroups( _packedGroups.Values, @@ -105,236 +176,11 @@ public sealed unsafe partial class WbDrawDispatcher _packedClassificationCache.EndFrame(); } - internal void BeginPackedProductionFrame( - in RenderFrameView view) - { - if (_packedProductionFrameOpen) - { - throw new InvalidOperationException( - "The packed dispatcher cannot begin a second production frame."); - } - - BeginPackedFrameStorage(in view); - _packedProductionFrameOpen = true; - _packedProductionGeneration = view.Generation; - _packedProductionFrameSequence = view.FrameSequence; - _packedProductionNextRange = 0; - } - - internal bool DrawPackedProductionRoute( - ICamera camera, - in RenderFrameView view, - RenderFrameCandidateRoute route, - int routeIndex, - uint cellId, - uint tupleLandblockId) - { - ValidatePackedProductionView(in view); - ReadOnlySpan ranges = - view.RouteRanges; - if (_packedProductionNextRange >= ranges.Length) - return false; - - RenderFrameCandidateRange range = - ranges[_packedProductionNextRange]; - if (range.Route != route - || range.RouteIndex != routeIndex - || range.CellId != cellId) - { - return false; - } - - int rangePosition = _packedProductionNextRange++; - bool diagnosticsEnabled = BeginEntityDispatch( - camera, - out Matrix4x4 viewProjection, - out Vector3 cameraWorldPosition); - PackedRangeClassification classified = - ClassifyPackedRange( - in view, - rangePosition, - tupleLandblockId, - publishSelection: true); - ExecuteClassifiedGroups( - viewProjection, - cameraWorldPosition, - classified.AnyVao, - _packedGroups.Values, - EntitySet.All, - classified.CandidateCount, - classified.MeshPartCount, - diagnosticsEnabled, - observeCurrentPath: false); - return true; - } - - internal void CompletePackedProductionFrame( - in RenderFrameView view) - { - ValidatePackedProductionView(in view); - int expected = view.RouteRanges.Length; - if (_packedProductionNextRange != expected) - { - throw new InvalidOperationException( - "The packed dispatcher did not consume the complete route stream: " - + $"consumed={_packedProductionNextRange} expected={expected}."); - } - - _packedClassificationCache.EndFrame(); - ResetPackedProductionFrame(); - } - - internal void AbortPackedProductionFrame() - { - if (!_packedProductionFrameOpen) - return; - - _packedClassificationCache.EndFrame(); - ResetPackedProductionFrame(); - } - - private void BeginPackedFrameStorage( - in RenderFrameView view) - { - if (_packedGroupFrame == long.MaxValue) - { - throw new InvalidOperationException( - "Packed dispatcher frame identity was exhausted."); - } - - _packedGroupFrame++; - PruneInstanceGroupsUnusedBeforeFrame( - _packedGroups, - _packedRetiredGroupKeys, - _packedGroupFrame - 1); - _packedEntityById.Clear(); - _packedSubmissions.Clear(); - _packedSelectionParts.Clear(); - _packedSelectionKeys.Clear(); - _packedClassificationCache.BeginFrame(view.Generation); - - ReadOnlySpan entities = - view.EntityCandidates; - for (int index = 0; index < entities.Length; index++) - { - if (!_packedEntityById.TryAdd( - entities[index].Projection.Id, - entities[index])) - { - throw new InvalidOperationException( - "Packed dispatcher received duplicate projection " - + $"{entities[index].Projection.Id}."); - } - } - } - - private PackedRangeClassification ClassifyPackedRange( - in RenderFrameView view, - int rangeIndex, - uint tupleLandblockId, - bool publishSelection) - { - ReadOnlySpan ranges = - view.RouteRanges; - if ((uint)rangeIndex >= (uint)ranges.Length) - throw new ArgumentOutOfRangeException(nameof(rangeIndex)); - - _nextPackedInstanceSubmissionOrder = 0; - foreach (InstanceGroup group in _packedGroups.Values) - group.ClearPerInstanceData(); - - uint anyVao = 0; - int meshPartCount = 0; - RenderFrameCandidateRange range = ranges[rangeIndex]; - ReadOnlySpan routeCandidates = - view.RouteCandidates; - ReadOnlySpan meshParts = view.MeshParts; - int end = checked(range.Offset + range.Count); - if ((uint)range.Offset > (uint)routeCandidates.Length - || (uint)end > (uint)routeCandidates.Length) - { - throw new InvalidOperationException( - $"Packed dispatcher route {rangeIndex} exceeds candidate storage."); - } - - for (int candidateIndex = range.Offset; - candidateIndex < end; - candidateIndex++) - { - RenderProjectionRecord projection = - routeCandidates[candidateIndex]; - if ((projection.Flags & RenderProjectionFlags.Draw) == 0) - continue; - if (!_packedEntityById.TryGetValue( - projection.Id, - out RenderFrameEntityCandidate source)) - { - throw new InvalidOperationException( - $"Packed route references absent entity {projection.Id}."); - } - - int meshEnd = checked( - source.MeshPartOffset + source.MeshPartCount); - if ((uint)source.MeshPartOffset > (uint)meshParts.Length - || (uint)meshEnd > (uint)meshParts.Length) - { - throw new InvalidOperationException( - $"Packed entity {projection.Id} exceeds mesh part storage."); - } - - meshPartCount = checked( - meshPartCount + source.MeshPartCount); - RenderInstanceCandidate candidate = - RenderInstanceCandidate.FromFrame( - in source, - tupleLandblockId); - ClassifyPackedEntity( - in projection, - in candidate, - meshParts.Slice( - source.MeshPartOffset, - source.MeshPartCount), - ref anyVao, - publishSelection); - } - - return new PackedRangeClassification( - anyVao, - range.Count, - meshPartCount); - } - - private void ValidatePackedProductionView( - in RenderFrameView view) - { - if (!_packedProductionFrameOpen - || view.Generation != _packedProductionGeneration - || view.FrameSequence != _packedProductionFrameSequence) - { - throw new InvalidOperationException( - "The packed dispatcher received a stale or foreign production frame."); - } - } - - private void ResetPackedProductionFrame() - { - _packedProductionFrameOpen = false; - _packedProductionGeneration = default; - _packedProductionFrameSequence = 0; - _packedProductionNextRange = 0; - } - - private readonly record struct PackedRangeClassification( - uint AnyVao, - int CandidateCount, - int MeshPartCount); - private void ClassifyPackedEntity( in RenderProjectionRecord projection, in RenderInstanceCandidate entity, ReadOnlySpan meshParts, - ref uint anyVao, - bool publishSelection) + ref uint anyVao) { (uint slot, bool culled) = ResolveSlotForFrame( _clipRoutingActive, @@ -385,8 +231,7 @@ public sealed unsafe partial class WbDrawDispatcher slot, lights, indoor, - selectionLighting, - publishSelection); + selectionLighting); return; } @@ -427,8 +272,6 @@ public sealed unsafe partial class WbDrawDispatcher if (renderData is null) { reusableAcrossFrames = false; - if (_missRequested.Add(meshRef.GfxObjId)) - _meshAdapter.EnsureLoaded(meshRef.GfxObjId); continue; } if (anyVao == 0) @@ -448,8 +291,6 @@ public sealed unsafe partial class WbDrawDispatcher if (partData is null) { reusableAcrossFrames = false; - if (_missRequested.Add(gfxObjId)) - _meshAdapter.EnsureLoaded(gfxObjId); continue; } @@ -493,8 +334,7 @@ public sealed unsafe partial class WbDrawDispatcher in entity, selectionPartIndex, (uint)gfxObjId, - model, - publishSelection); + model); } } else @@ -534,8 +374,7 @@ public sealed unsafe partial class WbDrawDispatcher in entity, partIndex, meshRef.GfxObjId, - model, - publishSelection); + model); } } @@ -642,8 +481,7 @@ public sealed unsafe partial class WbDrawDispatcher uint slot, InstanceLightSet lights, bool indoor, - Vector2 selectionLighting, - bool publishSelection) + Vector2 selectionLighting) { for (int i = 0; i < entry.Batches.Count; i++) { @@ -666,8 +504,7 @@ public sealed unsafe partial class WbDrawDispatcher in entity, part.PartIndex, part.GfxObjId, - part.RestPose * entity.RootWorld, - publishSelection); + part.RestPose * entity.RootWorld); } } @@ -756,29 +593,13 @@ public sealed unsafe partial class WbDrawDispatcher in RenderInstanceCandidate entity, int partIndex, uint gfxObjId, - Matrix4x4 localToWorld, - bool publishSelection) + Matrix4x4 localToWorld) { - if (!_packedSelectionKeys.Add(new PackedSelectionKey( - entity.LocalEntityId, - partIndex, - gfxObjId))) - { - return; - } - - if (publishSelection) - { - _selectionSink?.AddVisiblePart( - entity.ServerGuid, - entity.LocalEntityId, - partIndex, - gfxObjId, - localToWorld); - return; - } - if (_selectionSink is not IRetailSelectionRenderOracle oracle + || !_packedSelectionKeys.Add(new PackedSelectionKey( + entity.LocalEntityId, + partIndex, + gfxObjId)) || !oracle.TryCreateVisiblePart( entity.ServerGuid, entity.LocalEntityId, diff --git a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs index b6d50e74..a84fd7e0 100644 --- a/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs +++ b/src/AcDream.App/Rendering/Wb/WbDrawDispatcher.cs @@ -1483,10 +1483,41 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable HashSet? animatedEntityIds = null, EntitySet set = EntitySet.All) { - bool diag = BeginEntityDispatch( - camera, - out Matrix4x4 vp, - out Vector3 camPos); + _shader.Use(); + _selectionLighting?.TickLighting(); + _indoorProbeFrameCounter++; + var vp = camera.View * camera.Projection; + _shader.SetMatrix4("uViewProjection", vp); + // A7 Fix D D-3/D-4: object path — plain Lambert points + sun. MUST set + // explicitly (shared GL uniform; EnvCellRenderer sets it to 1). + _shader.SetInt("uLightingMode", 0); + // #176 stripe-hunt isolation (ACDREAM_LIGHT_DEBUG) — throwaway diagnostic. + _shader.SetInt("uLightDebug", AcDream.Core.Rendering.RenderingDiagnostics.LightDebugMode); + + // #128 self-heal: fresh re-request dedup per Draw pass. + _missRequested.Clear(); + + bool diag = string.Equals(Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG"), "1", StringComparison.Ordinal); + + if (diag && !_gpuQueriesInitialized) + { + for (int i = 0; i < GpuQueryRingDepth; i++) + { + _gpuQueryOpaque[i] = _gl.GenQuery(); + _gpuQueryTransparent[i] = _gl.GenQuery(); + } + _gpuQueriesInitialized = true; + } + + // Always run the CPU stopwatch — cheap; only logged under diag. + _cpuStopwatch.Restart(); + + // Camera world-space position for front-to-back sort (perf #2). The view + // matrix is the inverse of the camera's world transform, so the world + // translation lives in the inverse's translation row. + Vector3 camPos = Vector3.Zero; + if (Matrix4x4.Invert(camera.View, out var invView)) + camPos = invView.Translation; // ── Phase 1: clear groups, walk entities, build groups ────────────── // Draw is invoked several times per frame (landscape slices, late @@ -2027,71 +2058,11 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable if (RenderingDiagnostics.ProbeClipRouteEnabled && _clipRoutingActive) EmitClipRouteDispatchProbe(probeCulledEntities); - ExecuteClassifiedGroups( - vp, - camPos, - anyVao, - _groups.Values, - set, - walkResult.EntitiesWalked, - _walkScratch.Count, - diag, - observeCurrentPath: true); - } - - private bool BeginEntityDispatch( - ICamera camera, - out Matrix4x4 viewProjection, - out Vector3 cameraWorldPosition) - { - _shader.Use(); - _selectionLighting?.TickLighting(); - _indoorProbeFrameCounter++; - viewProjection = camera.View * camera.Projection; - _shader.SetMatrix4("uViewProjection", viewProjection); - _shader.SetInt("uLightingMode", 0); - _shader.SetInt( - "uLightDebug", - RenderingDiagnostics.LightDebugMode); - _missRequested.Clear(); - - bool diagnosticsEnabled = string.Equals( - Environment.GetEnvironmentVariable("ACDREAM_WB_DIAG"), - "1", - StringComparison.Ordinal); - if (diagnosticsEnabled && !_gpuQueriesInitialized) - { - for (int index = 0; index < GpuQueryRingDepth; index++) - { - _gpuQueryOpaque[index] = _gl.GenQuery(); - _gpuQueryTransparent[index] = _gl.GenQuery(); - } - _gpuQueriesInitialized = true; - } - - _cpuStopwatch.Restart(); - cameraWorldPosition = Vector3.Zero; - if (Matrix4x4.Invert(camera.View, out Matrix4x4 inverseView)) - cameraWorldPosition = inverseView.Translation; - return diagnosticsEnabled; - } - - private void ExecuteClassifiedGroups( - Matrix4x4 vp, - Vector3 camPos, - uint anyVao, - IEnumerable groups, - EntitySet set, - int entitiesWalked, - int tupleCount, - bool diag, - bool observeCurrentPath) - { // Nothing visible — skip the GL pass entirely. if (anyVao == 0) { - LastDrawStats = new DrawStats(set, entitiesWalked, tupleCount, 0, 0, 0, 0, 0, 0); - ObserveClassifiedDispatcherSubmission(observeCurrentPath, + LastDrawStats = new DrawStats(set, walkResult.EntitiesWalked, _walkScratch.Count, 0, 0, 0, 0, 0, 0); + ObserveCurrentDispatcherSubmission( visibleInstanceCount: 0, immediateInstanceCount: 0, deferTransparent: false, @@ -2104,7 +2075,7 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable // ── Phase 3: assign FirstInstance per group, lay matrices contiguously, sort opaque ── bool deferTransparent = _alphaQueue?.IsCollecting == true; var instanceCounts = PartitionInstanceGroups( - groups, + _groups.Values, deferTransparent, camPos, _opaqueDraws, @@ -2113,8 +2084,8 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable int immediateInstances = instanceCounts.ImmediateInstances; if (totalInstances == 0) { - LastDrawStats = new DrawStats(set, entitiesWalked, tupleCount, 0, 0, 0, 0, 0, 0); - ObserveClassifiedDispatcherSubmission(observeCurrentPath, + LastDrawStats = new DrawStats(set, walkResult.EntitiesWalked, _walkScratch.Count, 0, 0, 0, 0, 0, 0); + ObserveCurrentDispatcherSubmission( visibleInstanceCount: 0, immediateInstanceCount: 0, deferTransparent, @@ -2231,15 +2202,15 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable _transparentByteOffset = layout.TransparentByteOffset; LastDrawStats = new DrawStats( set, - entitiesWalked, - tupleCount, + walkResult.EntitiesWalked, + _walkScratch.Count, totalInstances, totalDraws, cullRuns, _opaqueDrawCount, _transparentDrawCount, totalTriangles); - ObserveClassifiedDispatcherSubmission(observeCurrentPath, + ObserveCurrentDispatcherSubmission( totalInstances, immediateInstances, deferTransparent, @@ -2450,7 +2421,6 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable } } - /// /// Phase A8 RR5 (2026-05-26): per-building draw overload. Walks only /// entities whose ParentCellId is in , plus @@ -2606,23 +2576,6 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable observer.ObserveDispatcherSubmission(in submission); } - private void ObserveClassifiedDispatcherSubmission( - bool observeCurrentPath, - int visibleInstanceCount, - int immediateInstanceCount, - bool deferTransparent, - Vector3 cameraWorldPosition) - { - if (observeCurrentPath) - { - ObserveCurrentDispatcherSubmission( - visibleInstanceCount, - immediateInstanceCount, - deferTransparent, - cameraWorldPosition); - } - } - internal static CurrentRenderDispatcherSubmission CreateDispatcherSubmission( int visibleInstanceCount, diff --git a/tests/AcDream.App.Tests/Rendering/ArchRenderSceneTests.cs b/tests/AcDream.App.Tests/Rendering/ArchRenderSceneTests.cs index 94750bdb..abdf712c 100644 --- a/tests/AcDream.App.Tests/Rendering/ArchRenderSceneTests.cs +++ b/tests/AcDream.App.Tests/Rendering/ArchRenderSceneTests.cs @@ -575,62 +575,6 @@ public sealed class ArchRenderSceneTests Assert.True(scene.Memory.EstimatedIndexBytes > 0); } - [Fact] - public void IndexRevision_AdvancesOnlyWhenOrderedMembershipCanChange() - { - RenderSceneGeneration generation = Generation(18); - using var scene = new ArchRenderScene(generation); - RenderProjectionRecord original = Record( - 18, - 1, - RenderProjectionClass.OutdoorStatic); - scene.Apply( - [RenderProjectionDelta.Register(generation, 1, original)]); - scene.ClearDirty(); - ulong registeredRevision = scene.OpenQuery().IndexRevision; - - Matrix4x4 movedTransform = - Matrix4x4.CreateTranslation(4, 5, 6); - RenderProjectionRecord moved = original with - { - Transform = new RenderTransform(movedTransform), - PreviousTransform = - new PreviousRenderTransform( - original.Transform.LocalToWorld), - Bounds = new RenderWorldBounds( - new Vector3(3, 4, 5), - new Vector3(5, 6, 7)), - }; - scene.Apply( - [ - RenderProjectionDelta.Update( - RenderProjectionDeltaKind.UpdateTransform, - generation, - 2, - moved), - ]); - - RenderSceneQuery movedQuery = scene.OpenQuery(); - Assert.Equal(registeredRevision, movedQuery.IndexRevision); - Assert.Equal(1, movedQuery.IndexCounts.Dirty); - - RenderProjectionRecord reordered = moved with - { - SortKey = new RenderSortKey(moved.SortKey.Value + 1), - }; - scene.Apply( - [ - RenderProjectionDelta.Update( - RenderProjectionDeltaKind.UpdateTransform, - generation, - 3, - reordered), - ]); - - Assert.True( - scene.OpenQuery().IndexRevision > registeredRevision); - } - private static RenderProjectionRecord Record( ulong id, ulong incarnation, diff --git a/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs b/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs index 9a5b3f5c..6bb4d3ff 100644 --- a/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs +++ b/tests/AcDream.App.Tests/Rendering/RenderScenePViewFrameProductTests.cs @@ -133,142 +133,6 @@ public sealed class RenderScenePViewFrameProductTests } } - [Fact] - public void Builder_ReusesOrderedIndicesWhileRefreshingDirtyRecords() - { - using var scene = new ArchRenderScene(Generation); - RenderProjectionRecord later = Record( - 0x0100_0000_0000_0012, - RenderProjectionClass.OutdoorStatic); - RenderProjectionRecord earlier = Record( - 0x0100_0000_0000_0011, - RenderProjectionClass.OutdoorStatic); - scene.Apply( - [ - RenderProjectionDelta.Register(Generation, 1, later), - RenderProjectionDelta.Register(Generation, 2, earlier), - ]); - - PortalVisibilityFrame portal = Portal(Cell); - ClipFrameAssembly clip = FullScreenClip(Cell); - ViewconeCuller viewcone = - ViewconeCuller.Build(clip, Matrix4x4.Identity); - var exchange = new RenderFrameExchange(); - var builder = new RenderScenePViewFrameBuilder(); - - Build(frameSequence: 1); - RenderFrameView first = exchange.BorrowLatest(Generation, 1); - try - { - Assert.Equal( - [earlier.Id, later.Id], - first.OutdoorStaticCandidates.ToArray() - .Select(static record => record.Id)); - } - finally - { - exchange.Release(in first); - } - - scene.ClearDirty(); - ulong retainedRevision = scene.OpenQuery().IndexRevision; - Matrix4x4 movedTransform = - Matrix4x4.CreateTranslation(0.25f, 0, 0); - RenderProjectionRecord moved = later with - { - Transform = new RenderTransform(movedTransform), - PreviousTransform = - new PreviousRenderTransform( - later.Transform.LocalToWorld), - Bounds = new RenderWorldBounds( - new Vector3(-0.75f, -1, -1), - new Vector3(1.25f, 1, 1)), - }; - scene.Apply( - [ - RenderProjectionDelta.Update( - RenderProjectionDeltaKind.UpdateTransform, - Generation, - 3, - moved), - ]); - Assert.Equal( - retainedRevision, - scene.OpenQuery().IndexRevision); - - Build(frameSequence: 2); - RenderFrameView second = exchange.BorrowLatest(Generation, 2); - try - { - RenderProjectionRecord projected = - second.EntityCandidates.ToArray() - .Single(candidate => - candidate.Projection.Id == later.Id) - .Projection; - Assert.Equal( - movedTransform, - projected.Transform.LocalToWorld); - Assert.Equal( - [earlier.Id, later.Id], - second.OutdoorStaticCandidates.ToArray() - .Select(static record => record.Id)); - } - finally - { - exchange.Release(in second); - } - - scene.ClearDirty(); - RenderProjectionRecord reordered = moved with - { - SortKey = new RenderSortKey(0), - }; - scene.Apply( - [ - RenderProjectionDelta.Update( - RenderProjectionDeltaKind.UpdateTransform, - Generation, - 4, - reordered), - ]); - Assert.True( - scene.OpenQuery().IndexRevision > retainedRevision); - - Build(frameSequence: 3); - RenderFrameView third = exchange.BorrowLatest(Generation, 3); - try - { - Assert.Equal( - [later.Id, earlier.Id], - third.OutdoorStaticCandidates.ToArray() - .Select(static record => record.Id)); - } - finally - { - exchange.Release(in third); - } - - void Build(ulong frameSequence) - { - RenderSceneQuery query = scene.OpenQuery(); - var input = new RenderScenePViewBuildInput( - query, - new RenderSceneDigest( - query.Generation, - query.Counts, - default), - portal, - clip, - viewcone, - [], - [Cell], - EmptyCellSource.Instance, - [], - RootIsOutdoor: true); - builder.Build(exchange, frameSequence, in input); - } - } - [Fact] public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate() { diff --git a/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs b/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs index cf313e09..403fc218 100644 --- a/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs +++ b/tests/AcDream.App.Tests/Rendering/RetailPViewPassExecutorTests.cs @@ -158,103 +158,6 @@ public sealed class RetailPViewPassExecutorTests }); } - [Fact] - public void DrawInside_production_product_supplies_the_ordered_entity_routes() - { - LoadedCell interior = InteriorWithExit(0xA9B40100u); - WorldEntity cellStatic = Entity( - 30u, - parentCellId: interior.CellId); - 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 staticRecord = - RenderProjectionRecordFactory.ProjectEntity( - StaticRenderProjectionJournal.StaticEntityId( - landblockId, - cellStatic.Id), - RenderProjectionClass.IndoorCellStatic, - RenderOwnerIncarnation.FromRaw(cellStatic.Id), - landblockId, - interior.CellId, - cellStatic, - spatiallyVisible: true); - RenderProjectionRecord dynamicRecord = - RenderProjectionRecordFactory.ProjectEntity( - LiveRenderProjectionJournal.ProjectionId(dynamic.Id), - RenderProjectionClass.LiveDynamicRoot, - RenderOwnerIncarnation.FromRaw(dynamic.Id), - landblockId, - interior.CellId, - dynamic, - spatiallyVisible: true); - shadow.Journal.Register(in staticRecord); - 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, [cellStatic, dynamic]), - executor); - - AssertAppearsInOrder( - string.Join('|', executor.Operations), - "entity-frame-begin", - "entity-route:CellStatic:0:00000000", - "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_does_not_require_compare_only_referee() - { - 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() { @@ -283,11 +186,11 @@ public sealed class RetailPViewPassExecutorTests drawInside, "passes.BeginFrame();", "passes.EmitDiagnostics(ctx, result);", - "DrawLandscapeThroughOutsideView(", + "DrawLandscapeThroughOutsideView(ctx, passes", "DrawExitPortalMasks(ctx, passes", "DrawEnvCellShells(passes, pvFrame);", - "DrawCellObjectLists(", - "DrawDynamicsLast("); + "DrawCellObjectLists(ctx, passes", + "DrawDynamicsLast(ctx, passes"); string landscape = MethodBody( source, @@ -295,9 +198,9 @@ public sealed class RetailPViewPassExecutorTests AssertAppearsInOrder( landscape, "passes.SetTerrainClip(slice.Planes);", - "passes.DrawLandscapeSlice(", - "DrawBuildingLookIns(", - "passes.DrawLandscapeSliceLate(", + "passes.DrawLandscapeSlice(ctx", + "DrawBuildingLookIns(ctx, passes", + "passes.DrawLandscapeSliceLate(ctx", "passes.DrawUnattachedSceneParticles(ctx);", "passes.FlushLandscapeAlpha();", "passes.ClearInteriorDepth();"); @@ -601,10 +504,7 @@ public sealed class RetailPViewPassExecutorTests public float Aspect { get; set; } = 1f; } - private sealed class RecordingExecutor : - IRetailPViewPassExecutor, - IRenderFrameEntityPassExecutor, - IDisposable + private sealed class RecordingExecutor : IRetailPViewPassExecutor, IDisposable { public void AbortFrame() => Operations.Add("abort"); @@ -657,41 +557,8 @@ public sealed class RetailPViewPassExecutorTests ViewconeCuller viewcone) => Operations.Add("outstage-routing"); public void EmitPhantomObjects(uint cellId, int survivorCount) => Operations.Add("phantom-objects"); - public void DrawLandscapeSlice( - RetailPViewFrameInput frame, - RetailPViewLandscapeSliceContext context) - { - Operations.Add("landscape-early"); - if (context.EntityDraw is RenderFrameEntityDrawRequest request) - { - RenderFrameView view = request.View; - DrawEntityRoute( - frame.Camera, - in view, - request.Route, - request.RouteIndex, - request.CellId, - request.TupleLandblockId); - } - } - - public void DrawLandscapeSliceLate( - RetailPViewFrameInput frame, - RetailPViewLandscapeLateSliceContext context) - { - Operations.Add("landscape-late"); - if (context.EntityDraw is RenderFrameEntityDrawRequest request) - { - RenderFrameView view = request.View; - DrawEntityRoute( - frame.Camera, - in view, - request.Route, - request.RouteIndex, - request.CellId, - request.TupleLandblockId); - } - } + public void DrawLandscapeSlice(RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context) => Operations.Add("landscape-early"); + public void DrawLandscapeSliceLate(RetailPViewFrameInput frame, RetailPViewLandscapeLateSliceContext context) => Operations.Add("landscape-late"); public void ClearInteriorDepth() => Operations.Add("interior-depth-clear"); public void DrawExitPortalMask(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("exit-mask"); public void DrawLookInPortalPunch(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("look-in-punch"); @@ -701,28 +568,6 @@ public sealed class RetailPViewPassExecutorTests public void DrawDynamicsParticles(RetailPViewFrameInput frame, IReadOnlyList survivors) => Operations.Add("dynamics-particles"); public void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result) => Operations.Add("diagnostics"); - public void BeginEntityFrame(in RenderFrameView view) => - Operations.Add("entity-frame-begin"); - - public bool DrawEntityRoute( - ICamera camera, - in RenderFrameView view, - RenderFrameCandidateRoute route, - int routeIndex, - uint cellId, - uint tupleLandblockId) - { - Operations.Add( - $"entity-route:{route}:{routeIndex}:{cellId:X8}"); - return true; - } - - public void CompleteEntityFrame(in RenderFrameView view) => - Operations.Add("entity-frame-complete"); - - public void AbortEntityFrame() => - Operations.Add("entity-frame-abort"); - public void Dispose() => _clipFrame.Dispose(); } }