feat(rendering): pack scene dispatcher inputs

Capture immutable presentation payloads in the render scene, flatten unique entity and mesh-part records into the borrowed frame arena, and compare every routed dispatcher input tuple without changing the production draw source.
This commit is contained in:
Erik 2026-07-25 00:44:58 +02:00
parent 720578592b
commit d8d9897376
13 changed files with 593 additions and 24 deletions

View file

@ -406,6 +406,41 @@ public sealed class CurrentRenderSceneOracleTests
}
}
[Fact]
public void SurfaceOverrideFingerprint_IsOrderIndependentAndContentExact()
{
var first = new Dictionary<uint, uint>
{
[0x0800_0002] = 0x0500_0012,
[0x0800_0001] = 0x0500_0011,
};
var reordered = new Dictionary<uint, uint>
{
[0x0800_0001] = 0x0500_0011,
[0x0800_0002] = 0x0500_0012,
};
var changed = new Dictionary<uint, uint>
{
[0x0800_0001] = 0x0500_0011,
[0x0800_0002] = 0x0500_0013,
};
RenderSceneHash128 expected =
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(first);
Assert.Equal(
expected,
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(
reordered));
Assert.NotEqual(
expected,
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(
changed));
Assert.NotEqual(
expected,
CurrentRenderSceneOracle.CreateSurfaceOverrideFingerprint(null));
}
private static CurrentRenderProjectionFingerprint CaptureSingle(
WorldEntity entity,
uint landblockId,

View file

@ -2,6 +2,7 @@ using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.Core.Selection;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
@ -26,6 +27,7 @@ public sealed class RenderFrameProductTests
dynamic.Id,
0,
Matrix4x4.CreateTranslation(1, 2, 3)));
writer.AddEntityCandidate(in dynamic, animated: true);
writer.AddClassification(new RenderFrameClassificationRecord(
outdoor.Id,
0,
@ -85,8 +87,16 @@ public sealed class RenderFrameProductTests
1),
view.RouteRanges[0]);
Assert.Equal(
new RenderFrameDiagnosticCounts(1, 2, 1, 1, 1, 1, 1, 1, 1),
new RenderFrameDiagnosticCounts(
1, 2, 1, 1, 1, 1, 1, 1, 1, 1, 1),
view.DiagnosticCounts);
RenderFrameEntityCandidate packed = Assert.Single(
view.EntityCandidates.ToArray());
Assert.Equal(dynamic.Id, packed.Projection.Id);
Assert.True(packed.Animated);
RenderFrameMeshPart part = Assert.Single(view.MeshParts.ToArray());
Assert.Equal(dynamic.Id, part.ProjectionId);
Assert.Equal(0, part.PartIndex);
Assert.Equal(digest, view.SourceDigest);
exchange.Release(in view);
@ -231,6 +241,7 @@ public sealed class RenderFrameProductTests
record.Id,
0,
Matrix4x4.Identity));
writer.AddEntityCandidate(in record, animated: false);
writer.AddClassification(new RenderFrameClassificationRecord(
record.Id,
0,
@ -277,6 +288,10 @@ public sealed class RenderFrameProductTests
Transform = new RenderTransform(Matrix4x4.Identity),
PreviousTransform = new PreviousRenderTransform(Matrix4x4.Identity),
Flags = RenderProjectionFlags.Draw,
EntityPayload = new RenderEntityPayload(
[new MeshRef((uint)value, Matrix4x4.Identity)],
PaletteOverride: null,
IsBuildingShell: false),
};
private static RetailSelectionMesh SelectionMesh() =>

View file

@ -2,6 +2,7 @@ using System.Numerics;
using AcDream.App.Rendering;
using AcDream.App.Rendering.Scene;
using AcDream.App.Rendering.Scene.Arch;
using AcDream.App.Rendering.Wb;
using AcDream.Core.World;
namespace AcDream.App.Tests.Rendering;
@ -81,6 +82,7 @@ public sealed class RenderScenePViewFrameProductTests
[],
[Cell],
EmptyCellSource.Instance,
[],
RootIsOutdoor: true);
builder.Build(exchange, frameSequence: 1, in input);
@ -115,8 +117,12 @@ public sealed class RenderScenePViewFrameProductTests
AlphaClassificationCount: 0,
LightSetCount: 0,
SelectionPartCount: 0,
RouteCandidateCount: 5),
RouteCandidateCount: 5,
EntityCandidateCount: 5,
MeshPartCount: 5),
view.DiagnosticCounts);
Assert.Equal(5, view.EntityCandidates.Length);
Assert.Equal(5, view.MeshParts.Length);
Assert.Same(portal, view.PortalFrame);
Assert.Same(clip, view.ClipAssembly);
Assert.Equal(digest, view.SourceDigest);
@ -162,6 +168,22 @@ public sealed class RenderScenePViewFrameProductTests
0,
[outdoorDynamic, cellDynamic]);
oracle.CompletePViewFrame();
oracle.BeginDispatcherFrame();
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 1,
[(outdoor, 0, Landblock)]);
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 1,
[(cellStatic, 0, Landblock)]);
oracle.ObserveDispatcherDraw(
WbDrawDispatcher.EntitySet.All,
entitiesWalked: 2,
[
(outdoorDynamic, 0, Landblock),
(cellDynamic, 0, Landblock),
]);
using var shadow = new RenderSceneShadowRuntime(Generation);
foreach (WorldEntity entity in entities)
@ -187,6 +209,8 @@ public sealed class RenderScenePViewFrameProductTests
[],
[Cell],
EmptyCellSource.Instance,
[],
Landblock,
rootIsOutdoor: true);
RenderFrameProductComparisonSnapshot matched = controller.Snapshot;
@ -198,6 +222,15 @@ public sealed class RenderScenePViewFrameProductTests
Assert.Equal(0, matched.MismatchCount);
Assert.Null(matched.FirstMismatch);
Assert.Equal(matched.ExpectedDigest, matched.ActualDigest);
Assert.Equal(1uL, matched.PackedInputComparisonCount);
Assert.Equal(1uL, matched.PackedInputSuccessfulComparisonCount);
Assert.Equal(4, matched.PackedInputExpectedCount);
Assert.Equal(4, matched.PackedInputActualCount);
Assert.Equal(0, matched.PackedInputMismatchCount);
Assert.Null(matched.PackedInputFirstMismatch);
Assert.Equal(
matched.PackedInputExpectedDigest,
matched.PackedInputActualDigest);
Assert.Empty(logs);
RenderProjectionId withdrawnId =
@ -213,6 +246,8 @@ public sealed class RenderScenePViewFrameProductTests
[],
[Cell],
EmptyCellSource.Instance,
[],
Landblock,
rootIsOutdoor: true);
RenderFrameProductComparisonSnapshot mismatch = controller.Snapshot;
@ -220,7 +255,11 @@ public sealed class RenderScenePViewFrameProductTests
Assert.Equal(1uL, mismatch.SuccessfulComparisonCount);
Assert.Equal(1, mismatch.MismatchCount);
Assert.Contains("field=count expected=4 actual=3", mismatch.FirstMismatch);
Assert.Single(logs);
Assert.Equal(1, mismatch.PackedInputMismatchCount);
Assert.Contains(
"field=count expected=4 actual=3",
mismatch.PackedInputFirstMismatch);
Assert.Equal(2, logs.Count);
}
private static RenderProjectionRecord Project(WorldEntity entity)
@ -286,7 +325,11 @@ public sealed class RenderScenePViewFrameProductTests
BuildingShellAnchorCellId: 0,
TransformFingerprint: default,
GeometryFingerprint: default,
AppearanceFingerprint: default));
AppearanceFingerprint: default),
new RenderEntityPayload(
[new MeshRef((uint)id, Matrix4x4.Identity)],
PaletteOverride: null,
IsBuildingShell: false));
}
private static WorldEntity Entity(