perf(rendering): draw retained frame product
Make the incremental render scene the production entity source at the existing retail PView stages while retaining the accepted dispatcher upload and draw executor. Keep diagnostics consumer-gated, retain ordered indices across unchanged frames, refresh only dirty records, and preserve exact mesh-load, selection, alpha, lighting, and route lifecycle semantics.
This commit is contained in:
parent
c7d7848dd2
commit
ef1d263337
13 changed files with 1333 additions and 267 deletions
|
|
@ -575,6 +575,62 @@ public sealed class ArchRenderSceneTests
|
|||
Assert.True(scene.Memory.EstimatedIndexBytes > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void IndexRevision_AdvancesOnlyWhenOrderedMembershipCanChange()
|
||||
{
|
||||
RenderSceneGeneration generation = Generation(18);
|
||||
using var scene = new ArchRenderScene(generation);
|
||||
RenderProjectionRecord original = Record(
|
||||
18,
|
||||
1,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
scene.Apply(
|
||||
[RenderProjectionDelta.Register(generation, 1, original)]);
|
||||
scene.ClearDirty();
|
||||
ulong registeredRevision = scene.OpenQuery().IndexRevision;
|
||||
|
||||
Matrix4x4 movedTransform =
|
||||
Matrix4x4.CreateTranslation(4, 5, 6);
|
||||
RenderProjectionRecord moved = original with
|
||||
{
|
||||
Transform = new RenderTransform(movedTransform),
|
||||
PreviousTransform =
|
||||
new PreviousRenderTransform(
|
||||
original.Transform.LocalToWorld),
|
||||
Bounds = new RenderWorldBounds(
|
||||
new Vector3(3, 4, 5),
|
||||
new Vector3(5, 6, 7)),
|
||||
};
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Update(
|
||||
RenderProjectionDeltaKind.UpdateTransform,
|
||||
generation,
|
||||
2,
|
||||
moved),
|
||||
]);
|
||||
|
||||
RenderSceneQuery movedQuery = scene.OpenQuery();
|
||||
Assert.Equal(registeredRevision, movedQuery.IndexRevision);
|
||||
Assert.Equal(1, movedQuery.IndexCounts.Dirty);
|
||||
|
||||
RenderProjectionRecord reordered = moved with
|
||||
{
|
||||
SortKey = new RenderSortKey(moved.SortKey.Value + 1),
|
||||
};
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Update(
|
||||
RenderProjectionDeltaKind.UpdateTransform,
|
||||
generation,
|
||||
3,
|
||||
reordered),
|
||||
]);
|
||||
|
||||
Assert.True(
|
||||
scene.OpenQuery().IndexRevision > registeredRevision);
|
||||
}
|
||||
|
||||
private static RenderProjectionRecord Record(
|
||||
ulong id,
|
||||
ulong incarnation,
|
||||
|
|
|
|||
|
|
@ -133,6 +133,142 @@ public sealed class RenderScenePViewFrameProductTests
|
|||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Builder_ReusesOrderedIndicesWhileRefreshingDirtyRecords()
|
||||
{
|
||||
using var scene = new ArchRenderScene(Generation);
|
||||
RenderProjectionRecord later = Record(
|
||||
0x0100_0000_0000_0012,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
RenderProjectionRecord earlier = Record(
|
||||
0x0100_0000_0000_0011,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Register(Generation, 1, later),
|
||||
RenderProjectionDelta.Register(Generation, 2, earlier),
|
||||
]);
|
||||
|
||||
PortalVisibilityFrame portal = Portal(Cell);
|
||||
ClipFrameAssembly clip = FullScreenClip(Cell);
|
||||
ViewconeCuller viewcone =
|
||||
ViewconeCuller.Build(clip, Matrix4x4.Identity);
|
||||
var exchange = new RenderFrameExchange();
|
||||
var builder = new RenderScenePViewFrameBuilder();
|
||||
|
||||
Build(frameSequence: 1);
|
||||
RenderFrameView first = exchange.BorrowLatest(Generation, 1);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[earlier.Id, later.Id],
|
||||
first.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static record => record.Id));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in first);
|
||||
}
|
||||
|
||||
scene.ClearDirty();
|
||||
ulong retainedRevision = scene.OpenQuery().IndexRevision;
|
||||
Matrix4x4 movedTransform =
|
||||
Matrix4x4.CreateTranslation(0.25f, 0, 0);
|
||||
RenderProjectionRecord moved = later with
|
||||
{
|
||||
Transform = new RenderTransform(movedTransform),
|
||||
PreviousTransform =
|
||||
new PreviousRenderTransform(
|
||||
later.Transform.LocalToWorld),
|
||||
Bounds = new RenderWorldBounds(
|
||||
new Vector3(-0.75f, -1, -1),
|
||||
new Vector3(1.25f, 1, 1)),
|
||||
};
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Update(
|
||||
RenderProjectionDeltaKind.UpdateTransform,
|
||||
Generation,
|
||||
3,
|
||||
moved),
|
||||
]);
|
||||
Assert.Equal(
|
||||
retainedRevision,
|
||||
scene.OpenQuery().IndexRevision);
|
||||
|
||||
Build(frameSequence: 2);
|
||||
RenderFrameView second = exchange.BorrowLatest(Generation, 2);
|
||||
try
|
||||
{
|
||||
RenderProjectionRecord projected =
|
||||
second.EntityCandidates.ToArray()
|
||||
.Single(candidate =>
|
||||
candidate.Projection.Id == later.Id)
|
||||
.Projection;
|
||||
Assert.Equal(
|
||||
movedTransform,
|
||||
projected.Transform.LocalToWorld);
|
||||
Assert.Equal(
|
||||
[earlier.Id, later.Id],
|
||||
second.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static record => record.Id));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in second);
|
||||
}
|
||||
|
||||
scene.ClearDirty();
|
||||
RenderProjectionRecord reordered = moved with
|
||||
{
|
||||
SortKey = new RenderSortKey(0),
|
||||
};
|
||||
scene.Apply(
|
||||
[
|
||||
RenderProjectionDelta.Update(
|
||||
RenderProjectionDeltaKind.UpdateTransform,
|
||||
Generation,
|
||||
4,
|
||||
reordered),
|
||||
]);
|
||||
Assert.True(
|
||||
scene.OpenQuery().IndexRevision > retainedRevision);
|
||||
|
||||
Build(frameSequence: 3);
|
||||
RenderFrameView third = exchange.BorrowLatest(Generation, 3);
|
||||
try
|
||||
{
|
||||
Assert.Equal(
|
||||
[later.Id, earlier.Id],
|
||||
third.OutdoorStaticCandidates.ToArray()
|
||||
.Select(static record => record.Id));
|
||||
}
|
||||
finally
|
||||
{
|
||||
exchange.Release(in third);
|
||||
}
|
||||
|
||||
void Build(ulong frameSequence)
|
||||
{
|
||||
RenderSceneQuery query = scene.OpenQuery();
|
||||
var input = new RenderScenePViewBuildInput(
|
||||
query,
|
||||
new RenderSceneDigest(
|
||||
query.Generation,
|
||||
query.Counts,
|
||||
default),
|
||||
portal,
|
||||
clip,
|
||||
viewcone,
|
||||
[],
|
||||
[Cell],
|
||||
EmptyCellSource.Instance,
|
||||
[],
|
||||
RootIsOutdoor: true);
|
||||
builder.Build(exchange, frameSequence, in input);
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Controller_MatchesCurrentPViewThenNamesAWithdrawnCandidate()
|
||||
{
|
||||
|
|
|
|||
|
|
@ -158,6 +158,103 @@ public sealed class RetailPViewPassExecutorTests
|
|||
});
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawInside_production_product_supplies_the_ordered_entity_routes()
|
||||
{
|
||||
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
||||
WorldEntity cellStatic = Entity(
|
||||
30u,
|
||||
parentCellId: interior.CellId);
|
||||
WorldEntity dynamic = Entity(
|
||||
31u,
|
||||
serverGuid: 0x80000031u,
|
||||
parentCellId: interior.CellId);
|
||||
uint landblockId = interior.CellId & 0xFFFF0000u;
|
||||
var oracle = new CurrentRenderSceneOracle();
|
||||
using var shadow = new RenderSceneShadowRuntime(
|
||||
RenderSceneGeneration.FromRaw(1));
|
||||
RenderProjectionRecord staticRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
StaticRenderProjectionJournal.StaticEntityId(
|
||||
landblockId,
|
||||
cellStatic.Id),
|
||||
RenderProjectionClass.IndoorCellStatic,
|
||||
RenderOwnerIncarnation.FromRaw(cellStatic.Id),
|
||||
landblockId,
|
||||
interior.CellId,
|
||||
cellStatic,
|
||||
spatiallyVisible: true);
|
||||
RenderProjectionRecord dynamicRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
||||
RenderProjectionClass.LiveDynamicRoot,
|
||||
RenderOwnerIncarnation.FromRaw(dynamic.Id),
|
||||
landblockId,
|
||||
interior.CellId,
|
||||
dynamic,
|
||||
spatiallyVisible: true);
|
||||
shadow.Journal.Register(in staticRecord);
|
||||
shadow.Journal.Register(in dynamicRecord);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var product = new RenderScenePViewFrameProductController(
|
||||
shadow,
|
||||
oracle);
|
||||
var renderer = new RetailPViewRenderer(oracle, product);
|
||||
using var executor = new RecordingExecutor();
|
||||
|
||||
renderer.DrawInside(
|
||||
Frame(interior, [cellStatic, dynamic]),
|
||||
executor);
|
||||
|
||||
AssertAppearsInOrder(
|
||||
string.Join('|', executor.Operations),
|
||||
"entity-frame-begin",
|
||||
"entity-route:CellStatic:0:00000000",
|
||||
"entity-route:DynamicLast:0:00000000",
|
||||
"entity-frame-complete");
|
||||
Assert.Equal(0, product.Snapshot.MismatchCount);
|
||||
Assert.Equal(
|
||||
product.Snapshot.ExpectedDigest,
|
||||
product.Snapshot.ActualDigest);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawInside_production_product_does_not_require_compare_only_referee()
|
||||
{
|
||||
LoadedCell interior = InteriorWithExit(0xA9B40100u);
|
||||
WorldEntity dynamic = Entity(
|
||||
32u,
|
||||
serverGuid: 0x80000032u,
|
||||
parentCellId: interior.CellId);
|
||||
uint landblockId = interior.CellId & 0xFFFF0000u;
|
||||
using var shadow = new RenderSceneShadowRuntime(
|
||||
RenderSceneGeneration.FromRaw(1));
|
||||
RenderProjectionRecord dynamicRecord =
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
LiveRenderProjectionJournal.ProjectionId(dynamic.Id),
|
||||
RenderProjectionClass.LiveDynamicRoot,
|
||||
RenderOwnerIncarnation.FromRaw(dynamic.Id),
|
||||
landblockId,
|
||||
interior.CellId,
|
||||
dynamic,
|
||||
spatiallyVisible: true);
|
||||
shadow.Journal.Register(in dynamicRecord);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var product = new RenderScenePViewFrameProductController(shadow);
|
||||
var renderer = new RetailPViewRenderer(null, product);
|
||||
using var executor = new RecordingExecutor();
|
||||
|
||||
renderer.DrawInside(Frame(interior, [dynamic]), executor);
|
||||
|
||||
AssertAppearsInOrder(
|
||||
string.Join('|', executor.Operations),
|
||||
"entity-frame-begin",
|
||||
"entity-route:DynamicLast:0:00000000",
|
||||
"entity-frame-complete");
|
||||
Assert.Equal(0uL, product.Snapshot.ComparisonCount);
|
||||
Assert.Equal(1, product.Snapshot.ActualCandidateCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawInside_interior_root_executes_the_nearby_building_look_in_punch()
|
||||
{
|
||||
|
|
@ -186,11 +283,11 @@ public sealed class RetailPViewPassExecutorTests
|
|||
drawInside,
|
||||
"passes.BeginFrame();",
|
||||
"passes.EmitDiagnostics(ctx, result);",
|
||||
"DrawLandscapeThroughOutsideView(ctx, passes",
|
||||
"DrawLandscapeThroughOutsideView(",
|
||||
"DrawExitPortalMasks(ctx, passes",
|
||||
"DrawEnvCellShells(passes, pvFrame);",
|
||||
"DrawCellObjectLists(ctx, passes",
|
||||
"DrawDynamicsLast(ctx, passes");
|
||||
"DrawCellObjectLists(",
|
||||
"DrawDynamicsLast(");
|
||||
|
||||
string landscape = MethodBody(
|
||||
source,
|
||||
|
|
@ -198,9 +295,9 @@ public sealed class RetailPViewPassExecutorTests
|
|||
AssertAppearsInOrder(
|
||||
landscape,
|
||||
"passes.SetTerrainClip(slice.Planes);",
|
||||
"passes.DrawLandscapeSlice(ctx",
|
||||
"DrawBuildingLookIns(ctx, passes",
|
||||
"passes.DrawLandscapeSliceLate(ctx",
|
||||
"passes.DrawLandscapeSlice(",
|
||||
"DrawBuildingLookIns(",
|
||||
"passes.DrawLandscapeSliceLate(",
|
||||
"passes.DrawUnattachedSceneParticles(ctx);",
|
||||
"passes.FlushLandscapeAlpha();",
|
||||
"passes.ClearInteriorDepth();");
|
||||
|
|
@ -506,7 +603,10 @@ public sealed class RetailPViewPassExecutorTests
|
|||
public float Aspect { get; set; } = 1f;
|
||||
}
|
||||
|
||||
private sealed class RecordingExecutor : IRetailPViewPassExecutor, IDisposable
|
||||
private sealed class RecordingExecutor :
|
||||
IRetailPViewPassExecutor,
|
||||
IRenderFrameEntityPassExecutor,
|
||||
IDisposable
|
||||
{
|
||||
public void AbortFrame() => Operations.Add("abort");
|
||||
|
||||
|
|
@ -559,8 +659,41 @@ public sealed class RetailPViewPassExecutorTests
|
|||
ViewconeCuller viewcone) => Operations.Add("outstage-routing");
|
||||
public void EmitPhantomObjects(uint cellId, int survivorCount) =>
|
||||
Operations.Add("phantom-objects");
|
||||
public void DrawLandscapeSlice(RetailPViewFrameInput frame, RetailPViewLandscapeSliceContext context) => Operations.Add("landscape-early");
|
||||
public void DrawLandscapeSliceLate(RetailPViewFrameInput frame, RetailPViewLandscapeLateSliceContext context) => Operations.Add("landscape-late");
|
||||
public void DrawLandscapeSlice(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewLandscapeSliceContext context)
|
||||
{
|
||||
Operations.Add("landscape-early");
|
||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
||||
{
|
||||
RenderFrameView view = request.View;
|
||||
DrawEntityRoute(
|
||||
frame.Camera,
|
||||
in view,
|
||||
request.Route,
|
||||
request.RouteIndex,
|
||||
request.CellId,
|
||||
request.TupleLandblockId);
|
||||
}
|
||||
}
|
||||
|
||||
public void DrawLandscapeSliceLate(
|
||||
RetailPViewFrameInput frame,
|
||||
RetailPViewLandscapeLateSliceContext context)
|
||||
{
|
||||
Operations.Add("landscape-late");
|
||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
||||
{
|
||||
RenderFrameView view = request.View;
|
||||
DrawEntityRoute(
|
||||
frame.Camera,
|
||||
in view,
|
||||
request.Route,
|
||||
request.RouteIndex,
|
||||
request.CellId,
|
||||
request.TupleLandblockId);
|
||||
}
|
||||
}
|
||||
public void ClearInteriorDepth() => Operations.Add("interior-depth-clear");
|
||||
public void DrawExitPortalMask(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("exit-mask");
|
||||
public void DrawLookInPortalPunch(RetailPViewFrameInput frame, RetailPViewCellSliceContext context) => Operations.Add("look-in-punch");
|
||||
|
|
@ -570,6 +703,28 @@ public sealed class RetailPViewPassExecutorTests
|
|||
public void DrawDynamicsParticles(RetailPViewFrameInput frame, IReadOnlyList<WorldEntity> survivors) => Operations.Add("dynamics-particles");
|
||||
public void EmitDiagnostics(RetailPViewFrameInput frame, RetailPViewFrameResult result) => Operations.Add("diagnostics");
|
||||
|
||||
public void BeginEntityFrame(in RenderFrameView view) =>
|
||||
Operations.Add("entity-frame-begin");
|
||||
|
||||
public bool DrawEntityRoute(
|
||||
ICamera camera,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
uint tupleLandblockId)
|
||||
{
|
||||
Operations.Add(
|
||||
$"entity-route:{route}:{routeIndex}:{cellId:X8}");
|
||||
return true;
|
||||
}
|
||||
|
||||
public void CompleteEntityFrame(in RenderFrameView view) =>
|
||||
Operations.Add("entity-frame-complete");
|
||||
|
||||
public void AbortEntityFrame() =>
|
||||
Operations.Add("entity-frame-abort");
|
||||
|
||||
public void Dispose() => _clipFrame.Dispose();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue