feat(rendering): compare the incremental shadow scene
Construct Slice F's non-drawing scene only for lifecycle automation, drain accepted static and live deltas at the final update boundary, and compare exact current-path fingerprints at cadence and checkpoints. Publish bounded mismatch, journal, index, digest, and memory evidence without changing normal launches or draw submission. Release: 8,211 passed, 5 skipped.
This commit is contained in:
parent
ff5d86175f
commit
0eb6648589
21 changed files with 937 additions and 20 deletions
|
|
@ -38,12 +38,14 @@ public sealed class FrameRootCompositionTests
|
|||
AssertAppearsInOrder(
|
||||
source,
|
||||
"new RenderFrameResourceController(",
|
||||
"new RenderSceneShadowComparisonController(",
|
||||
"new WorldSceneRenderer(",
|
||||
"new WorldLifecycleAutomationController(",
|
||||
"\"world lifecycle automation owner\"",
|
||||
"\"world lifecycle automation binding\"",
|
||||
"new SerialRenderFramePostDiagnosticsPhase(",
|
||||
"new RenderFrameOrchestrator(",
|
||||
"(IRenderFramePostDiagnosticsPhase?)lifecycleAutomation",
|
||||
"postDiagnostics,",
|
||||
"new RetailLiveFrameCoordinator(",
|
||||
"new UpdateFrameOrchestrator(",
|
||||
"d.FrameGraphs.PublishOwned(",
|
||||
|
|
|
|||
|
|
@ -144,6 +144,15 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
AbortedSelectionFrames: 0,
|
||||
SelectionPartCount: 7,
|
||||
SelectionDigest: new RenderSceneHash128(15, 16)),
|
||||
RenderSceneShadow =
|
||||
RenderSceneShadowComparisonSnapshot.Disabled with
|
||||
{
|
||||
Enabled = true,
|
||||
ComparisonCount = 19,
|
||||
SuccessfulComparisonCount = 19,
|
||||
ComparedOracleFrameSequence = 19,
|
||||
MatchedProjectionCount = 73,
|
||||
},
|
||||
TrackedGpuBytes = 1234,
|
||||
Residency = new ResidencySnapshot(
|
||||
[
|
||||
|
|
@ -214,6 +223,20 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
0xFEDCBA9876543210uL,
|
||||
renderSceneOracle.GetProperty("digest").GetProperty("high")
|
||||
.GetUInt64());
|
||||
JsonElement renderSceneShadow = json.RootElement
|
||||
.GetProperty("resources")
|
||||
.GetProperty("renderSceneShadow");
|
||||
Assert.True(renderSceneShadow.GetProperty("enabled").GetBoolean());
|
||||
Assert.Equal(
|
||||
19,
|
||||
renderSceneShadow.GetProperty("comparisonCount").GetInt64());
|
||||
Assert.Equal(
|
||||
73,
|
||||
renderSceneShadow.GetProperty("matchedProjectionCount")
|
||||
.GetInt32());
|
||||
Assert.Equal(
|
||||
0,
|
||||
renderSceneShadow.GetProperty("mismatchCount").GetInt64());
|
||||
JsonElement residency = json.RootElement
|
||||
.GetProperty("resources")
|
||||
.GetProperty("residency");
|
||||
|
|
@ -353,6 +376,7 @@ public sealed class WorldLifecycleAutomationControllerTests
|
|||
|
||||
private static WorldLifecycleResourceSnapshot EmptyResources() => new(
|
||||
0, 0, 0, 0, 0, 0, 0, CurrentRenderSceneOracleSnapshot.Disabled,
|
||||
RenderSceneShadowComparisonSnapshot.Disabled,
|
||||
0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
|
||||
0, 0, 0L, 0, 0L, 0L, 0L, 0L, 0, 0, 0, 0, 0, 0, 0, 0L, 0L,
|
||||
0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L, 0L,
|
||||
|
|
|
|||
|
|
@ -269,6 +269,7 @@ public sealed class LiveSessionHostTests
|
|||
InboundEventFifo = noop,
|
||||
LiveLiveness = noop,
|
||||
LiveRuntime = noop,
|
||||
RenderSceneProjection = noop,
|
||||
SessionIdentity = noop,
|
||||
RemoteTeleport = noop,
|
||||
NetworkEffects = noop,
|
||||
|
|
|
|||
|
|
@ -298,6 +298,7 @@ public sealed class LiveSessionResetPlanTests
|
|||
LiveRuntime = Stage(
|
||||
"live runtime",
|
||||
() => LiveSessionEntityRuntimeReset.ClearAndRequireConvergence(live)),
|
||||
RenderSceneProjection = Stage("render scene projection"),
|
||||
SessionIdentity = Stage("session identity", () =>
|
||||
{
|
||||
LiveSessionEntityRuntimeReset.RequireConvergence(live);
|
||||
|
|
@ -378,6 +379,7 @@ public sealed class LiveSessionResetPlanTests
|
|||
"inbound event fifo",
|
||||
"live liveness",
|
||||
"live runtime",
|
||||
"render scene projection",
|
||||
"session identity",
|
||||
"remote teleport",
|
||||
"network effects",
|
||||
|
|
|
|||
|
|
@ -0,0 +1,224 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Tests.Rendering;
|
||||
|
||||
public sealed class RenderSceneShadowRuntimeTests
|
||||
{
|
||||
private const uint Landblock = 0xA9B4FFFF;
|
||||
private const uint Cell = 0xA9B40170;
|
||||
|
||||
[Fact]
|
||||
public void ExactCurrentProjection_MatchesAndAcknowledgesDirtyWorkset()
|
||||
{
|
||||
WorldEntity entity = Entity(17, serverGuid: 0, parentCell: null);
|
||||
CurrentRenderSceneOracle oracle = Capture(
|
||||
entity,
|
||||
visibleCells: []);
|
||||
using var shadow = new RenderSceneShadowRuntime(Generation(1));
|
||||
Register(shadow, entity, RenderProjectionClass.OutdoorStatic);
|
||||
Assert.Equal(1, shadow.DrainUpdateBoundary().Registered);
|
||||
|
||||
var controller = new RenderSceneShadowComparisonController(
|
||||
shadow,
|
||||
oracle);
|
||||
RenderSceneShadowComparisonSnapshot snapshot =
|
||||
controller.CaptureCheckpointSnapshot();
|
||||
|
||||
Assert.True(snapshot.Enabled);
|
||||
Assert.Equal(1uL, snapshot.ComparisonCount);
|
||||
Assert.Equal(1uL, snapshot.SuccessfulComparisonCount);
|
||||
Assert.Equal(0, snapshot.MismatchCount);
|
||||
Assert.Equal(1, snapshot.MatchedProjectionCount);
|
||||
Assert.Equal(0, snapshot.PendingDeltaCount);
|
||||
Assert.Equal(0, snapshot.SceneIndexCounts.Dirty);
|
||||
Assert.Equal(1, snapshot.SceneCounts.Total);
|
||||
Assert.True(snapshot.Memory.TotalEstimatedBytes > 0);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void FirstExactMismatch_IsNamedAndLoggedOnlyOnce()
|
||||
{
|
||||
WorldEntity entity = Entity(18, serverGuid: 0, parentCell: null);
|
||||
CurrentRenderSceneOracle oracle = Capture(entity, []);
|
||||
using var shadow = new RenderSceneShadowRuntime(Generation(1));
|
||||
RenderProjectionRecord record = Project(
|
||||
entity,
|
||||
RenderProjectionClass.OutdoorStatic) with
|
||||
{
|
||||
Source = Project(
|
||||
entity,
|
||||
RenderProjectionClass.OutdoorStatic).Source with
|
||||
{
|
||||
TransformFingerprint = new RenderSceneHash128(91, 92),
|
||||
},
|
||||
};
|
||||
shadow.Journal.Register(in record);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var logs = new List<string>();
|
||||
var controller = new RenderSceneShadowComparisonController(
|
||||
shadow,
|
||||
oracle,
|
||||
logs.Add);
|
||||
|
||||
controller.CompareNow();
|
||||
controller.CompareNow();
|
||||
RenderSceneShadowComparisonSnapshot snapshot = controller.Snapshot;
|
||||
|
||||
Assert.Equal(2uL, snapshot.ComparisonCount);
|
||||
Assert.Equal(0uL, snapshot.SuccessfulComparisonCount);
|
||||
Assert.Equal(2, snapshot.MismatchCount);
|
||||
Assert.Contains("sourceChannel=static", snapshot.FirstMismatch);
|
||||
Assert.Contains("field=transform", snapshot.FirstMismatch);
|
||||
Assert.Single(logs);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void NonFloodedIndoorStatic_IsAValidRetainedSceneExtra()
|
||||
{
|
||||
WorldEntity entity = Entity(19, serverGuid: 0, parentCell: Cell);
|
||||
CurrentRenderSceneOracle oracle = Capture(entity, []);
|
||||
Assert.Empty(oracle.Projections);
|
||||
using var shadow = new RenderSceneShadowRuntime(Generation(1));
|
||||
Register(shadow, entity, RenderProjectionClass.IndoorCellStatic);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var controller = new RenderSceneShadowComparisonController(
|
||||
shadow,
|
||||
oracle);
|
||||
|
||||
controller.CompareNow();
|
||||
|
||||
Assert.Equal(1uL, controller.Snapshot.SuccessfulComparisonCount);
|
||||
Assert.Equal(0, controller.Snapshot.MismatchCount);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void DrawnOutdoorExtra_IsReportedAsStaleProjection()
|
||||
{
|
||||
WorldEntity expected = Entity(20, serverGuid: 0, parentCell: null);
|
||||
WorldEntity extra = Entity(21, serverGuid: 0, parentCell: null);
|
||||
CurrentRenderSceneOracle oracle = Capture(expected, []);
|
||||
using var shadow = new RenderSceneShadowRuntime(Generation(1));
|
||||
Register(shadow, expected, RenderProjectionClass.OutdoorStatic);
|
||||
Register(shadow, extra, RenderProjectionClass.OutdoorStatic);
|
||||
shadow.DrainUpdateBoundary();
|
||||
var controller = new RenderSceneShadowComparisonController(
|
||||
shadow,
|
||||
oracle);
|
||||
|
||||
controller.CompareNow();
|
||||
|
||||
Assert.Equal(1, controller.Snapshot.MismatchCount);
|
||||
Assert.Contains("expected=absent actual=drawn", controller.Snapshot.FirstMismatch);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void RejectedDelta_IsAComparisonFailureEvenWhenSceneIsEmpty()
|
||||
{
|
||||
CurrentRenderSceneOracle oracle = CaptureNoEntities();
|
||||
using var shadow = new RenderSceneShadowRuntime(Generation(1));
|
||||
WorldEntity missing = Entity(22, serverGuid: 0, parentCell: null);
|
||||
RenderProjectionRecord record = Project(
|
||||
missing,
|
||||
RenderProjectionClass.OutdoorStatic);
|
||||
shadow.Journal.Update(
|
||||
RenderProjectionDeltaKind.UpdateTransform,
|
||||
in record);
|
||||
RenderDeltaApplyResult apply = shadow.DrainUpdateBoundary();
|
||||
Assert.Equal(1, apply.RejectedMissing);
|
||||
var controller = new RenderSceneShadowComparisonController(
|
||||
shadow,
|
||||
oracle);
|
||||
|
||||
controller.CompareNow();
|
||||
|
||||
Assert.Equal(1, controller.Snapshot.MismatchCount);
|
||||
Assert.Contains("rejectedDeltas=1", controller.Snapshot.FirstMismatch);
|
||||
}
|
||||
|
||||
private static void Register(
|
||||
RenderSceneShadowRuntime shadow,
|
||||
WorldEntity entity,
|
||||
RenderProjectionClass projectionClass)
|
||||
{
|
||||
RenderProjectionRecord record = Project(entity, projectionClass);
|
||||
shadow.Journal.Register(in record);
|
||||
}
|
||||
|
||||
private static RenderProjectionRecord Project(
|
||||
WorldEntity entity,
|
||||
RenderProjectionClass projectionClass) =>
|
||||
RenderProjectionRecordFactory.ProjectEntity(
|
||||
StaticRenderProjectionJournal.StaticEntityId(
|
||||
Landblock,
|
||||
entity.Id),
|
||||
projectionClass,
|
||||
RenderOwnerIncarnation.FromRaw(entity.Id),
|
||||
Landblock,
|
||||
entity.ParentCellId ?? Landblock,
|
||||
entity,
|
||||
spatiallyVisible: true);
|
||||
|
||||
private static CurrentRenderSceneOracle Capture(
|
||||
WorldEntity entity,
|
||||
HashSet<uint> visibleCells)
|
||||
{
|
||||
var oracle = new CurrentRenderSceneOracle();
|
||||
var result = new InteriorEntityPartition.Result();
|
||||
InteriorEntityPartition.Partition(
|
||||
result,
|
||||
visibleCells,
|
||||
[
|
||||
(
|
||||
Landblock,
|
||||
Vector3.Zero,
|
||||
Vector3.One,
|
||||
(IReadOnlyList<WorldEntity>)[entity],
|
||||
(IReadOnlyDictionary<uint, WorldEntity>?)null),
|
||||
],
|
||||
oracle);
|
||||
return oracle;
|
||||
}
|
||||
|
||||
private static CurrentRenderSceneOracle CaptureNoEntities()
|
||||
{
|
||||
var oracle = new CurrentRenderSceneOracle();
|
||||
var result = new InteriorEntityPartition.Result();
|
||||
InteriorEntityPartition.Partition(
|
||||
result,
|
||||
[],
|
||||
Array.Empty<(
|
||||
uint LandblockId,
|
||||
Vector3 AabbMin,
|
||||
Vector3 AabbMax,
|
||||
IReadOnlyList<WorldEntity> Entities,
|
||||
IReadOnlyDictionary<uint, WorldEntity>? AnimatedById)>(),
|
||||
oracle);
|
||||
return oracle;
|
||||
}
|
||||
|
||||
private static RenderSceneGeneration Generation(ulong value) =>
|
||||
RenderSceneGeneration.FromRaw(value);
|
||||
|
||||
private static WorldEntity Entity(
|
||||
uint id,
|
||||
uint serverGuid,
|
||||
uint? parentCell) =>
|
||||
new()
|
||||
{
|
||||
Id = id,
|
||||
ServerGuid = serverGuid,
|
||||
SourceGfxObjOrSetupId = 0x02000001u,
|
||||
Position = new Vector3(1, 2, 3),
|
||||
Rotation = Quaternion.Identity,
|
||||
MeshRefs =
|
||||
[
|
||||
new MeshRef(
|
||||
0x01000001u,
|
||||
Matrix4x4.CreateTranslation(0.25f, 0.5f, 0.75f)),
|
||||
],
|
||||
ParentCellId = parentCell,
|
||||
};
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue