feat(rendering): prove packed dispatcher output parity

Build a compare-only dispatcher classifier from RenderFrameView and compare complete opaque, alpha, selection, clip, light, texture, transform, and draw payloads against the accepted path. Preserve retail's stable equal-CYpt ordering with explicit draw-local submission ordinals so material-group history cannot affect alpha ties.
This commit is contained in:
Erik 2026-07-25 01:31:56 +02:00
parent 58b712c6ec
commit 29195fb255
11 changed files with 1264 additions and 65 deletions

View file

@ -318,6 +318,9 @@ public sealed class CurrentRenderSceneOracleTests
OpaqueGroupCount: 1,
TransparentGroupCount: 1,
TransparentDeferred: true,
OpaqueDigest: new RenderSceneHash128(11, 12),
TransparentDigest: new RenderSceneHash128(21, 22),
TransparentSetDigest: new RenderSceneHash128(31, 32),
Digest: new RenderSceneHash128(101, 202));
oracle.ObserveDispatcherSubmission(in submission);
CurrentRenderSceneOracleSnapshot first = oracle.Snapshot;

View file

@ -86,6 +86,58 @@ public class InstanceGroupClearTests
Assert.Equal(6, immediate.ImmediateInstances);
}
[Fact]
public void DispatcherFingerprint_EqualDistanceAlphaUsesOriginalSubmissionOrder()
{
WbDrawDispatcher.InstanceGroup first = MakeCompleteGroup(
textureHandle: 0xAA,
submissionOrder: 0);
WbDrawDispatcher.InstanceGroup second = MakeCompleteGroup(
textureHandle: 0xBB,
submissionOrder: 1);
var scratch = new List<WbDrawDispatcher.AlphaFingerprint>();
var forward = WbDrawDispatcher.CreateDispatcherSubmission(
visibleInstanceCount: 2,
immediateInstanceCount: 0,
deferTransparent: true,
opaque: [],
transparent: [first, second],
cameraWorldPosition: Vector3.Zero,
alphaScratch: scratch);
var reversedMaterialGroups =
WbDrawDispatcher.CreateDispatcherSubmission(
visibleInstanceCount: 2,
immediateInstanceCount: 0,
deferTransparent: true,
opaque: [],
transparent: [second, first],
cameraWorldPosition: Vector3.Zero,
alphaScratch: scratch);
Assert.Equal(
forward.TransparentDigest,
reversedMaterialGroups.TransparentDigest);
first.SubmissionOrders[0] = 1;
second.SubmissionOrders[0] = 0;
var reversedSubmission = WbDrawDispatcher.CreateDispatcherSubmission(
visibleInstanceCount: 2,
immediateInstanceCount: 0,
deferTransparent: true,
opaque: [],
transparent: [first, second],
cameraWorldPosition: Vector3.Zero,
alphaScratch: scratch);
Assert.NotEqual(
forward.TransparentDigest,
reversedSubmission.TransparentDigest);
Assert.Equal(
forward.TransparentSetDigest,
reversedSubmission.TransparentSetDigest);
}
[Fact]
public void CachedGroupHandle_RequiresLiveMatchingRegistration()
{
@ -210,9 +262,34 @@ public class InstanceGroupClearTests
return group;
}
private static WbDrawDispatcher.InstanceGroup MakeCompleteGroup(
ulong textureHandle,
int submissionOrder)
{
var group = new WbDrawDispatcher.InstanceGroup
{
FirstIndex = 0,
BaseVertex = 0,
IndexCount = 6,
BindlessTextureHandle = textureHandle,
TextureLayer = 0,
Translucency = TranslucencyKind.AlphaBlend,
};
group.Matrices.Add(Matrix4x4.CreateTranslation(10f, 0f, 0f));
group.LocalSortCenters.Add(Vector3.Zero);
group.SubmissionOrders.Add(submissionOrder);
group.Slots.Add(0u);
group.LightSets.Add(WbDrawDispatcher.InstanceLightSet.Disabled);
group.IndoorFlags.Add(0u);
group.Opacities.Add(1f);
group.SelectionLighting.Add(new Vector2(0f, 1f));
return group;
}
// #193 (regression from #188, 2026-07-09): WbDrawDispatcher's InstanceGroup holds
// seven per-instance parallel lists — Matrices, LocalSortCenters, Slots,
// LightSets, IndoorFlags, Opacities, SelectionLighting — appended in lockstep
// eight per-instance parallel lists — Matrices, LocalSortCenters,
// SubmissionOrders, Slots, LightSets, IndoorFlags, Opacities, and
// SelectionLighting — appended in lockstep
// (one entry per drawn instance) every frame. The
// per-frame reset must clear ALL of them. #188 added Opacities but left it out of
// the inline clear loop, so it grew one float per instance per frame forever; as
@ -225,6 +302,7 @@ public class InstanceGroupClearTests
var grp = new WbDrawDispatcher.InstanceGroup();
grp.Matrices.Add(Matrix4x4.Identity);
grp.LocalSortCenters.Add(Vector3.Zero);
grp.SubmissionOrders.Add(0);
grp.Slots.Add(1u);
grp.LightSets.Add(WbDrawDispatcher.InstanceLightSet.Disabled);
grp.IndoorFlags.Add(0u);
@ -235,6 +313,7 @@ public class InstanceGroupClearTests
Assert.Empty(grp.Matrices);
Assert.Empty(grp.LocalSortCenters);
Assert.Empty(grp.SubmissionOrders);
Assert.Empty(grp.Slots);
Assert.Empty(grp.LightSets);
Assert.Empty(grp.IndoorFlags);