Revert "perf(rendering): draw retained frame product"
This reverts commit ef1d263337.
This commit is contained in:
parent
624e1119ca
commit
2c848d4167
13 changed files with 268 additions and 1334 deletions
|
|
@ -374,6 +374,12 @@ internal sealed class FrameRootCompositionPhase
|
||||||
&& d.Options.AutomationArtifactDirectory is not null
|
&& d.Options.AutomationArtifactDirectory is not null
|
||||||
? new CurrentRenderSceneOracle()
|
? new CurrentRenderSceneOracle()
|
||||||
: null;
|
: 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 =
|
RenderSceneShadowComparisonController? renderSceneShadowComparison =
|
||||||
currentRenderSceneOracle is not null
|
currentRenderSceneOracle is not null
|
||||||
&& live.RenderSceneShadow is not null
|
&& live.RenderSceneShadow is not null
|
||||||
|
|
@ -384,18 +390,18 @@ internal sealed class FrameRootCompositionPhase
|
||||||
acknowledgeDirty: false)
|
acknowledgeDirty: false)
|
||||||
: null;
|
: null;
|
||||||
RenderScenePViewFrameProductController? renderFrameProduct =
|
RenderScenePViewFrameProductController? renderFrameProduct =
|
||||||
live.RenderSceneShadow is not null
|
currentRenderSceneOracle is not null
|
||||||
|
&& live.RenderSceneShadow is not null
|
||||||
? new RenderScenePViewFrameProductController(
|
? new RenderScenePViewFrameProductController(
|
||||||
live.RenderSceneShadow,
|
live.RenderSceneShadow,
|
||||||
currentRenderSceneOracle,
|
currentRenderSceneOracle,
|
||||||
message => d.Log("[UI-PROBE] " + message),
|
message => d.Log("[UI-PROBE] " + message),
|
||||||
live.DrawDispatcher)
|
live.DrawDispatcher)
|
||||||
: null;
|
: null;
|
||||||
// After G4 the retained product is the production object source.
|
live.DrawDispatcher.SetCurrentRenderSceneObserver(
|
||||||
// The old dispatcher/selection observer is intentionally detached;
|
currentRenderSceneOracle);
|
||||||
// automation still compares the independently built PView route list.
|
live.SelectionScene.SetCurrentRenderSceneObserver(
|
||||||
live.DrawDispatcher.SetCurrentRenderSceneObserver(null);
|
currentRenderSceneOracle);
|
||||||
live.SelectionScene.SetCurrentRenderSceneObserver(null);
|
|
||||||
var worldSceneRenderer = new WorldSceneRenderer(
|
var worldSceneRenderer = new WorldSceneRenderer(
|
||||||
renderFrameResources,
|
renderFrameResources,
|
||||||
renderLoginState,
|
renderLoginState,
|
||||||
|
|
|
||||||
|
|
@ -200,11 +200,15 @@ internal sealed class LivePresentationCompositionPhase
|
||||||
{
|
{
|
||||||
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
CompositionAcquisitionScope.CompositionAcquisitionLease<
|
||||||
RenderSceneShadowRuntime>? renderSceneShadowLease = null;
|
RenderSceneShadowRuntime>? renderSceneShadowLease = null;
|
||||||
renderSceneShadowLease = scope.Acquire(
|
if (interaction.RetainedUi?.Screenshots is not null
|
||||||
"render scene",
|
&& d.Options.AutomationArtifactDirectory is not null)
|
||||||
() => new RenderSceneShadowRuntime(
|
{
|
||||||
RenderSceneGeneration.FromRaw(1)),
|
renderSceneShadowLease = scope.Acquire(
|
||||||
static value => value.Dispose());
|
"shadow render scene",
|
||||||
|
() => new RenderSceneShadowRuntime(
|
||||||
|
RenderSceneGeneration.FromRaw(1)),
|
||||||
|
static value => value.Dispose());
|
||||||
|
}
|
||||||
RenderSceneShadowRuntime? renderSceneShadow =
|
RenderSceneShadowRuntime? renderSceneShadow =
|
||||||
renderSceneShadowLease?.Resource;
|
renderSceneShadowLease?.Resource;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AcDream.App.Rendering.Scene;
|
|
||||||
using AcDream.App.Rendering.Sky;
|
using AcDream.App.Rendering.Sky;
|
||||||
using AcDream.App.Rendering.Wb;
|
using AcDream.App.Rendering.Wb;
|
||||||
using AcDream.Core.Rendering;
|
using AcDream.Core.Rendering;
|
||||||
|
|
@ -80,33 +79,8 @@ internal interface IOutdoorSceneParticleOwnerSource
|
||||||
IReadOnlySet<uint> OutdoorSceneParticleEntityIds { get; }
|
IReadOnlySet<uint> 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 :
|
internal sealed class RetailPViewPassExecutor :
|
||||||
IRetailPViewPassExecutor,
|
IRetailPViewPassExecutor,
|
||||||
IRenderFrameEntityPassExecutor,
|
|
||||||
IOutdoorSceneParticleOwnerSource
|
IOutdoorSceneParticleOwnerSource
|
||||||
{
|
{
|
||||||
private readonly GL _gl;
|
private readonly GL _gl;
|
||||||
|
|
@ -173,30 +147,6 @@ internal sealed class RetailPViewPassExecutor :
|
||||||
_particleClassifications.BeginFrame();
|
_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()
|
public void AbortFrame()
|
||||||
{
|
{
|
||||||
List<Exception>? failures = null;
|
List<Exception>? failures = null;
|
||||||
|
|
@ -382,18 +332,7 @@ internal sealed class RetailPViewPassExecutor :
|
||||||
_terrainDiagnostics.Complete();
|
_terrainDiagnostics.Complete();
|
||||||
|
|
||||||
DisableClipDistances();
|
DisableClipDistances();
|
||||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
if (context.OutdoorEntities.Count > 0)
|
||||||
{
|
|
||||||
RenderFrameView drawView = request.View;
|
|
||||||
_entities.DrawPackedProductionRoute(
|
|
||||||
frame.Camera,
|
|
||||||
in drawView,
|
|
||||||
request.Route,
|
|
||||||
request.RouteIndex,
|
|
||||||
request.CellId,
|
|
||||||
request.TupleLandblockId);
|
|
||||||
}
|
|
||||||
else if (context.OutdoorEntities.Count > 0)
|
|
||||||
{
|
{
|
||||||
var sceneryEntry = (
|
var sceneryEntry = (
|
||||||
frame.PlayerLandblockId ?? 0u,
|
frame.PlayerLandblockId ?? 0u,
|
||||||
|
|
@ -424,18 +363,7 @@ internal sealed class RetailPViewPassExecutor :
|
||||||
_clipFrame.BindTerrainClip(_gl);
|
_clipFrame.BindTerrainClip(_gl);
|
||||||
|
|
||||||
DisableClipDistances();
|
DisableClipDistances();
|
||||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
if (context.Dynamics.Count > 0)
|
||||||
{
|
|
||||||
RenderFrameView drawView = request.View;
|
|
||||||
_entities.DrawPackedProductionRoute(
|
|
||||||
frame.Camera,
|
|
||||||
in drawView,
|
|
||||||
request.Route,
|
|
||||||
request.RouteIndex,
|
|
||||||
request.CellId,
|
|
||||||
request.TupleLandblockId);
|
|
||||||
}
|
|
||||||
else if (context.Dynamics.Count > 0)
|
|
||||||
{
|
{
|
||||||
var dynamicsEntry = (
|
var dynamicsEntry = (
|
||||||
frame.PlayerLandblockId ?? 0u,
|
frame.PlayerLandblockId ?? 0u,
|
||||||
|
|
|
||||||
|
|
@ -76,6 +76,13 @@ public sealed class RetailPViewRenderer
|
||||||
InteriorEntityPartition.IObserver? partitionObserver,
|
InteriorEntityPartition.IObserver? partitionObserver,
|
||||||
RenderScenePViewFrameProductController? sceneFrameProduct = null)
|
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;
|
_partitionObserver = partitionObserver;
|
||||||
_candidateObserver = partitionObserver as ICurrentRenderPViewObserver;
|
_candidateObserver = partitionObserver as ICurrentRenderPViewObserver;
|
||||||
_sceneFrameProduct = sceneFrameProduct;
|
_sceneFrameProduct = sceneFrameProduct;
|
||||||
|
|
@ -205,16 +212,6 @@ public sealed class RetailPViewRenderer
|
||||||
ctx.ViewProjection,
|
ctx.ViewProjection,
|
||||||
_viewconeScratch);
|
_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();
|
_candidateObserver?.BeginPViewFrame();
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
@ -245,73 +242,32 @@ public sealed class RetailPViewRenderer
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (_sceneFrameProduct is not null)
|
DrawLandscapeThroughOutsideView(ctx, passes, clipAssembly, partition, viewcone);
|
||||||
{
|
|
||||||
frameView = _sceneFrameProduct.BuildAndBorrow(
|
|
||||||
pvFrame,
|
|
||||||
clipAssembly,
|
|
||||||
viewcone,
|
|
||||||
_lookInFrames,
|
|
||||||
drawableCells,
|
|
||||||
ctx.Cells,
|
|
||||||
ctx.AnimatedEntityIds,
|
|
||||||
ctx.RootCell.IsOutdoorNode);
|
|
||||||
frameViewBorrowed = true;
|
|
||||||
frameEntityPasses!.BeginEntityFrame(in frameView);
|
|
||||||
entityFrameOpen = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
DrawLandscapeThroughOutsideView(
|
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
clipAssembly,
|
|
||||||
partition,
|
|
||||||
viewcone,
|
|
||||||
frameEntityPasses,
|
|
||||||
in frameView);
|
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
|
DrawExitPortalMasks(ctx, passes, pvFrame, clipAssembly, drawableCells);
|
||||||
DrawEnvCellShells(passes, pvFrame);
|
DrawEnvCellShells(passes, pvFrame);
|
||||||
DrawCellObjectLists(
|
DrawCellObjectLists(ctx, passes, pvFrame, clipAssembly, drawableCells, partition, viewcone);
|
||||||
ctx,
|
DrawDynamicsLast(ctx, passes, partition, viewcone, ctx.RootCell.IsOutdoorNode);
|
||||||
passes,
|
|
||||||
|
_candidateObserver?.CompletePViewFrame();
|
||||||
|
_sceneFrameProduct?.BuildAndCompare(
|
||||||
pvFrame,
|
pvFrame,
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
|
viewcone,
|
||||||
|
_lookInFrames,
|
||||||
drawableCells,
|
drawableCells,
|
||||||
partition,
|
ctx.Cells,
|
||||||
viewcone,
|
ctx.AnimatedEntityIds,
|
||||||
frameEntityPasses,
|
ctx.PlayerLandblockId ?? 0,
|
||||||
in frameView);
|
|
||||||
DrawDynamicsLast(
|
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
partition,
|
|
||||||
viewcone,
|
|
||||||
ctx.RootCell.IsOutdoorNode,
|
ctx.RootCell.IsOutdoorNode,
|
||||||
frameEntityPasses,
|
ctx.CameraWorldPosition);
|
||||||
in frameView);
|
|
||||||
|
|
||||||
if (entityFrameOpen)
|
|
||||||
{
|
|
||||||
frameEntityPasses!.CompleteEntityFrame(in frameView);
|
|
||||||
entityFrameOpen = false;
|
|
||||||
}
|
|
||||||
_candidateObserver?.CompletePViewFrame();
|
|
||||||
_sceneFrameProduct?.CompleteProduction(in frameView);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
if (entityFrameOpen)
|
|
||||||
frameEntityPasses!.AbortEntityFrame();
|
|
||||||
_candidateObserver?.AbortPViewFrame();
|
_candidateObserver?.AbortPViewFrame();
|
||||||
throw;
|
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
|
// 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,
|
RetailPViewFrameInput ctx,
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result partition)
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
|
||||||
in RenderFrameView frameView)
|
|
||||||
{
|
{
|
||||||
if (_lookInFrames.Count == 0)
|
if (_lookInFrames.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -521,16 +475,7 @@ public sealed class RetailPViewRenderer
|
||||||
i,
|
i,
|
||||||
cellId,
|
cellId,
|
||||||
_cellStaticScratch);
|
_cellStaticScratch);
|
||||||
DrawEntityRouteOrLegacy(
|
passes.DrawEntityBucket(ctx, _cellStaticScratch, _oneCell);
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
frameEntityPasses,
|
|
||||||
in frameView,
|
|
||||||
RenderFrameCandidateRoute.LookInObject,
|
|
||||||
i,
|
|
||||||
cellId,
|
|
||||||
_cellStaticScratch,
|
|
||||||
_oneCell);
|
|
||||||
|
|
||||||
// The cell-particles pass for look-in cells — retail's
|
// The cell-particles pass for look-in cells — retail's
|
||||||
// nested DrawCells draws objects WITH their emitters.
|
// nested DrawCells draws objects WITH their emitters.
|
||||||
|
|
@ -547,9 +492,7 @@ public sealed class RetailPViewRenderer
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone)
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
|
||||||
in RenderFrameView frameView)
|
|
||||||
{
|
{
|
||||||
if (clipAssembly.OutsideViewSlices.Length == 0)
|
if (clipAssembly.OutsideViewSlices.Length == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -591,37 +534,15 @@ public sealed class RetailPViewRenderer
|
||||||
probeSliceIndex,
|
probeSliceIndex,
|
||||||
0,
|
0,
|
||||||
_outdoorStaticScratch);
|
_outdoorStaticScratch);
|
||||||
RenderFrameEntityDrawRequest? entityDraw =
|
|
||||||
frameEntityPasses is null
|
|
||||||
? null
|
|
||||||
: new RenderFrameEntityDrawRequest(
|
|
||||||
frameView,
|
|
||||||
RenderFrameCandidateRoute.LandscapeOutdoorStatic,
|
|
||||||
probeSliceIndex,
|
|
||||||
0,
|
|
||||||
ctx.PlayerLandblockId ?? 0);
|
|
||||||
probeSliceIndex++;
|
probeSliceIndex++;
|
||||||
passes.DrawLandscapeSlice(
|
passes.DrawLandscapeSlice(ctx, new RetailPViewLandscapeSliceContext(slice, _outdoorStaticScratch));
|
||||||
ctx,
|
|
||||||
new RetailPViewLandscapeSliceContext(
|
|
||||||
slice,
|
|
||||||
_outdoorStaticScratch)
|
|
||||||
{
|
|
||||||
EntityDraw = entityDraw,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #124: far-building look-ins draw HERE — still inside the landscape
|
// #124: far-building look-ins draw HERE — still inside the landscape
|
||||||
// stage (their punches mark against the terrain/exterior depth just
|
// stage (their punches mark against the terrain/exterior depth just
|
||||||
// drawn), strictly BEFORE the depth clear + seals below, matching
|
// drawn), strictly BEFORE the depth clear + seals below, matching
|
||||||
// retail's LScape::draw placement (DrawCells pc:432719 vs 432732/432785).
|
// retail's LScape::draw placement (DrawCells pc:432719 vs 432732/432785).
|
||||||
DrawBuildingLookIns(
|
DrawBuildingLookIns(ctx, passes, clipAssembly, partition);
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
clipAssembly,
|
|
||||||
partition,
|
|
||||||
frameEntityPasses,
|
|
||||||
in frameView);
|
|
||||||
|
|
||||||
// LATE phase (per slice): outside-stage dynamics' meshes (#118 — drawn
|
// LATE phase (per slice): outside-stage dynamics' meshes (#118 — drawn
|
||||||
// pre-clear so the seal protects their aperture pixels; AFTER the
|
// pre-clear so the seal protects their aperture pixels; AFTER the
|
||||||
|
|
@ -670,25 +591,9 @@ public sealed class RetailPViewRenderer
|
||||||
probeSliceIndex,
|
probeSliceIndex,
|
||||||
0,
|
0,
|
||||||
_outdoorStaticScratch);
|
_outdoorStaticScratch);
|
||||||
RenderFrameEntityDrawRequest? entityDraw =
|
|
||||||
frameEntityPasses is null
|
|
||||||
? null
|
|
||||||
: new RenderFrameEntityDrawRequest(
|
|
||||||
frameView,
|
|
||||||
RenderFrameCandidateRoute.LandscapeOutsideDynamic,
|
|
||||||
probeSliceIndex,
|
|
||||||
0,
|
|
||||||
ctx.PlayerLandblockId ?? 0);
|
|
||||||
probeSliceIndex++;
|
probeSliceIndex++;
|
||||||
passes.DrawLandscapeSliceLate(
|
passes.DrawLandscapeSliceLate(ctx, new RetailPViewLandscapeLateSliceContext(
|
||||||
ctx,
|
slice, _outdoorStaticScratch, _lateParticleOwnerScratch));
|
||||||
new RetailPViewLandscapeLateSliceContext(
|
|
||||||
slice,
|
|
||||||
_outdoorStaticScratch,
|
|
||||||
_lateParticleOwnerScratch)
|
|
||||||
{
|
|
||||||
EntityDraw = entityDraw,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// #131: UNATTACHED emitters (AttachedObjectId == 0 — portal swirls,
|
// #131: UNATTACHED emitters (AttachedObjectId == 0 — portal swirls,
|
||||||
|
|
@ -797,9 +702,7 @@ public sealed class RetailPViewRenderer
|
||||||
IRetailPViewPassExecutor passes,
|
IRetailPViewPassExecutor passes,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone,
|
||||||
bool rootIsOutdoor,
|
bool rootIsOutdoor)
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
|
||||||
in RenderFrameView frameView)
|
|
||||||
{
|
{
|
||||||
if (partition.Dynamics.Count == 0)
|
if (partition.Dynamics.Count == 0)
|
||||||
return;
|
return;
|
||||||
|
|
@ -846,16 +749,7 @@ public sealed class RetailPViewRenderer
|
||||||
0,
|
0,
|
||||||
_dynamicsScratch);
|
_dynamicsScratch);
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
DrawEntityRouteOrLegacy(
|
passes.DrawEntityBucket(ctx, _dynamicsScratch, visibleCellIds: null);
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
frameEntityPasses,
|
|
||||||
in frameView,
|
|
||||||
RenderFrameCandidateRoute.DynamicLast,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
_dynamicsScratch,
|
|
||||||
visibleCellIds: null);
|
|
||||||
|
|
||||||
// #121: dynamics' attached emitters (portal swirls, creature effects)
|
// #121: dynamics' attached emitters (portal swirls, creature effects)
|
||||||
// gate through the SAME cone-surviving owner set as their meshes —
|
// gate through the SAME cone-surviving owner set as their meshes —
|
||||||
|
|
@ -882,9 +776,7 @@ public sealed class RetailPViewRenderer
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
HashSet<uint> drawableCells,
|
HashSet<uint> drawableCells,
|
||||||
InteriorEntityPartition.Result partition,
|
InteriorEntityPartition.Result partition,
|
||||||
ViewconeCuller viewcone,
|
ViewconeCuller viewcone)
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
|
||||||
in RenderFrameView frameView)
|
|
||||||
{
|
{
|
||||||
// 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
|
||||||
|
|
@ -948,16 +840,7 @@ public sealed class RetailPViewRenderer
|
||||||
0,
|
0,
|
||||||
_allCellStatics);
|
_allCellStatics);
|
||||||
passes.UseIndoorMembershipOnlyRouting();
|
passes.UseIndoorMembershipOnlyRouting();
|
||||||
DrawEntityRouteOrLegacy(
|
passes.DrawEntityBucket(ctx, _allCellStatics, _cellObjCells);
|
||||||
ctx,
|
|
||||||
passes,
|
|
||||||
frameEntityPasses,
|
|
||||||
in frameView,
|
|
||||||
RenderFrameCandidateRoute.CellStatic,
|
|
||||||
0,
|
|
||||||
0,
|
|
||||||
_allCellStatics,
|
|
||||||
_cellObjCells);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cell-particle pass — consolidated across ALL visible cells into ONE
|
// Cell-particle pass — consolidated across ALL visible cells into ONE
|
||||||
|
|
@ -980,35 +863,6 @@ public sealed class RetailPViewRenderer
|
||||||
new RetailPViewCellSliceContext(0u, NoClipSlice, _allCellStatics));
|
new RetailPViewCellSliceContext(0u, NoClipSlice, _allCellStatics));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void DrawEntityRouteOrLegacy(
|
|
||||||
RetailPViewFrameInput frame,
|
|
||||||
IRetailPViewPassExecutor passes,
|
|
||||||
IRenderFrameEntityPassExecutor? frameEntityPasses,
|
|
||||||
in RenderFrameView frameView,
|
|
||||||
RenderFrameCandidateRoute route,
|
|
||||||
int routeIndex,
|
|
||||||
uint cellId,
|
|
||||||
IReadOnlyList<WorldEntity> legacyEntities,
|
|
||||||
HashSet<uint>? visibleCellIds)
|
|
||||||
{
|
|
||||||
if (frameEntityPasses is not null)
|
|
||||||
{
|
|
||||||
frameEntityPasses.DrawEntityRoute(
|
|
||||||
frame.Camera,
|
|
||||||
in frameView,
|
|
||||||
route,
|
|
||||||
routeIndex,
|
|
||||||
cellId,
|
|
||||||
frame.PlayerLandblockId ?? 0);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
passes.DrawEntityBucket(
|
|
||||||
frame,
|
|
||||||
legacyEntities,
|
|
||||||
visibleCellIds);
|
|
||||||
}
|
|
||||||
|
|
||||||
// T3 scratch lists (render thread only; cleared per use).
|
// T3 scratch lists (render thread only; cleared per use).
|
||||||
private readonly List<WorldEntity> _outdoorStaticScratch = new();
|
private readonly List<WorldEntity> _outdoorStaticScratch = new();
|
||||||
private readonly List<WorldEntity> _cellStaticScratch = new();
|
private readonly List<WorldEntity> _cellStaticScratch = new();
|
||||||
|
|
@ -1446,10 +1300,7 @@ public sealed class RetailPViewFrameResult
|
||||||
|
|
||||||
public readonly record struct RetailPViewLandscapeSliceContext(
|
public readonly record struct RetailPViewLandscapeSliceContext(
|
||||||
ClipViewSlice Slice,
|
ClipViewSlice Slice,
|
||||||
IReadOnlyList<WorldEntity> OutdoorEntities)
|
IReadOnlyList<WorldEntity> OutdoorEntities);
|
||||||
{
|
|
||||||
internal RenderFrameEntityDrawRequest? EntityDraw { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
/// <summary>#131/#132: the late landscape phase's per-slice payload —
|
/// <summary>#131/#132: the late landscape phase's per-slice payload —
|
||||||
/// outside-stage dynamics to mesh-draw, plus the full scene-particle owner
|
/// 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(
|
public readonly record struct RetailPViewLandscapeLateSliceContext(
|
||||||
ClipViewSlice Slice,
|
ClipViewSlice Slice,
|
||||||
IReadOnlyList<WorldEntity> Dynamics,
|
IReadOnlyList<WorldEntity> Dynamics,
|
||||||
IReadOnlyList<WorldEntity> ParticleOwners)
|
IReadOnlyList<WorldEntity> ParticleOwners);
|
||||||
{
|
|
||||||
internal RenderFrameEntityDrawRequest? EntityDraw { get; init; }
|
|
||||||
}
|
|
||||||
|
|
||||||
public readonly record struct RetailPViewCellSliceContext(
|
public readonly record struct RetailPViewCellSliceContext(
|
||||||
uint CellId,
|
uint CellId,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,7 @@ namespace AcDream.App.Rendering.Scene.Arch;
|
||||||
/// projection. Arch identities never leave this namespace; callers only see
|
/// projection. Arch identities never leave this namespace; callers only see
|
||||||
/// generation-checked acdream value types.
|
/// generation-checked acdream value types.
|
||||||
///
|
///
|
||||||
/// Slice F introduced this owner in non-drawing shadow mode; Slice G supplies
|
/// Slice F1 deliberately leaves this owner unbound and non-drawing.
|
||||||
/// its borrowed frame product to the production renderer.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
{
|
{
|
||||||
|
|
@ -36,7 +35,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
private ArchWorld _world;
|
private ArchWorld _world;
|
||||||
private RenderProjectionCounts _counts;
|
private RenderProjectionCounts _counts;
|
||||||
private ulong _lastAppliedJournalSequence;
|
private ulong _lastAppliedJournalSequence;
|
||||||
private ulong _indexRevision = 1;
|
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
public ArchRenderScene(RenderSceneGeneration initialGeneration)
|
public ArchRenderScene(RenderSceneGeneration initialGeneration)
|
||||||
|
|
@ -227,7 +225,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
_counts = default;
|
_counts = default;
|
||||||
_lastAppliedJournalSequence = 0;
|
_lastAppliedJournalSequence = 0;
|
||||||
Generation = replacementGeneration;
|
Generation = replacementGeneration;
|
||||||
AdvanceIndexRevision();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Dispose()
|
public void Dispose()
|
||||||
|
|
@ -277,13 +274,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
_dirty.Count);
|
_dirty.Count);
|
||||||
}
|
}
|
||||||
|
|
||||||
ulong IRenderSceneQuerySource.GetIndexRevision(
|
|
||||||
RenderSceneGeneration generation)
|
|
||||||
{
|
|
||||||
EnsureQueryGeneration(generation);
|
|
||||||
return _indexRevision;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool IRenderSceneQuerySource.TryGet(
|
bool IRenderSceneQuerySource.TryGet(
|
||||||
RenderSceneGeneration generation,
|
RenderSceneGeneration generation,
|
||||||
RenderProjectionId id,
|
RenderProjectionId id,
|
||||||
|
|
@ -601,12 +591,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
in RenderProjectionRecord prior,
|
in RenderProjectionRecord prior,
|
||||||
in RenderProjectionRecord current)
|
in RenderProjectionRecord current)
|
||||||
{
|
{
|
||||||
if (IndexMembershipEquals(in prior, in current))
|
|
||||||
{
|
|
||||||
SynchronizeDirtyIndex(in current);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
RemoveFromIndices(in prior);
|
RemoveFromIndices(in prior);
|
||||||
AddToIndices(in current);
|
AddToIndices(in current);
|
||||||
}
|
}
|
||||||
|
|
@ -651,7 +635,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
_lightCandidates.Add(record.Id);
|
_lightCandidates.Add(record.Id);
|
||||||
if (record.DirtyMask != RenderDirtyMask.None)
|
if (record.DirtyMask != RenderDirtyMask.None)
|
||||||
_dirty.Add(record.Id);
|
_dirty.Add(record.Id);
|
||||||
AdvanceIndexRevision();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RemoveFromIndices(in RenderProjectionRecord record)
|
private void RemoveFromIndices(in RenderProjectionRecord record)
|
||||||
|
|
@ -667,46 +650,6 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
||||||
_dirty.Remove(record.Id);
|
_dirty.Remove(record.Id);
|
||||||
RemoveCell(_cellStatics, record.Residency.FullCellId, record.Id);
|
RemoveCell(_cellStatics, record.Residency.FullCellId, record.Id);
|
||||||
RemoveCell(_cellDynamics, 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) =>
|
private static bool IsDynamic(RenderProjectionClass projectionClass) =>
|
||||||
|
|
|
||||||
|
|
@ -455,7 +455,6 @@ internal interface IRenderSceneQuerySource
|
||||||
{
|
{
|
||||||
RenderProjectionCounts GetCounts(RenderSceneGeneration generation);
|
RenderProjectionCounts GetCounts(RenderSceneGeneration generation);
|
||||||
RenderSceneIndexCounts GetIndexCounts(RenderSceneGeneration generation);
|
RenderSceneIndexCounts GetIndexCounts(RenderSceneGeneration generation);
|
||||||
ulong GetIndexRevision(RenderSceneGeneration generation);
|
|
||||||
|
|
||||||
bool TryGet(
|
bool TryGet(
|
||||||
RenderSceneGeneration generation,
|
RenderSceneGeneration generation,
|
||||||
|
|
@ -504,9 +503,6 @@ internal readonly struct RenderSceneQuery
|
||||||
public RenderSceneIndexCounts IndexCounts =>
|
public RenderSceneIndexCounts IndexCounts =>
|
||||||
Source.GetIndexCounts(Generation);
|
Source.GetIndexCounts(Generation);
|
||||||
|
|
||||||
public ulong IndexRevision =>
|
|
||||||
Source.GetIndexRevision(Generation);
|
|
||||||
|
|
||||||
public bool TryGet(
|
public bool TryGet(
|
||||||
RenderProjectionId id,
|
RenderProjectionId id,
|
||||||
out RenderProjectionRecord record) =>
|
out RenderProjectionRecord record) =>
|
||||||
|
|
|
||||||
|
|
@ -113,15 +113,16 @@ internal interface IRenderFrameProductSnapshotSource
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owns the same-frame PView product. Diagnostic composition may compare it
|
/// G1 compare-only owner. It builds the candidate frame product directly from
|
||||||
/// with the retired current-path observer; production borrows the exact same
|
/// the incremental scene after the accepted PView traversal, borrows it in the
|
||||||
/// arena and supplies its ordered ranges directly to the dispatcher.
|
/// same render frame, and compares route membership against the current-path
|
||||||
|
/// observer. It never supplies a production draw input.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RenderScenePViewFrameProductController :
|
internal sealed class RenderScenePViewFrameProductController :
|
||||||
IRenderFrameProductSnapshotSource
|
IRenderFrameProductSnapshotSource
|
||||||
{
|
{
|
||||||
private readonly RenderSceneShadowRuntime _shadow;
|
private readonly RenderSceneShadowRuntime _shadow;
|
||||||
private readonly CurrentRenderSceneOracle? _current;
|
private readonly CurrentRenderSceneOracle _current;
|
||||||
private readonly WbDrawDispatcher? _dispatcher;
|
private readonly WbDrawDispatcher? _dispatcher;
|
||||||
private readonly RenderScenePViewFrameBuilder _builder = new();
|
private readonly RenderScenePViewFrameBuilder _builder = new();
|
||||||
private readonly RenderFrameExchange _exchange = new();
|
private readonly RenderFrameExchange _exchange = new();
|
||||||
|
|
@ -154,12 +155,12 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
|
|
||||||
public RenderScenePViewFrameProductController(
|
public RenderScenePViewFrameProductController(
|
||||||
RenderSceneShadowRuntime shadow,
|
RenderSceneShadowRuntime shadow,
|
||||||
CurrentRenderSceneOracle? current = null,
|
CurrentRenderSceneOracle current,
|
||||||
Action<string>? log = null,
|
Action<string>? log = null,
|
||||||
WbDrawDispatcher? dispatcher = null)
|
WbDrawDispatcher? dispatcher = null)
|
||||||
{
|
{
|
||||||
_shadow = shadow ?? throw new ArgumentNullException(nameof(shadow));
|
_shadow = shadow ?? throw new ArgumentNullException(nameof(shadow));
|
||||||
_current = current;
|
_current = current ?? throw new ArgumentNullException(nameof(current));
|
||||||
_log = log;
|
_log = log;
|
||||||
_dispatcher = dispatcher;
|
_dispatcher = dispatcher;
|
||||||
Snapshot = RenderFrameProductComparisonSnapshot.Disabled with
|
Snapshot = RenderFrameProductComparisonSnapshot.Disabled with
|
||||||
|
|
@ -170,56 +171,6 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
|
|
||||||
public RenderFrameProductComparisonSnapshot Snapshot { get; private set; }
|
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<PortalVisibilityFrame> lookInFrames,
|
|
||||||
HashSet<uint> drawableCells,
|
|
||||||
IRetailPViewCellSource cells,
|
|
||||||
HashSet<uint>? 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(
|
public void BuildAndCompare(
|
||||||
PortalVisibilityFrame portalFrame,
|
PortalVisibilityFrame portalFrame,
|
||||||
ClipFrameAssembly clipAssembly,
|
ClipFrameAssembly clipAssembly,
|
||||||
|
|
@ -232,7 +183,18 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
bool rootIsOutdoor,
|
bool rootIsOutdoor,
|
||||||
Vector3 cameraWorldPosition = default)
|
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,
|
portalFrame,
|
||||||
clipAssembly,
|
clipAssembly,
|
||||||
viewcone,
|
viewcone,
|
||||||
|
|
@ -241,6 +203,10 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
cells,
|
cells,
|
||||||
animatedEntityIds,
|
animatedEntityIds,
|
||||||
rootIsOutdoor);
|
rootIsOutdoor);
|
||||||
|
_builder.Build(_exchange, frameSequence, in input);
|
||||||
|
RenderFrameView view = _exchange.BorrowLatest(
|
||||||
|
scene.Generation,
|
||||||
|
frameSequence);
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
Compare(
|
Compare(
|
||||||
|
|
@ -250,92 +216,26 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
}
|
}
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
Release(in view);
|
_exchange.Release(in view);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void CompleteProduction(in RenderFrameView view)
|
private void Compare(
|
||||||
{
|
in RenderFrameView view,
|
||||||
_packedClassification =
|
uint tupleLandblockId,
|
||||||
_dispatcher?.PackedClassificationSnapshot ?? default;
|
Vector3 cameraWorldPosition)
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
_comparisonCount = checked(_comparisonCount + 1);
|
_comparisonCount = checked(_comparisonCount + 1);
|
||||||
_expected.Clear();
|
_expected.Clear();
|
||||||
_actual.Clear();
|
_actual.Clear();
|
||||||
|
|
||||||
IReadOnlyList<CurrentRenderPViewCandidateFingerprint> expected =
|
IReadOnlyList<CurrentRenderPViewCandidateFingerprint> expected =
|
||||||
Current.PViewCandidates;
|
_current.PViewCandidates;
|
||||||
if (_expected.Capacity < expected.Count)
|
if (_expected.Capacity < expected.Count)
|
||||||
_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 =
|
CurrentRenderPViewCandidateFingerprint candidate = expected[i];
|
||||||
expected[index];
|
|
||||||
CurrentRenderProjectionFingerprint projection =
|
CurrentRenderProjectionFingerprint projection =
|
||||||
candidate.Projection;
|
candidate.Projection;
|
||||||
_expected.Add(new CandidateKey(
|
_expected.Add(new CandidateKey(
|
||||||
|
|
@ -345,15 +245,11 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
ExpectedId(in projection)));
|
ExpectedId(in projection)));
|
||||||
}
|
}
|
||||||
|
|
||||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
ReadOnlySpan<RenderFrameCandidateRange> ranges = view.RouteRanges;
|
||||||
view.RouteRanges;
|
ReadOnlySpan<RenderProjectionRecord> records = view.RouteCandidates;
|
||||||
ReadOnlySpan<RenderProjectionRecord> records =
|
|
||||||
view.RouteCandidates;
|
|
||||||
if (_actual.Capacity < records.Length)
|
if (_actual.Capacity < records.Length)
|
||||||
_actual.Capacity = records.Length;
|
_actual.Capacity = records.Length;
|
||||||
for (int rangeIndex = 0;
|
for (int rangeIndex = 0; rangeIndex < ranges.Length; rangeIndex++)
|
||||||
rangeIndex < ranges.Length;
|
|
||||||
rangeIndex++)
|
|
||||||
{
|
{
|
||||||
RenderFrameCandidateRange range = ranges[rangeIndex];
|
RenderFrameCandidateRange range = ranges[rangeIndex];
|
||||||
int end = checked(range.Offset + range.Count);
|
int end = checked(range.Offset + range.Count);
|
||||||
|
|
@ -389,32 +285,10 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
{
|
{
|
||||||
_firstMismatch = mismatch;
|
_firstMismatch = mismatch;
|
||||||
_log?.Invoke(
|
_log?.Invoke(
|
||||||
"[render-frame-product] first mismatch: "
|
"[render-frame-product] first mismatch: " + 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 =
|
PackedInputComparison packed =
|
||||||
ComparePackedInput(in view, tupleLandblockId);
|
ComparePackedInput(in view, tupleLandblockId);
|
||||||
ClassifiedOutputComparison classified =
|
ClassifiedOutputComparison classified =
|
||||||
|
|
@ -438,7 +312,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
Enabled: true,
|
Enabled: true,
|
||||||
ProductFrameSequence: view.FrameSequence,
|
ProductFrameSequence: view.FrameSequence,
|
||||||
CurrentPViewFrameSequence:
|
CurrentPViewFrameSequence:
|
||||||
Current.Snapshot.CompletedPViewFrameSequence,
|
_current.Snapshot.CompletedPViewFrameSequence,
|
||||||
ComparisonCount: _comparisonCount,
|
ComparisonCount: _comparisonCount,
|
||||||
SuccessfulComparisonCount: _successfulComparisonCount,
|
SuccessfulComparisonCount: _successfulComparisonCount,
|
||||||
ExpectedCandidateCount: _expected.Count,
|
ExpectedCandidateCount: _expected.Count,
|
||||||
|
|
@ -521,7 +395,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
_packedClassification =
|
_packedClassification =
|
||||||
dispatcher.PackedClassificationSnapshot;
|
dispatcher.PackedClassificationSnapshot;
|
||||||
IReadOnlyList<CurrentRenderDispatcherSubmission> expected =
|
IReadOnlyList<CurrentRenderDispatcherSubmission> expected =
|
||||||
Current.DispatcherSubmissions;
|
_current.DispatcherSubmissions;
|
||||||
IReadOnlyList<CurrentRenderDispatcherSubmission> actual =
|
IReadOnlyList<CurrentRenderDispatcherSubmission> actual =
|
||||||
dispatcher.PackedDispatcherSubmissions;
|
dispatcher.PackedDispatcherSubmissions;
|
||||||
RenderSceneHash128 expectedDigest =
|
RenderSceneHash128 expectedDigest =
|
||||||
|
|
@ -552,7 +426,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
}
|
}
|
||||||
|
|
||||||
IReadOnlyList<CurrentRenderSelectionFingerprint>
|
IReadOnlyList<CurrentRenderSelectionFingerprint>
|
||||||
expectedSelection = Current.SelectionParts;
|
expectedSelection = _current.SelectionParts;
|
||||||
IReadOnlyList<CurrentRenderSelectionFingerprint>
|
IReadOnlyList<CurrentRenderSelectionFingerprint>
|
||||||
actualSelection = dispatcher.PackedSelectionParts;
|
actualSelection = dispatcher.PackedSelectionParts;
|
||||||
BuildSelectionKeys(
|
BuildSelectionKeys(
|
||||||
|
|
@ -754,7 +628,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
_packedActual.Clear();
|
_packedActual.Clear();
|
||||||
|
|
||||||
IReadOnlyList<CurrentRenderDispatcherFingerprint> expected =
|
IReadOnlyList<CurrentRenderDispatcherFingerprint> expected =
|
||||||
Current.DispatcherCandidates;
|
_current.DispatcherCandidates;
|
||||||
if (_packedExpected.Capacity < expected.Count)
|
if (_packedExpected.Capacity < expected.Count)
|
||||||
_packedExpected.Capacity = expected.Count;
|
_packedExpected.Capacity = expected.Count;
|
||||||
for (int index = 0; index < expected.Count; index++)
|
for (int index = 0; index < expected.Count; index++)
|
||||||
|
|
@ -876,10 +750,10 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
|
|
||||||
private string? ComparePackedInputKeys(int actualDrawCount)
|
private string? ComparePackedInputKeys(int actualDrawCount)
|
||||||
{
|
{
|
||||||
if (Current.Snapshot.DispatcherDrawCount != actualDrawCount)
|
if (_current.Snapshot.DispatcherDrawCount != actualDrawCount)
|
||||||
{
|
{
|
||||||
return $"field=drawCount expected="
|
return $"field=drawCount expected="
|
||||||
+ $"{Current.Snapshot.DispatcherDrawCount} "
|
+ $"{_current.Snapshot.DispatcherDrawCount} "
|
||||||
+ $"actual={actualDrawCount}";
|
+ $"actual={actualDrawCount}";
|
||||||
}
|
}
|
||||||
if (_packedExpected.Count != _packedActual.Count)
|
if (_packedExpected.Count != _packedActual.Count)
|
||||||
|
|
@ -1017,11 +891,6 @@ internal sealed class RenderScenePViewFrameProductController :
|
||||||
RenderSceneHash128 ExpectedDigest,
|
RenderSceneHash128 ExpectedDigest,
|
||||||
RenderSceneHash128 ActualDigest);
|
RenderSceneHash128 ActualDigest);
|
||||||
|
|
||||||
private readonly record struct CandidateComparison(
|
|
||||||
bool Successful,
|
|
||||||
RenderSceneHash128 ExpectedDigest,
|
|
||||||
RenderSceneHash128 ActualDigest);
|
|
||||||
|
|
||||||
private readonly record struct ClassifiedOutputComparison(
|
private readonly record struct ClassifiedOutputComparison(
|
||||||
bool Successful,
|
bool Successful,
|
||||||
int ExpectedDrawCount,
|
int ExpectedDrawCount,
|
||||||
|
|
@ -1124,19 +993,11 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
private RenderProjectionRecord[] _outdoor = [];
|
private RenderProjectionRecord[] _outdoor = [];
|
||||||
private RenderProjectionRecord[] _dynamics = [];
|
private RenderProjectionRecord[] _dynamics = [];
|
||||||
private RenderProjectionRecord[] _cell = [];
|
private RenderProjectionRecord[] _cell = [];
|
||||||
private RenderProjectionRecord[] _dirty = [];
|
|
||||||
private RenderProjectionRecord[] _survivors = [];
|
private RenderProjectionRecord[] _survivors = [];
|
||||||
private RenderProjectionRecord[] _cellRoute = [];
|
private RenderProjectionRecord[] _cellRoute = [];
|
||||||
private readonly Dictionary<RenderProjectionId, int>
|
|
||||||
_outdoorPositions = [];
|
|
||||||
private readonly Dictionary<RenderProjectionId, int>
|
|
||||||
_dynamicPositions = [];
|
|
||||||
private int _outdoorCount;
|
private int _outdoorCount;
|
||||||
private int _dynamicCount;
|
private int _dynamicCount;
|
||||||
private int _cellRouteCount;
|
private int _cellRouteCount;
|
||||||
private int _dirtyCount;
|
|
||||||
private RenderSceneGeneration _indexGeneration;
|
|
||||||
private ulong _indexRevision;
|
|
||||||
|
|
||||||
public void Build(
|
public void Build(
|
||||||
RenderFrameExchange exchange,
|
RenderFrameExchange exchange,
|
||||||
|
|
@ -1162,7 +1023,6 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
BuildCellStaticRoute(writer, in input);
|
BuildCellStaticRoute(writer, in input);
|
||||||
BuildDynamicLastRoute(writer, in input);
|
BuildDynamicLastRoute(writer, in input);
|
||||||
writer.Publish();
|
writer.Publish();
|
||||||
AcknowledgeCachedDirtyRecords();
|
|
||||||
}
|
}
|
||||||
catch
|
catch
|
||||||
{
|
{
|
||||||
|
|
@ -1174,97 +1034,21 @@ internal sealed class RenderScenePViewFrameBuilder
|
||||||
private void LoadSceneIndices(RenderSceneQuery scene)
|
private void LoadSceneIndices(RenderSceneQuery scene)
|
||||||
{
|
{
|
||||||
RenderSceneIndexCounts counts = scene.IndexCounts;
|
RenderSceneIndexCounts counts = scene.IndexCounts;
|
||||||
ulong revision = scene.IndexRevision;
|
EnsureCapacity(ref _outdoor, counts.OutdoorStatic);
|
||||||
if (scene.Generation != _indexGeneration
|
_outdoorCount = scene.CopyIndexTo(
|
||||||
|| revision != _indexRevision)
|
RenderSceneIndex.OutdoorStatic,
|
||||||
{
|
_outdoor);
|
||||||
EnsureCapacity(ref _outdoor, counts.OutdoorStatic);
|
_outdoorCount = CompactAndSort(
|
||||||
_outdoorCount = scene.CopyIndexTo(
|
_outdoor,
|
||||||
RenderSceneIndex.OutdoorStatic,
|
_outdoorCount);
|
||||||
_outdoor);
|
|
||||||
_outdoorCount = CompactAndSort(
|
|
||||||
_outdoor,
|
|
||||||
_outdoorCount);
|
|
||||||
BuildPositionIndex(
|
|
||||||
_outdoor,
|
|
||||||
_outdoorCount,
|
|
||||||
_outdoorPositions);
|
|
||||||
|
|
||||||
EnsureCapacity(ref _dynamics, counts.Dynamic);
|
EnsureCapacity(ref _dynamics, counts.Dynamic);
|
||||||
_dynamicCount = scene.CopyIndexTo(
|
_dynamicCount = scene.CopyIndexTo(
|
||||||
RenderSceneIndex.Dynamic,
|
RenderSceneIndex.Dynamic,
|
||||||
_dynamics);
|
_dynamics);
|
||||||
_dynamicCount = CompactAndSort(
|
_dynamicCount = CompactAndSort(
|
||||||
_dynamics,
|
_dynamics,
|
||||||
_dynamicCount);
|
_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<RenderProjectionId, int> positions)
|
|
||||||
{
|
|
||||||
positions.Clear();
|
|
||||||
positions.EnsureCapacity(count);
|
|
||||||
for (int index = 0; index < count; index++)
|
|
||||||
positions.Add(records[index].Id, index);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void BuildOutdoorRoutes(
|
private void BuildOutdoorRoutes(
|
||||||
|
|
|
||||||
|
|
@ -50,12 +50,10 @@ internal interface IRenderSceneShadowSnapshotSource
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Owner of the derived render projection. Slice F introduced it as a
|
/// Diagnostic-only owner of Slice F's non-drawing render projection. The
|
||||||
/// diagnostic shadow; Slice G keeps the same generation/incarnation journal
|
/// normal runtime never constructs this type. Accepted world/live lifecycle
|
||||||
/// boundary while lending its frame product to production drawing. Accepted
|
/// edges append to one ordered journal, and the update thread drains it only
|
||||||
/// world/live lifecycle edges append to one ordered journal, and the update
|
/// after current-frame animation, network, and attachment reconciliation.
|
||||||
/// thread drains it only after current-frame animation, network, and
|
|
||||||
/// attachment reconciliation.
|
|
||||||
/// </summary>
|
/// </summary>
|
||||||
internal sealed class RenderSceneShadowRuntime : IDisposable
|
internal sealed class RenderSceneShadowRuntime : IDisposable
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,4 @@
|
||||||
using System.Numerics;
|
using System.Numerics;
|
||||||
using AcDream.Core.Rendering;
|
|
||||||
using AcDream.App.Rendering.Scene;
|
using AcDream.App.Rendering.Scene;
|
||||||
using AcDream.App.Rendering.Selection;
|
using AcDream.App.Rendering.Selection;
|
||||||
using AcDream.Core.Lighting;
|
using AcDream.Core.Lighting;
|
||||||
|
|
@ -11,10 +10,10 @@ using DatReaderWriter.Enums;
|
||||||
namespace AcDream.App.Rendering.Wb;
|
namespace AcDream.App.Rendering.Wb;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Scene-frame classification and production submission. The retained group
|
/// G2 compare-only classification of the scene-built frame product. This file
|
||||||
/// storage is also consumed by the G2/G3 compare oracle, while production
|
/// deliberately shares the production dispatcher's exact mesh, texture,
|
||||||
/// routes reuse the dispatcher's exact mesh, texture, light, translucency,
|
/// light, translucency, selection, grouping, and ordering owners while keeping
|
||||||
/// selection, upload, and draw owners.
|
/// its group storage separate. It never uploads or draws.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public sealed unsafe partial class WbDrawDispatcher
|
public sealed unsafe partial class WbDrawDispatcher
|
||||||
{
|
{
|
||||||
|
|
@ -36,10 +35,6 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
private long _packedGroupFrame;
|
private long _packedGroupFrame;
|
||||||
private long _nextPackedGroupRegistration = 1;
|
private long _nextPackedGroupRegistration = 1;
|
||||||
private int _nextPackedInstanceSubmissionOrder;
|
private int _nextPackedInstanceSubmissionOrder;
|
||||||
private bool _packedProductionFrameOpen;
|
|
||||||
private RenderSceneGeneration _packedProductionGeneration;
|
|
||||||
private ulong _packedProductionFrameSequence;
|
|
||||||
private int _packedProductionNextRange;
|
|
||||||
|
|
||||||
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
||||||
PackedDispatcherSubmissions => _packedSubmissions;
|
PackedDispatcherSubmissions => _packedSubmissions;
|
||||||
|
|
@ -56,28 +51,104 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
uint tupleLandblockId,
|
uint tupleLandblockId,
|
||||||
Vector3 cameraWorldPosition)
|
Vector3 cameraWorldPosition)
|
||||||
{
|
{
|
||||||
if (_packedProductionFrameOpen)
|
if (_packedGroupFrame == long.MaxValue)
|
||||||
{
|
{
|
||||||
throw new InvalidOperationException(
|
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<RenderFrameEntityCandidate> 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<RenderFrameMeshPart> meshParts = view.MeshParts;
|
||||||
|
ReadOnlySpan<RenderProjectionRecord> routeCandidates =
|
||||||
|
view.RouteCandidates;
|
||||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||||
view.RouteRanges;
|
view.RouteRanges;
|
||||||
for (int rangeIndex = 0;
|
for (int rangeIndex = 0;
|
||||||
rangeIndex < ranges.Length;
|
rangeIndex < ranges.Length;
|
||||||
rangeIndex++)
|
rangeIndex++)
|
||||||
{
|
{
|
||||||
PackedRangeClassification classified =
|
_nextPackedInstanceSubmissionOrder = 0;
|
||||||
ClassifyPackedRange(
|
uint anyVao = 0;
|
||||||
in view,
|
foreach (InstanceGroup group in _packedGroups.Values)
|
||||||
rangeIndex,
|
group.ClearPerInstanceData();
|
||||||
tupleLandblockId,
|
|
||||||
publishSelection: false);
|
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(
|
bool deferTransparent = ShouldDeferPackedTransparent(
|
||||||
classified.AnyVao,
|
anyVao,
|
||||||
_alphaQueue?.IsCollecting == true);
|
_alphaQueue?.IsCollecting == true);
|
||||||
InstanceLayoutCounts counts = PartitionInstanceGroups(
|
InstanceLayoutCounts counts = PartitionInstanceGroups(
|
||||||
_packedGroups.Values,
|
_packedGroups.Values,
|
||||||
|
|
@ -105,236 +176,11 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
_packedClassificationCache.EndFrame();
|
_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<RenderFrameCandidateRange> 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<RenderFrameEntityCandidate> 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<RenderFrameCandidateRange> 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<RenderProjectionRecord> routeCandidates =
|
|
||||||
view.RouteCandidates;
|
|
||||||
ReadOnlySpan<RenderFrameMeshPart> 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(
|
private void ClassifyPackedEntity(
|
||||||
in RenderProjectionRecord projection,
|
in RenderProjectionRecord projection,
|
||||||
in RenderInstanceCandidate entity,
|
in RenderInstanceCandidate entity,
|
||||||
ReadOnlySpan<RenderFrameMeshPart> meshParts,
|
ReadOnlySpan<RenderFrameMeshPart> meshParts,
|
||||||
ref uint anyVao,
|
ref uint anyVao)
|
||||||
bool publishSelection)
|
|
||||||
{
|
{
|
||||||
(uint slot, bool culled) = ResolveSlotForFrame(
|
(uint slot, bool culled) = ResolveSlotForFrame(
|
||||||
_clipRoutingActive,
|
_clipRoutingActive,
|
||||||
|
|
@ -385,8 +231,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
slot,
|
slot,
|
||||||
lights,
|
lights,
|
||||||
indoor,
|
indoor,
|
||||||
selectionLighting,
|
selectionLighting);
|
||||||
publishSelection);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -427,8 +272,6 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
if (renderData is null)
|
if (renderData is null)
|
||||||
{
|
{
|
||||||
reusableAcrossFrames = false;
|
reusableAcrossFrames = false;
|
||||||
if (_missRequested.Add(meshRef.GfxObjId))
|
|
||||||
_meshAdapter.EnsureLoaded(meshRef.GfxObjId);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if (anyVao == 0)
|
if (anyVao == 0)
|
||||||
|
|
@ -448,8 +291,6 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
if (partData is null)
|
if (partData is null)
|
||||||
{
|
{
|
||||||
reusableAcrossFrames = false;
|
reusableAcrossFrames = false;
|
||||||
if (_missRequested.Add(gfxObjId))
|
|
||||||
_meshAdapter.EnsureLoaded(gfxObjId);
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -493,8 +334,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
in entity,
|
in entity,
|
||||||
selectionPartIndex,
|
selectionPartIndex,
|
||||||
(uint)gfxObjId,
|
(uint)gfxObjId,
|
||||||
model,
|
model);
|
||||||
publishSelection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
|
|
@ -534,8 +374,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
in entity,
|
in entity,
|
||||||
partIndex,
|
partIndex,
|
||||||
meshRef.GfxObjId,
|
meshRef.GfxObjId,
|
||||||
model,
|
model);
|
||||||
publishSelection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -642,8 +481,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
uint slot,
|
uint slot,
|
||||||
InstanceLightSet lights,
|
InstanceLightSet lights,
|
||||||
bool indoor,
|
bool indoor,
|
||||||
Vector2 selectionLighting,
|
Vector2 selectionLighting)
|
||||||
bool publishSelection)
|
|
||||||
{
|
{
|
||||||
for (int i = 0; i < entry.Batches.Count; i++)
|
for (int i = 0; i < entry.Batches.Count; i++)
|
||||||
{
|
{
|
||||||
|
|
@ -666,8 +504,7 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
in entity,
|
in entity,
|
||||||
part.PartIndex,
|
part.PartIndex,
|
||||||
part.GfxObjId,
|
part.GfxObjId,
|
||||||
part.RestPose * entity.RootWorld,
|
part.RestPose * entity.RootWorld);
|
||||||
publishSelection);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -756,29 +593,13 @@ public sealed unsafe partial class WbDrawDispatcher
|
||||||
in RenderInstanceCandidate entity,
|
in RenderInstanceCandidate entity,
|
||||||
int partIndex,
|
int partIndex,
|
||||||
uint gfxObjId,
|
uint gfxObjId,
|
||||||
Matrix4x4 localToWorld,
|
Matrix4x4 localToWorld)
|
||||||
bool publishSelection)
|
|
||||||
{
|
{
|
||||||
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
|
if (_selectionSink is not IRetailSelectionRenderOracle oracle
|
||||||
|
|| !_packedSelectionKeys.Add(new PackedSelectionKey(
|
||||||
|
entity.LocalEntityId,
|
||||||
|
partIndex,
|
||||||
|
gfxObjId))
|
||||||
|| !oracle.TryCreateVisiblePart(
|
|| !oracle.TryCreateVisiblePart(
|
||||||
entity.ServerGuid,
|
entity.ServerGuid,
|
||||||
entity.LocalEntityId,
|
entity.LocalEntityId,
|
||||||
|
|
|
||||||
|
|
@ -1483,10 +1483,41 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
|
||||||
HashSet<uint>? animatedEntityIds = null,
|
HashSet<uint>? animatedEntityIds = null,
|
||||||
EntitySet set = EntitySet.All)
|
EntitySet set = EntitySet.All)
|
||||||
{
|
{
|
||||||
bool diag = BeginEntityDispatch(
|
_shader.Use();
|
||||||
camera,
|
_selectionLighting?.TickLighting();
|
||||||
out Matrix4x4 vp,
|
_indoorProbeFrameCounter++;
|
||||||
out Vector3 camPos);
|
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 ──────────────
|
// ── Phase 1: clear groups, walk entities, build groups ──────────────
|
||||||
// Draw is invoked several times per frame (landscape slices, late
|
// 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)
|
if (RenderingDiagnostics.ProbeClipRouteEnabled && _clipRoutingActive)
|
||||||
EmitClipRouteDispatchProbe(probeCulledEntities);
|
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<InstanceGroup> groups,
|
|
||||||
EntitySet set,
|
|
||||||
int entitiesWalked,
|
|
||||||
int tupleCount,
|
|
||||||
bool diag,
|
|
||||||
bool observeCurrentPath)
|
|
||||||
{
|
|
||||||
// Nothing visible — skip the GL pass entirely.
|
// Nothing visible — skip the GL pass entirely.
|
||||||
if (anyVao == 0)
|
if (anyVao == 0)
|
||||||
{
|
{
|
||||||
LastDrawStats = new DrawStats(set, entitiesWalked, tupleCount, 0, 0, 0, 0, 0, 0);
|
LastDrawStats = new DrawStats(set, walkResult.EntitiesWalked, _walkScratch.Count, 0, 0, 0, 0, 0, 0);
|
||||||
ObserveClassifiedDispatcherSubmission(observeCurrentPath,
|
ObserveCurrentDispatcherSubmission(
|
||||||
visibleInstanceCount: 0,
|
visibleInstanceCount: 0,
|
||||||
immediateInstanceCount: 0,
|
immediateInstanceCount: 0,
|
||||||
deferTransparent: false,
|
deferTransparent: false,
|
||||||
|
|
@ -2104,7 +2075,7 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
|
||||||
// ── Phase 3: assign FirstInstance per group, lay matrices contiguously, sort opaque ──
|
// ── Phase 3: assign FirstInstance per group, lay matrices contiguously, sort opaque ──
|
||||||
bool deferTransparent = _alphaQueue?.IsCollecting == true;
|
bool deferTransparent = _alphaQueue?.IsCollecting == true;
|
||||||
var instanceCounts = PartitionInstanceGroups(
|
var instanceCounts = PartitionInstanceGroups(
|
||||||
groups,
|
_groups.Values,
|
||||||
deferTransparent,
|
deferTransparent,
|
||||||
camPos,
|
camPos,
|
||||||
_opaqueDraws,
|
_opaqueDraws,
|
||||||
|
|
@ -2113,8 +2084,8 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
|
||||||
int immediateInstances = instanceCounts.ImmediateInstances;
|
int immediateInstances = instanceCounts.ImmediateInstances;
|
||||||
if (totalInstances == 0)
|
if (totalInstances == 0)
|
||||||
{
|
{
|
||||||
LastDrawStats = new DrawStats(set, entitiesWalked, tupleCount, 0, 0, 0, 0, 0, 0);
|
LastDrawStats = new DrawStats(set, walkResult.EntitiesWalked, _walkScratch.Count, 0, 0, 0, 0, 0, 0);
|
||||||
ObserveClassifiedDispatcherSubmission(observeCurrentPath,
|
ObserveCurrentDispatcherSubmission(
|
||||||
visibleInstanceCount: 0,
|
visibleInstanceCount: 0,
|
||||||
immediateInstanceCount: 0,
|
immediateInstanceCount: 0,
|
||||||
deferTransparent,
|
deferTransparent,
|
||||||
|
|
@ -2231,15 +2202,15 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
|
||||||
_transparentByteOffset = layout.TransparentByteOffset;
|
_transparentByteOffset = layout.TransparentByteOffset;
|
||||||
LastDrawStats = new DrawStats(
|
LastDrawStats = new DrawStats(
|
||||||
set,
|
set,
|
||||||
entitiesWalked,
|
walkResult.EntitiesWalked,
|
||||||
tupleCount,
|
_walkScratch.Count,
|
||||||
totalInstances,
|
totalInstances,
|
||||||
totalDraws,
|
totalDraws,
|
||||||
cullRuns,
|
cullRuns,
|
||||||
_opaqueDrawCount,
|
_opaqueDrawCount,
|
||||||
_transparentDrawCount,
|
_transparentDrawCount,
|
||||||
totalTriangles);
|
totalTriangles);
|
||||||
ObserveClassifiedDispatcherSubmission(observeCurrentPath,
|
ObserveCurrentDispatcherSubmission(
|
||||||
totalInstances,
|
totalInstances,
|
||||||
immediateInstances,
|
immediateInstances,
|
||||||
deferTransparent,
|
deferTransparent,
|
||||||
|
|
@ -2450,7 +2421,6 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Phase A8 RR5 (2026-05-26): per-building draw overload. Walks only
|
/// Phase A8 RR5 (2026-05-26): per-building draw overload. Walks only
|
||||||
/// entities whose ParentCellId is in <paramref name="cellIds"/>, plus
|
/// entities whose ParentCellId is in <paramref name="cellIds"/>, plus
|
||||||
|
|
@ -2606,23 +2576,6 @@ public sealed unsafe partial class WbDrawDispatcher : IDisposable
|
||||||
observer.ObserveDispatcherSubmission(in submission);
|
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
|
internal static CurrentRenderDispatcherSubmission
|
||||||
CreateDispatcherSubmission(
|
CreateDispatcherSubmission(
|
||||||
int visibleInstanceCount,
|
int visibleInstanceCount,
|
||||||
|
|
|
||||||
|
|
@ -575,62 +575,6 @@ public sealed class ArchRenderSceneTests
|
||||||
Assert.True(scene.Memory.EstimatedIndexBytes > 0);
|
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(
|
private static RenderProjectionRecord Record(
|
||||||
ulong id,
|
ulong id,
|
||||||
ulong incarnation,
|
ulong incarnation,
|
||||||
|
|
|
||||||
|
|
@ -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]
|
[Fact]
|
||||||
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
|
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -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]
|
[Fact]
|
||||||
public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch()
|
public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch()
|
||||||
{
|
{
|
||||||
|
|
@ -283,11 +186,11 @@ public sealed class RetailPViewPassExecutorTests
|
||||||
drawInside,
|
drawInside,
|
||||||
"passes.BeginFrame();",
|
"passes.BeginFrame();",
|
||||||
"passes.EmitDiagnostics(ctx, result);",
|
"passes.EmitDiagnostics(ctx, result);",
|
||||||
"DrawLandscapeThroughOutsideView(",
|
"DrawLandscapeThroughOutsideView(ctx, passes",
|
||||||
"DrawExitPortalMasks(ctx, passes",
|
"DrawExitPortalMasks(ctx, passes",
|
||||||
"DrawEnvCellShells(passes, pvFrame);",
|
"DrawEnvCellShells(passes, pvFrame);",
|
||||||
"DrawCellObjectLists(",
|
"DrawCellObjectLists(ctx, passes",
|
||||||
"DrawDynamicsLast(");
|
"DrawDynamicsLast(ctx, passes");
|
||||||
|
|
||||||
string landscape = MethodBody(
|
string landscape = MethodBody(
|
||||||
source,
|
source,
|
||||||
|
|
@ -295,9 +198,9 @@ public sealed class RetailPViewPassExecutorTests
|
||||||
AssertAppearsInOrder(
|
AssertAppearsInOrder(
|
||||||
landscape,
|
landscape,
|
||||||
"passes.SetTerrainClip(slice.Planes);",
|
"passes.SetTerrainClip(slice.Planes);",
|
||||||
"passes.DrawLandscapeSlice(",
|
"passes.DrawLandscapeSlice(ctx",
|
||||||
"DrawBuildingLookIns(",
|
"DrawBuildingLookIns(ctx, passes",
|
||||||
"passes.DrawLandscapeSliceLate(",
|
"passes.DrawLandscapeSliceLate(ctx",
|
||||||
"passes.DrawUnattachedSceneParticles(ctx);",
|
"passes.DrawUnattachedSceneParticles(ctx);",
|
||||||
"passes.FlushLandscapeAlpha();",
|
"passes.FlushLandscapeAlpha();",
|
||||||
"passes.ClearInteriorDepth();");
|
"passes.ClearInteriorDepth();");
|
||||||
|
|
@ -601,10 +504,7 @@ public sealed class RetailPViewPassExecutorTests
|
||||||
public float Aspect { get; set; } = 1f;
|
public float Aspect { get; set; } = 1f;
|
||||||
}
|
}
|
||||||
|
|
||||||
private sealed class RecordingExecutor :
|
private sealed class RecordingExecutor : IRetailPViewPassExecutor, IDisposable
|
||||||
IRetailPViewPassExecutor,
|
|
||||||
IRenderFrameEntityPassExecutor,
|
|
||||||
IDisposable
|
|
||||||
{
|
{
|
||||||
public void AbortFrame() => Operations.Add("abort");
|
public void AbortFrame() => Operations.Add("abort");
|
||||||
|
|
||||||
|
|
@ -657,41 +557,8 @@ public sealed class RetailPViewPassExecutorTests
|
||||||
ViewconeCuller viewcone) => Operations.Add("outstage-routing");
|
ViewconeCuller viewcone) => Operations.Add("outstage-routing");
|
||||||
public void EmitPhantomObjects(uint cellId, int survivorCount) =>
|
public void EmitPhantomObjects(uint cellId, int survivorCount) =>
|
||||||
Operations.Add("phantom-objects");
|
Operations.Add("phantom-objects");
|
||||||
public void DrawLandscapeSlice(
|
public void DrawLandscapeSlice(RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context) => Operations.Add("landscape-early");
|
||||||
RetailPViewFrameInput frame,
|
public void DrawLandscapeSliceLate(RetailPViewFrameInput frame, RetailPViewLandscapeLateSliceContext context) => Operations.Add("landscape-late");
|
||||||
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 ClearInteriorDepth() => Operations.Add("interior-depth-clear");
|
public void ClearInteriorDepth() => Operations.Add("interior-depth-clear");
|
||||||
public void DrawExitPortalMask(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("exit-mask");
|
public void DrawExitPortalMask(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("exit-mask");
|
||||||
public void DrawLookInPortalPunch(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("look-in-punch");
|
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<WorldEntity> survivors) => Operations.Add("dynamics-particles");
|
public void DrawDynamicsParticles(RetailPViewFrameInput frame, IReadOnlyList<WorldEntity> survivors) => 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) =>
|
|
||||||
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();
|
public void Dispose() => _clipFrame.Dispose();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue