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:
parent
58b712c6ec
commit
29195fb255
11 changed files with 1264 additions and 65 deletions
|
|
@ -35,6 +35,22 @@ internal readonly record struct RenderFrameProductComparisonSnapshot(
|
|||
string? PackedInputFirstMismatch,
|
||||
RenderSceneHash128 PackedInputExpectedDigest,
|
||||
RenderSceneHash128 PackedInputActualDigest,
|
||||
ulong ClassifiedOutputComparisonCount,
|
||||
ulong ClassifiedOutputSuccessfulComparisonCount,
|
||||
int ClassifiedOutputExpectedDrawCount,
|
||||
int ClassifiedOutputActualDrawCount,
|
||||
long ClassifiedOutputMismatchCount,
|
||||
string? ClassifiedOutputFirstMismatch,
|
||||
RenderSceneHash128 ClassifiedOutputExpectedDigest,
|
||||
RenderSceneHash128 ClassifiedOutputActualDigest,
|
||||
ulong SelectionOutputComparisonCount,
|
||||
ulong SelectionOutputSuccessfulComparisonCount,
|
||||
int SelectionOutputExpectedPartCount,
|
||||
int SelectionOutputActualPartCount,
|
||||
long SelectionOutputMismatchCount,
|
||||
string? SelectionOutputFirstMismatch,
|
||||
RenderSceneHash128 SelectionOutputExpectedDigest,
|
||||
RenderSceneHash128 SelectionOutputActualDigest,
|
||||
RenderFrameDiagnosticCounts ProductCounts)
|
||||
{
|
||||
public static RenderFrameProductComparisonSnapshot Disabled { get; } =
|
||||
|
|
@ -58,6 +74,22 @@ internal readonly record struct RenderFrameProductComparisonSnapshot(
|
|||
PackedInputFirstMismatch: null,
|
||||
PackedInputExpectedDigest: RenderSceneHash128.Empty,
|
||||
PackedInputActualDigest: RenderSceneHash128.Empty,
|
||||
ClassifiedOutputComparisonCount: 0,
|
||||
ClassifiedOutputSuccessfulComparisonCount: 0,
|
||||
ClassifiedOutputExpectedDrawCount: 0,
|
||||
ClassifiedOutputActualDrawCount: 0,
|
||||
ClassifiedOutputMismatchCount: 0,
|
||||
ClassifiedOutputFirstMismatch: null,
|
||||
ClassifiedOutputExpectedDigest: RenderSceneHash128.Empty,
|
||||
ClassifiedOutputActualDigest: RenderSceneHash128.Empty,
|
||||
SelectionOutputComparisonCount: 0,
|
||||
SelectionOutputSuccessfulComparisonCount: 0,
|
||||
SelectionOutputExpectedPartCount: 0,
|
||||
SelectionOutputActualPartCount: 0,
|
||||
SelectionOutputMismatchCount: 0,
|
||||
SelectionOutputFirstMismatch: null,
|
||||
SelectionOutputExpectedDigest: RenderSceneHash128.Empty,
|
||||
SelectionOutputActualDigest: RenderSceneHash128.Empty,
|
||||
ProductCounts: default);
|
||||
}
|
||||
|
||||
|
|
@ -77,12 +109,15 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
{
|
||||
private readonly RenderSceneShadowRuntime _shadow;
|
||||
private readonly CurrentRenderSceneOracle _current;
|
||||
private readonly WbDrawDispatcher? _dispatcher;
|
||||
private readonly RenderScenePViewFrameBuilder _builder = new();
|
||||
private readonly RenderFrameExchange _exchange = new();
|
||||
private readonly List<CandidateKey> _expected = [];
|
||||
private readonly List<CandidateKey> _actual = [];
|
||||
private readonly List<PackedInputKey> _packedExpected = [];
|
||||
private readonly List<PackedInputKey> _packedActual = [];
|
||||
private readonly List<SelectionKey> _selectionExpected = [];
|
||||
private readonly List<SelectionKey> _selectionActual = [];
|
||||
private readonly Action<string>? _log;
|
||||
private ulong _productFrameSequence;
|
||||
private ulong _comparisonCount;
|
||||
|
|
@ -93,15 +128,25 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
private ulong _packedInputSuccessfulComparisonCount;
|
||||
private long _packedInputMismatchCount;
|
||||
private string? _packedInputFirstMismatch;
|
||||
private ulong _classifiedOutputComparisonCount;
|
||||
private ulong _classifiedOutputSuccessfulComparisonCount;
|
||||
private long _classifiedOutputMismatchCount;
|
||||
private string? _classifiedOutputFirstMismatch;
|
||||
private ulong _selectionOutputComparisonCount;
|
||||
private ulong _selectionOutputSuccessfulComparisonCount;
|
||||
private long _selectionOutputMismatchCount;
|
||||
private string? _selectionOutputFirstMismatch;
|
||||
|
||||
public RenderScenePViewFrameProductController(
|
||||
RenderSceneShadowRuntime shadow,
|
||||
CurrentRenderSceneOracle current,
|
||||
Action<string>? log = null)
|
||||
Action<string>? log = null,
|
||||
WbDrawDispatcher? dispatcher = null)
|
||||
{
|
||||
_shadow = shadow ?? throw new ArgumentNullException(nameof(shadow));
|
||||
_current = current ?? throw new ArgumentNullException(nameof(current));
|
||||
_log = log;
|
||||
_dispatcher = dispatcher;
|
||||
Snapshot = RenderFrameProductComparisonSnapshot.Disabled with
|
||||
{
|
||||
Enabled = true,
|
||||
|
|
@ -119,7 +164,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
IRetailPViewCellSource cells,
|
||||
HashSet<uint>? animatedEntityIds,
|
||||
uint tupleLandblockId,
|
||||
bool rootIsOutdoor)
|
||||
bool rootIsOutdoor,
|
||||
Vector3 cameraWorldPosition = default)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(portalFrame);
|
||||
ArgumentNullException.ThrowIfNull(clipAssembly);
|
||||
|
|
@ -147,7 +193,10 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
frameSequence);
|
||||
try
|
||||
{
|
||||
Compare(in view, tupleLandblockId);
|
||||
Compare(
|
||||
in view,
|
||||
tupleLandblockId,
|
||||
cameraWorldPosition);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -155,7 +204,10 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
}
|
||||
}
|
||||
|
||||
private void Compare(in RenderFrameView view, uint tupleLandblockId)
|
||||
private void Compare(
|
||||
in RenderFrameView view,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
_comparisonCount = checked(_comparisonCount + 1);
|
||||
_expected.Clear();
|
||||
|
|
@ -225,6 +277,11 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
|
||||
PackedInputComparison packed =
|
||||
ComparePackedInput(in view, tupleLandblockId);
|
||||
ClassifiedOutputComparison classified =
|
||||
CompareClassifiedOutput(
|
||||
in view,
|
||||
tupleLandblockId,
|
||||
cameraWorldPosition);
|
||||
|
||||
Snapshot = new RenderFrameProductComparisonSnapshot(
|
||||
Enabled: true,
|
||||
|
|
@ -248,9 +305,247 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
PackedInputFirstMismatch: _packedInputFirstMismatch,
|
||||
PackedInputExpectedDigest: packed.ExpectedDigest,
|
||||
PackedInputActualDigest: packed.ActualDigest,
|
||||
ClassifiedOutputComparisonCount:
|
||||
_classifiedOutputComparisonCount,
|
||||
ClassifiedOutputSuccessfulComparisonCount:
|
||||
_classifiedOutputSuccessfulComparisonCount,
|
||||
ClassifiedOutputExpectedDrawCount:
|
||||
classified.ExpectedDrawCount,
|
||||
ClassifiedOutputActualDrawCount:
|
||||
classified.ActualDrawCount,
|
||||
ClassifiedOutputMismatchCount:
|
||||
_classifiedOutputMismatchCount,
|
||||
ClassifiedOutputFirstMismatch:
|
||||
_classifiedOutputFirstMismatch,
|
||||
ClassifiedOutputExpectedDigest:
|
||||
classified.ExpectedDigest,
|
||||
ClassifiedOutputActualDigest:
|
||||
classified.ActualDigest,
|
||||
SelectionOutputComparisonCount:
|
||||
_selectionOutputComparisonCount,
|
||||
SelectionOutputSuccessfulComparisonCount:
|
||||
_selectionOutputSuccessfulComparisonCount,
|
||||
SelectionOutputExpectedPartCount:
|
||||
classified.ExpectedSelectionCount,
|
||||
SelectionOutputActualPartCount:
|
||||
classified.ActualSelectionCount,
|
||||
SelectionOutputMismatchCount:
|
||||
_selectionOutputMismatchCount,
|
||||
SelectionOutputFirstMismatch:
|
||||
_selectionOutputFirstMismatch,
|
||||
SelectionOutputExpectedDigest:
|
||||
classified.ExpectedSelectionDigest,
|
||||
SelectionOutputActualDigest:
|
||||
classified.ActualSelectionDigest,
|
||||
ProductCounts: view.DiagnosticCounts);
|
||||
}
|
||||
|
||||
private ClassifiedOutputComparison CompareClassifiedOutput(
|
||||
in RenderFrameView view,
|
||||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
WbDrawDispatcher? dispatcher = _dispatcher;
|
||||
if (dispatcher is null)
|
||||
return default;
|
||||
|
||||
dispatcher.BuildPackedDispatcherOracle(
|
||||
in view,
|
||||
tupleLandblockId,
|
||||
cameraWorldPosition);
|
||||
IReadOnlyList<CurrentRenderDispatcherSubmission> expected =
|
||||
_current.DispatcherSubmissions;
|
||||
IReadOnlyList<CurrentRenderDispatcherSubmission> actual =
|
||||
dispatcher.PackedDispatcherSubmissions;
|
||||
RenderSceneHash128 expectedDigest =
|
||||
BuildSubmissionDigest(expected);
|
||||
RenderSceneHash128 actualDigest =
|
||||
BuildSubmissionDigest(actual);
|
||||
_classifiedOutputComparisonCount =
|
||||
checked(_classifiedOutputComparisonCount + 1);
|
||||
string? mismatch = CompareSubmissions(expected, actual);
|
||||
if (mismatch is null)
|
||||
{
|
||||
_classifiedOutputSuccessfulComparisonCount =
|
||||
checked(
|
||||
_classifiedOutputSuccessfulComparisonCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_classifiedOutputMismatchCount =
|
||||
checked(_classifiedOutputMismatchCount + 1);
|
||||
if (_classifiedOutputFirstMismatch is null)
|
||||
{
|
||||
_classifiedOutputFirstMismatch = mismatch;
|
||||
_log?.Invoke(
|
||||
"[render-classified-output] first mismatch: "
|
||||
+ mismatch);
|
||||
}
|
||||
}
|
||||
|
||||
IReadOnlyList<CurrentRenderSelectionFingerprint>
|
||||
expectedSelection = _current.SelectionParts;
|
||||
IReadOnlyList<CurrentRenderSelectionFingerprint>
|
||||
actualSelection = dispatcher.PackedSelectionParts;
|
||||
BuildSelectionKeys(
|
||||
expectedSelection,
|
||||
_selectionExpected);
|
||||
BuildSelectionKeys(
|
||||
actualSelection,
|
||||
_selectionActual);
|
||||
RenderSceneHash128 expectedSelectionDigest =
|
||||
BuildSelectionDigest(_selectionExpected);
|
||||
RenderSceneHash128 actualSelectionDigest =
|
||||
BuildSelectionDigest(_selectionActual);
|
||||
_selectionOutputComparisonCount =
|
||||
checked(_selectionOutputComparisonCount + 1);
|
||||
string? selectionMismatch = CompareSelection(
|
||||
_selectionExpected,
|
||||
_selectionActual);
|
||||
if (selectionMismatch is null)
|
||||
{
|
||||
_selectionOutputSuccessfulComparisonCount =
|
||||
checked(
|
||||
_selectionOutputSuccessfulComparisonCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_selectionOutputMismatchCount =
|
||||
checked(_selectionOutputMismatchCount + 1);
|
||||
if (_selectionOutputFirstMismatch is null)
|
||||
{
|
||||
_selectionOutputFirstMismatch = selectionMismatch;
|
||||
_log?.Invoke(
|
||||
"[render-selection-output] first mismatch: "
|
||||
+ selectionMismatch);
|
||||
}
|
||||
}
|
||||
|
||||
return new ClassifiedOutputComparison(
|
||||
expected.Count,
|
||||
actual.Count,
|
||||
expectedDigest,
|
||||
actualDigest,
|
||||
expectedSelection.Count,
|
||||
actualSelection.Count,
|
||||
expectedSelectionDigest,
|
||||
actualSelectionDigest);
|
||||
}
|
||||
|
||||
private static string? CompareSubmissions(
|
||||
IReadOnlyList<CurrentRenderDispatcherSubmission> expected,
|
||||
IReadOnlyList<CurrentRenderDispatcherSubmission> actual)
|
||||
{
|
||||
if (expected.Count != actual.Count)
|
||||
{
|
||||
return $"field=drawCount expected={expected.Count} "
|
||||
+ $"actual={actual.Count}";
|
||||
}
|
||||
|
||||
for (int index = 0; index < expected.Count; index++)
|
||||
{
|
||||
if (expected[index] != actual[index])
|
||||
{
|
||||
return $"field=draw index={index} "
|
||||
+ $"expected={expected[index]} "
|
||||
+ $"actual={actual[index]}";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static string? CompareSelection(
|
||||
IReadOnlyList<SelectionKey> expected,
|
||||
IReadOnlyList<SelectionKey> actual)
|
||||
{
|
||||
if (expected.Count != actual.Count)
|
||||
{
|
||||
return $"field=partCount expected={expected.Count} "
|
||||
+ $"actual={actual.Count}";
|
||||
}
|
||||
|
||||
for (int index = 0; index < expected.Count; index++)
|
||||
{
|
||||
if (expected[index] != actual[index])
|
||||
{
|
||||
return $"field=part index={index} "
|
||||
+ $"expected={expected[index]} "
|
||||
+ $"actual={actual[index]}";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static RenderSceneHash128 BuildSubmissionDigest(
|
||||
IReadOnlyList<CurrentRenderDispatcherSubmission> submissions)
|
||||
{
|
||||
StableRenderHash128 hash = StableRenderHash128.Create();
|
||||
hash.Add(submissions.Count);
|
||||
for (int index = 0; index < submissions.Count; index++)
|
||||
{
|
||||
CurrentRenderDispatcherSubmission submission =
|
||||
submissions[index];
|
||||
hash.Add(submission.VisibleInstanceCount);
|
||||
hash.Add(submission.ImmediateInstanceCount);
|
||||
hash.Add(submission.OpaqueGroupCount);
|
||||
hash.Add(submission.TransparentGroupCount);
|
||||
hash.Add(submission.TransparentDeferred);
|
||||
hash.Add(submission.OpaqueDigest.Low);
|
||||
hash.Add(submission.OpaqueDigest.High);
|
||||
hash.Add(submission.TransparentDigest.Low);
|
||||
hash.Add(submission.TransparentDigest.High);
|
||||
hash.Add(submission.TransparentSetDigest.Low);
|
||||
hash.Add(submission.TransparentSetDigest.High);
|
||||
hash.Add(submission.Digest.Low);
|
||||
hash.Add(submission.Digest.High);
|
||||
}
|
||||
|
||||
return hash.Finish();
|
||||
}
|
||||
|
||||
private static void BuildSelectionKeys(
|
||||
IReadOnlyList<CurrentRenderSelectionFingerprint> source,
|
||||
List<SelectionKey> destination)
|
||||
{
|
||||
destination.Clear();
|
||||
if (destination.Capacity < source.Count)
|
||||
destination.Capacity = source.Count;
|
||||
for (int index = 0; index < source.Count; index++)
|
||||
{
|
||||
CurrentRenderSelectionFingerprint part = source[index];
|
||||
destination.Add(new SelectionKey(
|
||||
part.ServerGuid,
|
||||
part.LocalEntityId,
|
||||
part.PartIndex,
|
||||
part.GfxObjId,
|
||||
part.LocalToWorld,
|
||||
part.Geometry));
|
||||
}
|
||||
destination.Sort(SelectionKeyComparer.Instance);
|
||||
}
|
||||
|
||||
private static RenderSceneHash128 BuildSelectionDigest(
|
||||
IReadOnlyList<SelectionKey> parts)
|
||||
{
|
||||
StableRenderHash128 hash = StableRenderHash128.Create();
|
||||
hash.Add(parts.Count);
|
||||
for (int index = 0; index < parts.Count; index++)
|
||||
{
|
||||
SelectionKey part = parts[index];
|
||||
hash.Add(part.ServerGuid);
|
||||
hash.Add(part.LocalEntityId);
|
||||
hash.Add(part.PartIndex);
|
||||
hash.Add(part.GfxObjId);
|
||||
hash.Add(part.LocalToWorld);
|
||||
hash.Add(part.Geometry.Low);
|
||||
hash.Add(part.Geometry.High);
|
||||
}
|
||||
|
||||
return hash.Finish();
|
||||
}
|
||||
|
||||
private PackedInputComparison ComparePackedInput(
|
||||
in RenderFrameView view,
|
||||
uint tupleLandblockId)
|
||||
|
|
@ -492,6 +787,45 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
RenderSceneHash128 ExpectedDigest,
|
||||
RenderSceneHash128 ActualDigest);
|
||||
|
||||
private readonly record struct ClassifiedOutputComparison(
|
||||
int ExpectedDrawCount,
|
||||
int ActualDrawCount,
|
||||
RenderSceneHash128 ExpectedDigest,
|
||||
RenderSceneHash128 ActualDigest,
|
||||
int ExpectedSelectionCount,
|
||||
int ActualSelectionCount,
|
||||
RenderSceneHash128 ExpectedSelectionDigest,
|
||||
RenderSceneHash128 ActualSelectionDigest);
|
||||
|
||||
private readonly record struct SelectionKey(
|
||||
uint ServerGuid,
|
||||
uint LocalEntityId,
|
||||
int PartIndex,
|
||||
uint GfxObjId,
|
||||
Matrix4x4 LocalToWorld,
|
||||
RenderSceneHash128 Geometry);
|
||||
|
||||
private sealed class SelectionKeyComparer : IComparer<SelectionKey>
|
||||
{
|
||||
public static SelectionKeyComparer Instance { get; } = new();
|
||||
|
||||
private SelectionKeyComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int Compare(SelectionKey left, SelectionKey right)
|
||||
{
|
||||
int value = left.LocalEntityId.CompareTo(
|
||||
right.LocalEntityId);
|
||||
if (value != 0) return value;
|
||||
value = left.PartIndex.CompareTo(right.PartIndex);
|
||||
if (value != 0) return value;
|
||||
value = left.GfxObjId.CompareTo(right.GfxObjId);
|
||||
if (value != 0) return value;
|
||||
return left.ServerGuid.CompareTo(right.ServerGuid);
|
||||
}
|
||||
}
|
||||
|
||||
private readonly record struct PackedInputKey(
|
||||
int DrawSequence,
|
||||
WbDrawDispatcher.EntitySet Set,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue