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
|
|
@ -257,6 +257,8 @@ public sealed class RetailPViewRenderer
|
|||
_lookInFrames,
|
||||
drawableCells,
|
||||
ctx.Cells,
|
||||
ctx.AnimatedEntityIds,
|
||||
ctx.PlayerLandblockId ?? 0,
|
||||
ctx.RootCell.IsOutdoorNode);
|
||||
return result;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -430,6 +430,7 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
|||
_world.Set(entry.Entity, record.Material);
|
||||
_world.Set(entry.Entity, record.DegradeState);
|
||||
_world.Set(entry.Entity, record.Source);
|
||||
_world.Set(entry.Entity, record.EntityPayload);
|
||||
OrDirty(
|
||||
entry.Entity,
|
||||
record.Id,
|
||||
|
|
@ -528,6 +529,7 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
|||
record.OwnerIncarnation,
|
||||
record.DirtyMask,
|
||||
record.Source,
|
||||
record.EntityPayload,
|
||||
tag);
|
||||
|
||||
private void WriteRecord(
|
||||
|
|
@ -546,6 +548,7 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
|||
_world.Set(entity, record.OwnerIncarnation);
|
||||
_world.Set(entity, record.DirtyMask);
|
||||
_world.Set(entity, record.Source);
|
||||
_world.Set(entity, record.EntityPayload);
|
||||
}
|
||||
|
||||
private RenderProjectionRecord ReadRecord(in SceneEntry entry) =>
|
||||
|
|
@ -563,7 +566,8 @@ internal sealed class ArchRenderScene : IRenderScene, IRenderSceneQuerySource
|
|||
_world.Get<RenderDegradeState>(entry.Entity),
|
||||
_world.Get<RenderSortKey>(entry.Entity),
|
||||
_world.Get<RenderDirtyMask>(entry.Entity),
|
||||
_world.Get<RenderSourceMetadata>(entry.Entity));
|
||||
_world.Get<RenderSourceMetadata>(entry.Entity),
|
||||
_world.Get<RenderEntityPayload>(entry.Entity));
|
||||
|
||||
private void Destroy(in SceneEntry entry)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -50,7 +50,10 @@ internal readonly record struct CurrentRenderDispatcherFingerprint(
|
|||
int MeshRefIndex,
|
||||
uint TupleLandblockId,
|
||||
uint CacheLandblockId,
|
||||
CurrentRenderProjectionFingerprint Projection);
|
||||
CurrentRenderProjectionFingerprint Projection,
|
||||
uint GfxObjId,
|
||||
Matrix4x4 PartTransform,
|
||||
RenderSceneHash128 SurfaceOverrides);
|
||||
|
||||
internal readonly record struct CurrentRenderDispatcherSubmission(
|
||||
int VisibleInstanceCount,
|
||||
|
|
@ -535,7 +538,13 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
MeshRefIndex: tuple.MeshRefIndex,
|
||||
TupleLandblockId: tuple.LandblockId,
|
||||
CacheLandblockId: cacheLandblockId,
|
||||
Projection: projection);
|
||||
Projection: projection,
|
||||
GfxObjId: tuple.Entity.MeshRefs[tuple.MeshRefIndex].GfxObjId,
|
||||
PartTransform:
|
||||
tuple.Entity.MeshRefs[tuple.MeshRefIndex].PartTransform,
|
||||
SurfaceOverrides: CreateSurfaceOverrideFingerprint(
|
||||
tuple.Entity.MeshRefs[tuple.MeshRefIndex]
|
||||
.SurfaceOverrides));
|
||||
_dispatcherCandidates.Add(candidate);
|
||||
AddDispatcherCandidate(ref _dispatcherHash, in candidate);
|
||||
}
|
||||
|
|
@ -730,6 +739,7 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
MeshRef mesh = entity.MeshRefs[meshIndex];
|
||||
geometry.Add(mesh.GfxObjId);
|
||||
geometry.Add(mesh.PartTransform);
|
||||
AddSurfaceOverrides(ref geometry, mesh.SurfaceOverrides);
|
||||
}
|
||||
|
||||
StableRenderHash128 appearance = StableRenderHash128.Create();
|
||||
|
|
@ -795,6 +805,54 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
Appearance: appearance.Finish());
|
||||
}
|
||||
|
||||
private static void AddSurfaceOverrides(
|
||||
ref StableRenderHash128 geometry,
|
||||
IReadOnlyDictionary<uint, uint>? overrides)
|
||||
{
|
||||
RenderSceneHash128 fingerprint =
|
||||
CreateSurfaceOverrideFingerprint(overrides);
|
||||
geometry.Add(fingerprint.Low);
|
||||
geometry.Add(fingerprint.High);
|
||||
}
|
||||
|
||||
internal static RenderSceneHash128 CreateSurfaceOverrideFingerprint(
|
||||
IReadOnlyDictionary<uint, uint>? overrides)
|
||||
{
|
||||
if (overrides is null)
|
||||
{
|
||||
StableRenderHash128 missing = StableRenderHash128.Create();
|
||||
missing.Add(-1);
|
||||
return missing.Finish();
|
||||
}
|
||||
|
||||
ulong xorLow = 0;
|
||||
ulong xorHigh = 0;
|
||||
ulong sumLow = 0;
|
||||
ulong sumHigh = 0;
|
||||
foreach ((uint surfaceId, uint replacementId) in overrides)
|
||||
{
|
||||
StableRenderHash128 pair = StableRenderHash128.Create();
|
||||
pair.Add(surfaceId);
|
||||
pair.Add(replacementId);
|
||||
RenderSceneHash128 value = pair.Finish();
|
||||
xorLow ^= value.Low;
|
||||
xorHigh ^= value.High;
|
||||
unchecked
|
||||
{
|
||||
sumLow += value.Low;
|
||||
sumHigh += value.High;
|
||||
}
|
||||
}
|
||||
|
||||
StableRenderHash128 geometry = StableRenderHash128.Create();
|
||||
geometry.Add(overrides.Count);
|
||||
geometry.Add(xorLow);
|
||||
geometry.Add(xorHigh);
|
||||
geometry.Add(sumLow);
|
||||
geometry.Add(sumHigh);
|
||||
return geometry.Finish();
|
||||
}
|
||||
|
||||
private static RenderSceneHash128 FingerprintSelectionGeometry(
|
||||
RetailSelectionMesh mesh)
|
||||
{
|
||||
|
|
@ -854,6 +912,10 @@ internal sealed class CurrentRenderSceneOracle :
|
|||
hash.Add(candidate.MeshRefIndex);
|
||||
hash.Add(candidate.TupleLandblockId);
|
||||
hash.Add(candidate.CacheLandblockId);
|
||||
hash.Add(candidate.GfxObjId);
|
||||
hash.Add(candidate.PartTransform);
|
||||
hash.Add(candidate.SurfaceOverrides.Low);
|
||||
hash.Add(candidate.SurfaceOverrides.High);
|
||||
CurrentRenderProjectionFingerprint projection = candidate.Projection;
|
||||
AddProjection(ref hash, in projection);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Selection;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
|
|
@ -36,6 +37,17 @@ internal readonly record struct RenderFrameTransformRecord(
|
|||
int PartIndex,
|
||||
Matrix4x4 LocalToWorld);
|
||||
|
||||
internal readonly record struct RenderFrameEntityCandidate(
|
||||
RenderProjectionRecord Projection,
|
||||
int MeshPartOffset,
|
||||
int MeshPartCount,
|
||||
bool Animated);
|
||||
|
||||
internal readonly record struct RenderFrameMeshPart(
|
||||
RenderProjectionId ProjectionId,
|
||||
int PartIndex,
|
||||
MeshRef MeshRef);
|
||||
|
||||
internal readonly record struct RenderFrameClassificationRecord(
|
||||
RenderProjectionId ProjectionId,
|
||||
int CandidateIndex,
|
||||
|
|
@ -77,7 +89,9 @@ internal readonly record struct RenderFrameDiagnosticCounts(
|
|||
int AlphaClassificationCount,
|
||||
int LightSetCount,
|
||||
int SelectionPartCount,
|
||||
int RouteCandidateCount);
|
||||
int RouteCandidateCount,
|
||||
int EntityCandidateCount,
|
||||
int MeshPartCount);
|
||||
|
||||
/// <summary>
|
||||
/// A validated borrowed view over one reusable frame arena. Copies are safe:
|
||||
|
|
@ -133,6 +147,12 @@ internal readonly struct RenderFrameView
|
|||
public ReadOnlySpan<RenderFrameTransformRecord> Transforms =>
|
||||
Arena.Transforms;
|
||||
|
||||
public ReadOnlySpan<RenderFrameEntityCandidate> EntityCandidates =>
|
||||
Arena.EntityCandidates;
|
||||
|
||||
public ReadOnlySpan<RenderFrameMeshPart> MeshParts =>
|
||||
Arena.MeshParts;
|
||||
|
||||
public ReadOnlySpan<RenderFrameClassificationRecord> Classifications =>
|
||||
Arena.Classifications;
|
||||
|
||||
|
|
@ -192,6 +212,8 @@ internal sealed class RenderFrameArena
|
|||
private RenderFrameCellRange[] _cellRanges = [];
|
||||
private RenderProjectionRecord[] _dynamics = [];
|
||||
private RenderFrameTransformRecord[] _transforms = [];
|
||||
private RenderFrameEntityCandidate[] _entityCandidates = [];
|
||||
private RenderFrameMeshPart[] _meshParts = [];
|
||||
private RenderFrameClassificationRecord[] _classifications = [];
|
||||
private RenderFrameObjectLightSet[] _lightSets = [];
|
||||
private RenderFrameSelectionPart[] _selectionParts = [];
|
||||
|
|
@ -202,6 +224,8 @@ internal sealed class RenderFrameArena
|
|||
private int _cellRangeCount;
|
||||
private int _dynamicCount;
|
||||
private int _transformCount;
|
||||
private int _entityCandidateCount;
|
||||
private int _meshPartCount;
|
||||
private int _classificationCount;
|
||||
private int _lightSetCount;
|
||||
private int _selectionPartCount;
|
||||
|
|
@ -234,7 +258,9 @@ internal sealed class RenderFrameArena
|
|||
_alphaClassificationCount,
|
||||
_lightSetCount,
|
||||
_selectionPartCount,
|
||||
_routeCandidateCount);
|
||||
_routeCandidateCount,
|
||||
_entityCandidateCount,
|
||||
_meshPartCount);
|
||||
|
||||
internal ReadOnlySpan<RenderProjectionRecord> OutdoorStaticCandidates =>
|
||||
_outdoor.AsSpan(0, _outdoorCount);
|
||||
|
|
@ -251,6 +277,12 @@ internal sealed class RenderFrameArena
|
|||
internal ReadOnlySpan<RenderFrameTransformRecord> Transforms =>
|
||||
_transforms.AsSpan(0, _transformCount);
|
||||
|
||||
internal ReadOnlySpan<RenderFrameEntityCandidate> EntityCandidates =>
|
||||
_entityCandidates.AsSpan(0, _entityCandidateCount);
|
||||
|
||||
internal ReadOnlySpan<RenderFrameMeshPart> MeshParts =>
|
||||
_meshParts.AsSpan(0, _meshPartCount);
|
||||
|
||||
internal ReadOnlySpan<RenderFrameClassificationRecord> Classifications =>
|
||||
_classifications.AsSpan(0, _classificationCount);
|
||||
|
||||
|
|
@ -284,6 +316,8 @@ internal sealed class RenderFrameArena
|
|||
_cellRangeCount = 0;
|
||||
_dynamicCount = 0;
|
||||
_transformCount = 0;
|
||||
_entityCandidateCount = 0;
|
||||
_meshPartCount = 0;
|
||||
_classificationCount = 0;
|
||||
_lightSetCount = 0;
|
||||
_selectionPartCount = 0;
|
||||
|
|
@ -397,6 +431,46 @@ internal sealed class RenderFrameArena
|
|||
_transforms[_transformCount++] = transform;
|
||||
}
|
||||
|
||||
internal void AddEntityCandidate(
|
||||
ulong epoch,
|
||||
in RenderProjectionRecord projection,
|
||||
bool animated)
|
||||
{
|
||||
EnsureBuilding(epoch);
|
||||
IReadOnlyList<MeshRef>? meshRefs =
|
||||
projection.EntityPayload.MeshRefs;
|
||||
int meshPartCount = meshRefs?.Count ?? 0;
|
||||
if (meshPartCount != projection.MeshSet.MeshCount)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Projection {projection.Id} declares "
|
||||
+ $"{projection.MeshSet.MeshCount} mesh parts but its "
|
||||
+ $"presentation payload contains {meshPartCount}.");
|
||||
}
|
||||
|
||||
int meshPartOffset = _meshPartCount;
|
||||
EnsureCapacity(
|
||||
ref _meshParts,
|
||||
checked(meshPartOffset + meshPartCount));
|
||||
for (int partIndex = 0; partIndex < meshPartCount; partIndex++)
|
||||
{
|
||||
_meshParts[_meshPartCount++] = new RenderFrameMeshPart(
|
||||
projection.Id,
|
||||
partIndex,
|
||||
meshRefs![partIndex]);
|
||||
}
|
||||
|
||||
EnsureCapacity(
|
||||
ref _entityCandidates,
|
||||
_entityCandidateCount + 1);
|
||||
_entityCandidates[_entityCandidateCount++] =
|
||||
new RenderFrameEntityCandidate(
|
||||
projection,
|
||||
meshPartOffset,
|
||||
meshPartCount,
|
||||
animated);
|
||||
}
|
||||
|
||||
internal void AddClassification(
|
||||
ulong epoch,
|
||||
in RenderFrameClassificationRecord classification)
|
||||
|
|
@ -529,6 +603,10 @@ internal sealed class RenderFrameArena
|
|||
{
|
||||
if (_selectionPartCount > 0)
|
||||
Array.Clear(_selectionParts, 0, _selectionPartCount);
|
||||
if (_entityCandidateCount > 0)
|
||||
Array.Clear(_entityCandidates, 0, _entityCandidateCount);
|
||||
if (_meshPartCount > 0)
|
||||
Array.Clear(_meshParts, 0, _meshPartCount);
|
||||
}
|
||||
|
||||
private static void EnsureCapacity<T>(ref T[] values, int required)
|
||||
|
|
@ -603,6 +681,11 @@ internal readonly struct RenderFrameWriter
|
|||
public void AddTransform(in RenderFrameTransformRecord transform) =>
|
||||
Arena.AddTransform(_epoch, in transform);
|
||||
|
||||
public void AddEntityCandidate(
|
||||
in RenderProjectionRecord projection,
|
||||
bool animated) =>
|
||||
Arena.AddEntityCandidate(_epoch, in projection, animated);
|
||||
|
||||
public void AddClassification(
|
||||
in RenderFrameClassificationRecord classification) =>
|
||||
Arena.AddClassification(_epoch, in classification);
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ internal sealed class RenderProjectionJournal
|
|||
if (prior.MeshSet != current.MeshSet
|
||||
|| prior.Material != current.Material
|
||||
|| prior.DegradeState != current.DegradeState
|
||||
|| prior.EntityPayload != current.EntityPayload
|
||||
|| prior.Source.GeometryFingerprint
|
||||
!= current.Source.GeometryFingerprint
|
||||
|| prior.Source.AppearanceFingerprint
|
||||
|
|
|
|||
|
|
@ -80,7 +80,11 @@ internal static class RenderProjectionRecordFactory
|
|||
fingerprint.Transform,
|
||||
fingerprint.Geometry,
|
||||
fingerprint.Appearance,
|
||||
fingerprint.Flags));
|
||||
fingerprint.Flags),
|
||||
new RenderEntityPayload(
|
||||
entity.MeshRefs,
|
||||
entity.PaletteOverride,
|
||||
entity.IsBuildingShell));
|
||||
}
|
||||
|
||||
private static (Vector3 Minimum, Vector3 Maximum) CalculateBounds(
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.World;
|
||||
|
||||
namespace AcDream.App.Rendering.Scene;
|
||||
|
||||
|
|
@ -194,6 +195,18 @@ internal readonly record struct RenderSourceMetadata(
|
|||
RenderSceneHash128 AppearanceFingerprint,
|
||||
uint CurrentProjectionFlags = 0);
|
||||
|
||||
/// <summary>
|
||||
/// Borrowed immutable presentation payload captured at a scene publication
|
||||
/// edge. WorldEntity replaces its MeshRefs collection when animated or
|
||||
/// appearance state changes, so retaining this exact collection reference is
|
||||
/// a snapshot rather than a callback into gameplay state. G2 copies it into
|
||||
/// the reusable frame arena before any dispatcher consumer observes it.
|
||||
/// </summary>
|
||||
internal readonly record struct RenderEntityPayload(
|
||||
IReadOnlyList<MeshRef> MeshRefs,
|
||||
PaletteOverride? PaletteOverride,
|
||||
bool IsBuildingShell);
|
||||
|
||||
internal readonly record struct RenderProjectionRecord(
|
||||
RenderProjectionId Id,
|
||||
RenderProjectionClass ProjectionClass,
|
||||
|
|
@ -208,7 +221,8 @@ internal readonly record struct RenderProjectionRecord(
|
|||
RenderDegradeState DegradeState,
|
||||
RenderSortKey SortKey,
|
||||
RenderDirtyMask DirtyMask,
|
||||
RenderSourceMetadata Source = default);
|
||||
RenderSourceMetadata Source = default,
|
||||
RenderEntityPayload EntityPayload = default);
|
||||
|
||||
internal enum RenderProjectionDeltaKind : byte
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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(
|
||||
|
|
|
|||
|
|
@ -105,6 +105,18 @@ internal sealed class StaticRenderProjectionJournal :
|
|||
PreviousTransform = new PreviousRenderTransform(
|
||||
retained.Record.Transform.LocalToWorld),
|
||||
};
|
||||
if (accepted.Source.GeometryFingerprint
|
||||
== retained.Record.Source.GeometryFingerprint
|
||||
&& accepted.Source.AppearanceFingerprint
|
||||
== retained.Record.Source.AppearanceFingerprint
|
||||
&& accepted.EntityPayload.IsBuildingShell
|
||||
== retained.Record.EntityPayload.IsBuildingShell)
|
||||
{
|
||||
accepted = accepted with
|
||||
{
|
||||
EntityPayload = retained.Record.EntityPayload,
|
||||
};
|
||||
}
|
||||
if (accepted.ProjectionClass
|
||||
!= retained.Record.ProjectionClass)
|
||||
{
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue