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:
parent
720578592b
commit
d8d9897376
13 changed files with 593 additions and 24 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
|
|
@ -11,6 +12,7 @@ internal readonly record struct RenderScenePViewBuildInput(
|
|||
IReadOnlyList<PortalVisibilityFrame> LookInFrames,
|
||||
HashSet<uint> DrawableCells,
|
||||
IRetailPViewCellSource Cells,
|
||||
HashSet<uint>? AnimatedEntityIds,
|
||||
bool RootIsOutdoor);
|
||||
|
||||
internal readonly record struct RenderFrameProductComparisonSnapshot(
|
||||
|
|
@ -25,6 +27,14 @@ internal readonly record struct RenderFrameProductComparisonSnapshot(
|
|||
string? FirstMismatch,
|
||||
RenderSceneHash128 ExpectedDigest,
|
||||
RenderSceneHash128 ActualDigest,
|
||||
ulong PackedInputComparisonCount,
|
||||
ulong PackedInputSuccessfulComparisonCount,
|
||||
int PackedInputExpectedCount,
|
||||
int PackedInputActualCount,
|
||||
long PackedInputMismatchCount,
|
||||
string? PackedInputFirstMismatch,
|
||||
RenderSceneHash128 PackedInputExpectedDigest,
|
||||
RenderSceneHash128 PackedInputActualDigest,
|
||||
RenderFrameDiagnosticCounts ProductCounts)
|
||||
{
|
||||
public static RenderFrameProductComparisonSnapshot Disabled { get; } =
|
||||
|
|
@ -40,6 +50,14 @@ internal readonly record struct RenderFrameProductComparisonSnapshot(
|
|||
FirstMismatch: null,
|
||||
ExpectedDigest: RenderSceneHash128.Empty,
|
||||
ActualDigest: RenderSceneHash128.Empty,
|
||||
PackedInputComparisonCount: 0,
|
||||
PackedInputSuccessfulComparisonCount: 0,
|
||||
PackedInputExpectedCount: 0,
|
||||
PackedInputActualCount: 0,
|
||||
PackedInputMismatchCount: 0,
|
||||
PackedInputFirstMismatch: null,
|
||||
PackedInputExpectedDigest: RenderSceneHash128.Empty,
|
||||
PackedInputActualDigest: RenderSceneHash128.Empty,
|
||||
ProductCounts: default);
|
||||
}
|
||||
|
||||
|
|
@ -63,12 +81,18 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
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 Action<string>? _log;
|
||||
private ulong _productFrameSequence;
|
||||
private ulong _comparisonCount;
|
||||
private ulong _successfulComparisonCount;
|
||||
private long _mismatchCount;
|
||||
private string? _firstMismatch;
|
||||
private ulong _packedInputComparisonCount;
|
||||
private ulong _packedInputSuccessfulComparisonCount;
|
||||
private long _packedInputMismatchCount;
|
||||
private string? _packedInputFirstMismatch;
|
||||
|
||||
public RenderScenePViewFrameProductController(
|
||||
RenderSceneShadowRuntime shadow,
|
||||
|
|
@ -93,6 +117,8 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
IReadOnlyList<PortalVisibilityFrame> lookInFrames,
|
||||
HashSet<uint> drawableCells,
|
||||
IRetailPViewCellSource cells,
|
||||
HashSet<uint>? animatedEntityIds,
|
||||
uint tupleLandblockId,
|
||||
bool rootIsOutdoor)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(portalFrame);
|
||||
|
|
@ -113,6 +139,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
lookInFrames,
|
||||
drawableCells,
|
||||
cells,
|
||||
animatedEntityIds,
|
||||
rootIsOutdoor);
|
||||
_builder.Build(_exchange, frameSequence, in input);
|
||||
RenderFrameView view = _exchange.BorrowLatest(
|
||||
|
|
@ -120,7 +147,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
frameSequence);
|
||||
try
|
||||
{
|
||||
Compare(in view);
|
||||
Compare(in view, tupleLandblockId);
|
||||
}
|
||||
finally
|
||||
{
|
||||
|
|
@ -128,7 +155,7 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
}
|
||||
}
|
||||
|
||||
private void Compare(in RenderFrameView view)
|
||||
private void Compare(in RenderFrameView view, uint tupleLandblockId)
|
||||
{
|
||||
_comparisonCount = checked(_comparisonCount + 1);
|
||||
_expected.Clear();
|
||||
|
|
@ -196,6 +223,9 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
}
|
||||
}
|
||||
|
||||
PackedInputComparison packed =
|
||||
ComparePackedInput(in view, tupleLandblockId);
|
||||
|
||||
Snapshot = new RenderFrameProductComparisonSnapshot(
|
||||
Enabled: true,
|
||||
ProductFrameSequence: view.FrameSequence,
|
||||
|
|
@ -209,9 +239,184 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
FirstMismatch: _firstMismatch,
|
||||
ExpectedDigest: expectedDigest,
|
||||
ActualDigest: actualDigest,
|
||||
PackedInputComparisonCount: _packedInputComparisonCount,
|
||||
PackedInputSuccessfulComparisonCount:
|
||||
_packedInputSuccessfulComparisonCount,
|
||||
PackedInputExpectedCount: _packedExpected.Count,
|
||||
PackedInputActualCount: _packedActual.Count,
|
||||
PackedInputMismatchCount: _packedInputMismatchCount,
|
||||
PackedInputFirstMismatch: _packedInputFirstMismatch,
|
||||
PackedInputExpectedDigest: packed.ExpectedDigest,
|
||||
PackedInputActualDigest: packed.ActualDigest,
|
||||
ProductCounts: view.DiagnosticCounts);
|
||||
}
|
||||
|
||||
private PackedInputComparison ComparePackedInput(
|
||||
in RenderFrameView view,
|
||||
uint tupleLandblockId)
|
||||
{
|
||||
_packedInputComparisonCount =
|
||||
checked(_packedInputComparisonCount + 1);
|
||||
_packedExpected.Clear();
|
||||
_packedActual.Clear();
|
||||
|
||||
IReadOnlyList<CurrentRenderDispatcherFingerprint> expected =
|
||||
_current.DispatcherCandidates;
|
||||
if (_packedExpected.Capacity < expected.Count)
|
||||
_packedExpected.Capacity = expected.Count;
|
||||
for (int index = 0; index < expected.Count; index++)
|
||||
{
|
||||
CurrentRenderDispatcherFingerprint candidate = expected[index];
|
||||
CurrentRenderProjectionFingerprint projection =
|
||||
candidate.Projection;
|
||||
_packedExpected.Add(new PackedInputKey(
|
||||
candidate.DrawSequence,
|
||||
candidate.Set,
|
||||
ExpectedId(in projection),
|
||||
candidate.MeshRefIndex,
|
||||
candidate.TupleLandblockId,
|
||||
candidate.CacheLandblockId,
|
||||
projection.EntityId,
|
||||
projection.ServerGuid,
|
||||
projection.SourceId,
|
||||
projection.ParentCellId,
|
||||
projection.EffectCellId,
|
||||
projection.Flags,
|
||||
projection.Transform,
|
||||
projection.Geometry,
|
||||
projection.Appearance,
|
||||
candidate.GfxObjId,
|
||||
candidate.PartTransform,
|
||||
candidate.SurfaceOverrides));
|
||||
}
|
||||
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges = view.RouteRanges;
|
||||
ReadOnlySpan<RenderProjectionRecord> routeCandidates =
|
||||
view.RouteCandidates;
|
||||
if (_packedActual.Capacity < view.MeshParts.Length)
|
||||
_packedActual.Capacity = view.MeshParts.Length;
|
||||
for (int drawSequence = 0;
|
||||
drawSequence < ranges.Length;
|
||||
drawSequence++)
|
||||
{
|
||||
RenderFrameCandidateRange range = ranges[drawSequence];
|
||||
int end = checked(range.Offset + range.Count);
|
||||
for (int candidateIndex = range.Offset;
|
||||
candidateIndex < end;
|
||||
candidateIndex++)
|
||||
{
|
||||
RenderProjectionRecord projection =
|
||||
routeCandidates[candidateIndex];
|
||||
if ((projection.Flags & RenderProjectionFlags.Draw) == 0)
|
||||
continue;
|
||||
|
||||
IReadOnlyList<AcDream.Core.World.MeshRef>? meshRefs =
|
||||
projection.EntityPayload.MeshRefs;
|
||||
int meshCount = meshRefs?.Count ?? 0;
|
||||
for (int partIndex = 0;
|
||||
partIndex < meshCount;
|
||||
partIndex++)
|
||||
{
|
||||
AcDream.Core.World.MeshRef meshRef =
|
||||
meshRefs![partIndex];
|
||||
_packedActual.Add(new PackedInputKey(
|
||||
drawSequence,
|
||||
WbDrawDispatcher.EntitySet.All,
|
||||
projection.Id,
|
||||
partIndex,
|
||||
tupleLandblockId,
|
||||
DispatcherCacheLandblock(
|
||||
in projection,
|
||||
tupleLandblockId),
|
||||
projection.Source.LocalEntityId,
|
||||
projection.Source.ServerGuid,
|
||||
projection.Source.SourceId,
|
||||
projection.Source.ParentCellId,
|
||||
projection.Source.EffectCellId,
|
||||
projection.Source.CurrentProjectionFlags,
|
||||
projection.Source.TransformFingerprint,
|
||||
projection.Source.GeometryFingerprint,
|
||||
projection.Source.AppearanceFingerprint,
|
||||
meshRef.GfxObjId,
|
||||
meshRef.PartTransform,
|
||||
CurrentRenderSceneOracle
|
||||
.CreateSurfaceOverrideFingerprint(
|
||||
meshRef.SurfaceOverrides)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_packedExpected.Sort(PackedInputKeyComparer.Instance);
|
||||
_packedActual.Sort(PackedInputKeyComparer.Instance);
|
||||
RenderSceneHash128 expectedDigest =
|
||||
BuildPackedInputDigest(_packedExpected);
|
||||
RenderSceneHash128 actualDigest =
|
||||
BuildPackedInputDigest(_packedActual);
|
||||
string? mismatch = ComparePackedInputKeys(ranges.Length);
|
||||
if (mismatch is null)
|
||||
{
|
||||
_packedInputSuccessfulComparisonCount =
|
||||
checked(_packedInputSuccessfulComparisonCount + 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
_packedInputMismatchCount =
|
||||
checked(_packedInputMismatchCount + 1);
|
||||
if (_packedInputFirstMismatch is null)
|
||||
{
|
||||
_packedInputFirstMismatch = mismatch;
|
||||
_log?.Invoke(
|
||||
"[render-packed-input] first mismatch: " + mismatch);
|
||||
}
|
||||
}
|
||||
|
||||
return new PackedInputComparison(expectedDigest, actualDigest);
|
||||
}
|
||||
|
||||
private static uint DispatcherCacheLandblock(
|
||||
in RenderProjectionRecord projection,
|
||||
uint tupleLandblockId) =>
|
||||
projection.Source.ParentCellId != 0
|
||||
? (projection.Source.ParentCellId & 0xFFFF0000u) | 0xFFFFu
|
||||
: tupleLandblockId;
|
||||
|
||||
private string? ComparePackedInputKeys(int actualDrawCount)
|
||||
{
|
||||
if (_current.Snapshot.DispatcherDrawCount != actualDrawCount)
|
||||
{
|
||||
return $"field=drawCount expected="
|
||||
+ $"{_current.Snapshot.DispatcherDrawCount} "
|
||||
+ $"actual={actualDrawCount}";
|
||||
}
|
||||
if (_packedExpected.Count != _packedActual.Count)
|
||||
{
|
||||
return $"field=count expected={_packedExpected.Count} "
|
||||
+ $"actual={_packedActual.Count}";
|
||||
}
|
||||
|
||||
for (int index = 0; index < _packedExpected.Count; index++)
|
||||
{
|
||||
if (_packedExpected[index] != _packedActual[index])
|
||||
{
|
||||
return $"field=meshPart index={index} "
|
||||
+ $"expected={_packedExpected[index]} "
|
||||
+ $"actual={_packedActual[index]}";
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private static RenderSceneHash128 BuildPackedInputDigest(
|
||||
List<PackedInputKey> candidates)
|
||||
{
|
||||
StableRenderHash128 hash = StableRenderHash128.Create();
|
||||
hash.Add(candidates.Count);
|
||||
for (int index = 0; index < candidates.Count; index++)
|
||||
candidates[index].AddTo(ref hash);
|
||||
return hash.Finish();
|
||||
}
|
||||
|
||||
private string? CompareKeys()
|
||||
{
|
||||
if (_expected.Count != _actual.Count)
|
||||
|
|
@ -283,6 +488,57 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
uint CellId,
|
||||
RenderProjectionId ProjectionId);
|
||||
|
||||
private readonly record struct PackedInputComparison(
|
||||
RenderSceneHash128 ExpectedDigest,
|
||||
RenderSceneHash128 ActualDigest);
|
||||
|
||||
private readonly record struct PackedInputKey(
|
||||
int DrawSequence,
|
||||
WbDrawDispatcher.EntitySet Set,
|
||||
RenderProjectionId ProjectionId,
|
||||
int MeshRefIndex,
|
||||
uint TupleLandblockId,
|
||||
uint CacheLandblockId,
|
||||
uint LocalEntityId,
|
||||
uint ServerGuid,
|
||||
uint SourceId,
|
||||
uint ParentCellId,
|
||||
uint EffectCellId,
|
||||
uint SourceFlags,
|
||||
RenderSceneHash128 Transform,
|
||||
RenderSceneHash128 Geometry,
|
||||
RenderSceneHash128 Appearance,
|
||||
uint GfxObjId,
|
||||
Matrix4x4 PartTransform,
|
||||
RenderSceneHash128 SurfaceOverrides)
|
||||
{
|
||||
public void AddTo(ref StableRenderHash128 hash)
|
||||
{
|
||||
hash.Add(DrawSequence);
|
||||
hash.Add((int)Set);
|
||||
ProjectionId.AddTo(ref hash);
|
||||
hash.Add(MeshRefIndex);
|
||||
hash.Add(TupleLandblockId);
|
||||
hash.Add(CacheLandblockId);
|
||||
hash.Add(LocalEntityId);
|
||||
hash.Add(ServerGuid);
|
||||
hash.Add(SourceId);
|
||||
hash.Add(ParentCellId);
|
||||
hash.Add(EffectCellId);
|
||||
hash.Add(SourceFlags);
|
||||
hash.Add(Transform.Low);
|
||||
hash.Add(Transform.High);
|
||||
hash.Add(Geometry.Low);
|
||||
hash.Add(Geometry.High);
|
||||
hash.Add(Appearance.Low);
|
||||
hash.Add(Appearance.High);
|
||||
hash.Add(GfxObjId);
|
||||
hash.Add(PartTransform);
|
||||
hash.Add(SurfaceOverrides.Low);
|
||||
hash.Add(SurfaceOverrides.High);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class CandidateKeyComparer : IComparer<CandidateKey>
|
||||
{
|
||||
public static CandidateKeyComparer Instance { get; } = new();
|
||||
|
|
@ -302,6 +558,25 @@ internal sealed class RenderScenePViewFrameProductController :
|
|||
return left.ProjectionId.CompareTo(right.ProjectionId);
|
||||
}
|
||||
}
|
||||
|
||||
private sealed class PackedInputKeyComparer :
|
||||
IComparer<PackedInputKey>
|
||||
{
|
||||
public static PackedInputKeyComparer Instance { get; } = new();
|
||||
|
||||
private PackedInputKeyComparer()
|
||||
{
|
||||
}
|
||||
|
||||
public int Compare(PackedInputKey left, PackedInputKey right)
|
||||
{
|
||||
int value = left.DrawSequence.CompareTo(right.DrawSequence);
|
||||
if (value != 0) return value;
|
||||
value = left.ProjectionId.CompareTo(right.ProjectionId);
|
||||
if (value != 0) return value;
|
||||
return left.MeshRefIndex.CompareTo(right.MeshRefIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -313,7 +588,7 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
{
|
||||
private const byte EnvCellProjectionDomain = 2;
|
||||
|
||||
private readonly HashSet<RenderProjectionId> _transformIds = [];
|
||||
private readonly HashSet<RenderProjectionId> _projectionIds = [];
|
||||
private RenderProjectionRecord[] _outdoor = [];
|
||||
private RenderProjectionRecord[] _dynamics = [];
|
||||
private RenderProjectionRecord[] _cell = [];
|
||||
|
|
@ -338,7 +613,7 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
input.ClipAssembly);
|
||||
RenderSceneDigest digest = input.SourceDigest;
|
||||
writer.SetSourceDigest(in digest);
|
||||
_transformIds.Clear();
|
||||
_projectionIds.Clear();
|
||||
LoadSceneIndices(input.Scene);
|
||||
|
||||
BuildOutdoorRoutes(writer, in input);
|
||||
|
|
@ -398,7 +673,10 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
|
||||
_survivors[count++] = record;
|
||||
writer.AddOutdoor(in record);
|
||||
AddTransform(writer, in record);
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
writer.AddRouteRange(
|
||||
|
|
@ -429,7 +707,10 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
continue;
|
||||
|
||||
for (int index = 0; index < count; index++)
|
||||
AddTransform(writer, in _cell[index]);
|
||||
AddProjection(
|
||||
writer,
|
||||
in _cell[index],
|
||||
input.AnimatedEntityIds);
|
||||
writer.AddRouteRange(
|
||||
RenderFrameCandidateRoute.LookInObject,
|
||||
i,
|
||||
|
|
@ -471,7 +752,10 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
|
||||
_survivors[count++] = record;
|
||||
writer.AddDynamic(in record);
|
||||
AddTransform(writer, in record);
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
writer.AddRouteRange(
|
||||
|
|
@ -512,7 +796,10 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
}
|
||||
|
||||
_cell[count++] = record;
|
||||
AddTransform(writer, in record);
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
writer.AddCellRange(
|
||||
|
|
@ -563,7 +850,10 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
|
||||
_survivors[count++] = record;
|
||||
writer.AddDynamic(in record);
|
||||
AddTransform(writer, in record);
|
||||
AddProjection(
|
||||
writer,
|
||||
in record,
|
||||
input.AnimatedEntityIds);
|
||||
}
|
||||
|
||||
writer.AddRouteRange(
|
||||
|
|
@ -598,16 +888,20 @@ internal sealed class RenderScenePViewFrameBuilder
|
|||
return CompactAndSort(_cell, count);
|
||||
}
|
||||
|
||||
private void AddTransform(
|
||||
private void AddProjection(
|
||||
RenderFrameWriter writer,
|
||||
in RenderProjectionRecord record)
|
||||
in RenderProjectionRecord record,
|
||||
HashSet<uint>? animatedEntityIds)
|
||||
{
|
||||
if (!_transformIds.Add(record.Id))
|
||||
if (!_projectionIds.Add(record.Id))
|
||||
return;
|
||||
writer.AddTransform(new RenderFrameTransformRecord(
|
||||
record.Id,
|
||||
-1,
|
||||
record.Transform.LocalToWorld));
|
||||
writer.AddEntityCandidate(
|
||||
in record,
|
||||
animatedEntityIds?.Contains(record.Source.LocalEntityId) == true);
|
||||
}
|
||||
|
||||
private static int CompactAndSort(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue