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
|
|
@ -61,6 +61,9 @@ internal readonly record struct CurrentRenderDispatcherSubmission(
|
|||
int OpaqueGroupCount,
|
||||
int TransparentGroupCount,
|
||||
bool TransparentDeferred,
|
||||
RenderSceneHash128 OpaqueDigest,
|
||||
RenderSceneHash128 TransparentDigest,
|
||||
RenderSceneHash128 TransparentSetDigest,
|
||||
RenderSceneHash128 Digest);
|
||||
|
||||
internal readonly record struct CurrentRenderSelectionFingerprint(
|
||||
|
|
@ -209,6 +212,8 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
_pviewCandidates = [];
|
||||
private readonly List<CurrentRenderDispatcherFingerprint>
|
||||
_dispatcherCandidates = [];
|
||||
private readonly List<CurrentRenderDispatcherSubmission>
|
||||
_dispatcherSubmissions = [];
|
||||
private readonly Dictionary<WorldEntity, CurrentRenderProjectionFingerprint>
|
||||
_dispatcherProjectionByEntity =
|
||||
new(ReferenceEqualityComparer.Instance);
|
||||
|
|
@ -249,6 +254,8 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
PViewCandidates => _pviewCandidates;
|
||||
internal IReadOnlyList<CurrentRenderDispatcherFingerprint>
|
||||
DispatcherCandidates => _dispatcherCandidates;
|
||||
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
||||
DispatcherSubmissions => _dispatcherSubmissions;
|
||||
internal IReadOnlyList<CurrentRenderSelectionFingerprint>
|
||||
SelectionParts => _selectionParts;
|
||||
|
||||
|
|
@ -470,6 +477,7 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
{
|
||||
_dispatcherFrameOpen = true;
|
||||
_dispatcherCandidates.Clear();
|
||||
_dispatcherSubmissions.Clear();
|
||||
_dispatcherProjectionByEntity.Clear();
|
||||
_dispatcherDrawCount = 0;
|
||||
_dispatcherEntityCount = 0;
|
||||
|
|
@ -589,6 +597,7 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
|
||||
_dispatcherInstanceCount = checked(
|
||||
_dispatcherInstanceCount + submission.VisibleInstanceCount);
|
||||
_dispatcherSubmissions.Add(submission);
|
||||
_dispatcherOpaqueGroupCount = checked(
|
||||
_dispatcherOpaqueGroupCount + submission.OpaqueGroupCount);
|
||||
_dispatcherTransparentGroupCount = checked(
|
||||
|
|
@ -621,6 +630,7 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
|
||||
_dispatcherFrameOpen = false;
|
||||
_dispatcherCandidates.Clear();
|
||||
_dispatcherSubmissions.Clear();
|
||||
_dispatcherProjectionByEntity.Clear();
|
||||
_abortedDispatcherFrames = checked(_abortedDispatcherFrames + 1);
|
||||
Snapshot = Snapshot with
|
||||
|
|
@ -862,7 +872,7 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
return geometry.Finish();
|
||||
}
|
||||
|
||||
private static RenderSceneHash128 FingerprintSelectionGeometry(
|
||||
internal static RenderSceneHash128 FingerprintSelectionGeometry(
|
||||
RetailSelectionMesh mesh)
|
||||
{
|
||||
StableRenderHash128 geometry = StableRenderHash128.Create();
|
||||
|
|
|
|||
|
|
@ -69,6 +69,31 @@ internal readonly record struct RenderInstanceCandidate(
|
|||
MeshPartCount: meshPartCount,
|
||||
TupleLandblockId: tupleLandblockId);
|
||||
}
|
||||
|
||||
internal static RenderInstanceCandidate FromFrame(
|
||||
in RenderFrameEntityCandidate source,
|
||||
uint tupleLandblockId)
|
||||
{
|
||||
RenderProjectionRecord projection = source.Projection;
|
||||
return new RenderInstanceCandidate(
|
||||
ProjectionId: projection.Id,
|
||||
LocalEntityId: projection.Source.LocalEntityId,
|
||||
ServerGuid: projection.Source.ServerGuid,
|
||||
SourceId: projection.Source.SourceId,
|
||||
ParentCellId: projection.Source.ParentCellId,
|
||||
RootWorld: projection.Transform.LocalToWorld,
|
||||
Position: projection.Transform.Position,
|
||||
Rotation: projection.Transform.Rotation,
|
||||
Scale: projection.Transform.UniformScale,
|
||||
Bounds: projection.Bounds,
|
||||
PaletteOverride:
|
||||
projection.EntityPayload.PaletteOverride,
|
||||
IsBuildingShell:
|
||||
projection.EntityPayload.IsBuildingShell,
|
||||
Animated: source.Animated,
|
||||
MeshPartCount: source.MeshPartCount,
|
||||
TupleLandblockId: tupleLandblockId);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -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