perf(rendering): draw retained frame product

Make the incremental render scene the production entity source at the existing retail PView stages while retaining the accepted dispatcher upload and draw executor. Keep diagnostics consumer-gated, retain ordered indices across unchanged frames, refresh only dirty records, and preserve exact mesh-load, selection, alpha, lighting, and route lifecycle semantics.
This commit is contained in:
Erik 2026-07-25 04:12:23 +02:00
parent c7d7848dd2
commit ef1d263337
13 changed files with 1333 additions and 267 deletions

View file

@ -113,16 +113,15 @@ internal interface IRenderFrameProductSnapshotSource
}
/// <summary>
/// G1 compare-only owner. It builds the candidate frame product directly from
/// the incremental scene after the accepted PView traversal, borrows it in the
/// same render frame, and compares route membership against the current-path
/// observer. It never supplies a production draw input.
/// Owns the same-frame PView product. Diagnostic composition may compare it
/// with the retired current-path observer; production borrows the exact same
/// arena and supplies its ordered ranges directly to the dispatcher.
/// </summary>
internal sealed class RenderScenePViewFrameProductController :
IRenderFrameProductSnapshotSource
{
private readonly RenderSceneShadowRuntime _shadow;
private readonly CurrentRenderSceneOracle _current;
private readonly CurrentRenderSceneOracle? _current;
private readonly WbDrawDispatcher? _dispatcher;
private readonly RenderScenePViewFrameBuilder _builder = new();
private readonly RenderFrameExchange _exchange = new();
@ -155,12 +154,12 @@ internal sealed class RenderScenePViewFrameProductController :
public RenderScenePViewFrameProductController(
RenderSceneShadowRuntime shadow,
CurrentRenderSceneOracle current,
CurrentRenderSceneOracle? current = null,
Action<string>? log = null,
WbDrawDispatcher? dispatcher = null)
{
_shadow = shadow ?? throw new ArgumentNullException(nameof(shadow));
_current = current ?? throw new ArgumentNullException(nameof(current));
_current = current;
_log = log;
_dispatcher = dispatcher;
Snapshot = RenderFrameProductComparisonSnapshot.Disabled with
@ -171,6 +170,56 @@ internal sealed class RenderScenePViewFrameProductController :
public RenderFrameProductComparisonSnapshot Snapshot { get; private set; }
private CurrentRenderSceneOracle Current =>
_current
?? throw new InvalidOperationException(
"Current-path comparison was requested without a referee.");
public RenderFrameView BuildAndBorrow(
PortalVisibilityFrame portalFrame,
ClipFrameAssembly clipAssembly,
ViewconeCuller viewcone,
IReadOnlyList<PortalVisibilityFrame> lookInFrames,
HashSet<uint> drawableCells,
IRetailPViewCellSource cells,
HashSet<uint>? animatedEntityIds,
bool rootIsOutdoor)
{
ArgumentNullException.ThrowIfNull(portalFrame);
ArgumentNullException.ThrowIfNull(clipAssembly);
ArgumentNullException.ThrowIfNull(viewcone);
ArgumentNullException.ThrowIfNull(lookInFrames);
ArgumentNullException.ThrowIfNull(drawableCells);
ArgumentNullException.ThrowIfNull(cells);
ulong frameSequence = checked(++_productFrameSequence);
RenderSceneQuery scene = _shadow.Query;
RenderSceneDigest sourceDigest = _current is null
? new RenderSceneDigest(
scene.Generation,
scene.Counts,
default)
: _shadow.BuildDigest();
var input = new RenderScenePViewBuildInput(
scene,
sourceDigest,
portalFrame,
clipAssembly,
viewcone,
lookInFrames,
drawableCells,
cells,
animatedEntityIds,
rootIsOutdoor);
_builder.Build(_exchange, frameSequence, in input);
return _exchange.BorrowLatest(
scene.Generation,
frameSequence);
}
public void Release(in RenderFrameView view) =>
_exchange.Release(in view);
public void BuildAndCompare(
PortalVisibilityFrame portalFrame,
ClipFrameAssembly clipAssembly,
@ -183,18 +232,7 @@ internal sealed class RenderScenePViewFrameProductController :
bool rootIsOutdoor,
Vector3 cameraWorldPosition = default)
{
ArgumentNullException.ThrowIfNull(portalFrame);
ArgumentNullException.ThrowIfNull(clipAssembly);
ArgumentNullException.ThrowIfNull(viewcone);
ArgumentNullException.ThrowIfNull(lookInFrames);
ArgumentNullException.ThrowIfNull(drawableCells);
ArgumentNullException.ThrowIfNull(cells);
ulong frameSequence = checked(++_productFrameSequence);
RenderSceneQuery scene = _shadow.Query;
var input = new RenderScenePViewBuildInput(
scene,
_shadow.BuildDigest(),
RenderFrameView view = BuildAndBorrow(
portalFrame,
clipAssembly,
viewcone,
@ -203,10 +241,6 @@ internal sealed class RenderScenePViewFrameProductController :
cells,
animatedEntityIds,
rootIsOutdoor);
_builder.Build(_exchange, frameSequence, in input);
RenderFrameView view = _exchange.BorrowLatest(
scene.Generation,
frameSequence);
try
{
Compare(
@ -216,26 +250,92 @@ internal sealed class RenderScenePViewFrameProductController :
}
finally
{
_exchange.Release(in view);
Release(in view);
}
}
private void Compare(
in RenderFrameView view,
uint tupleLandblockId,
Vector3 cameraWorldPosition)
public void CompleteProduction(in RenderFrameView view)
{
_packedClassification =
_dispatcher?.PackedClassificationSnapshot ?? default;
if (_current is null)
{
_shadow.ClearDirty();
Snapshot = Snapshot with
{
Enabled = true,
ProductFrameSequence = view.FrameSequence,
ActualCandidateCount = view.RouteCandidates.Length,
PackedClassificationProjectionCount =
_packedClassification.ProjectionCount,
PackedClassificationStaticRebuildCount =
_packedClassification.StaticRebuildCount,
PackedClassificationSameFrameReuseCount =
_packedClassification.SameFrameReuseCount,
PackedClassificationCrossFrameReuseCount =
_packedClassification.CrossFrameReuseCount,
PackedClassificationAnimatedCount =
_packedClassification.AnimatedClassificationCount,
PackedClassificationRetiredCount =
_packedClassification.RetiredProjectionCount,
PackedClassificationRetainedPayloadBytes =
_packedClassification.RetainedPayloadBytes,
ProductCounts = view.DiagnosticCounts,
};
return;
}
CandidateComparison candidates =
CompareCandidateMembership(in view);
if (candidates.Successful)
_shadow.ClearDirty();
Snapshot = Snapshot with
{
Enabled = true,
ProductFrameSequence = view.FrameSequence,
CurrentPViewFrameSequence =
Current.Snapshot.CompletedPViewFrameSequence,
ComparisonCount = _comparisonCount,
SuccessfulComparisonCount = _successfulComparisonCount,
ExpectedCandidateCount = _expected.Count,
ActualCandidateCount = _actual.Count,
MismatchCount = _mismatchCount,
FirstMismatch = _firstMismatch,
ExpectedDigest = candidates.ExpectedDigest,
ActualDigest = candidates.ActualDigest,
PackedClassificationProjectionCount =
_packedClassification.ProjectionCount,
PackedClassificationStaticRebuildCount =
_packedClassification.StaticRebuildCount,
PackedClassificationSameFrameReuseCount =
_packedClassification.SameFrameReuseCount,
PackedClassificationCrossFrameReuseCount =
_packedClassification.CrossFrameReuseCount,
PackedClassificationAnimatedCount =
_packedClassification.AnimatedClassificationCount,
PackedClassificationRetiredCount =
_packedClassification.RetiredProjectionCount,
PackedClassificationRetainedPayloadBytes =
_packedClassification.RetainedPayloadBytes,
ProductCounts = view.DiagnosticCounts,
};
}
private CandidateComparison CompareCandidateMembership(
in RenderFrameView view)
{
_comparisonCount = checked(_comparisonCount + 1);
_expected.Clear();
_actual.Clear();
IReadOnlyList<CurrentRenderPViewCandidateFingerprint> expected =
_current.PViewCandidates;
Current.PViewCandidates;
if (_expected.Capacity < expected.Count)
_expected.Capacity = expected.Count;
for (int i = 0; i < expected.Count; i++)
for (int index = 0; index < expected.Count; index++)
{
CurrentRenderPViewCandidateFingerprint candidate = expected[i];
CurrentRenderPViewCandidateFingerprint candidate =
expected[index];
CurrentRenderProjectionFingerprint projection =
candidate.Projection;
_expected.Add(new CandidateKey(
@ -245,11 +345,15 @@ internal sealed class RenderScenePViewFrameProductController :
ExpectedId(in projection)));
}
ReadOnlySpan<RenderFrameCandidateRange> ranges = view.RouteRanges;
ReadOnlySpan<RenderProjectionRecord> records = view.RouteCandidates;
ReadOnlySpan<RenderFrameCandidateRange> ranges =
view.RouteRanges;
ReadOnlySpan<RenderProjectionRecord> records =
view.RouteCandidates;
if (_actual.Capacity < records.Length)
_actual.Capacity = records.Length;
for (int rangeIndex = 0; rangeIndex < ranges.Length; rangeIndex++)
for (int rangeIndex = 0;
rangeIndex < ranges.Length;
rangeIndex++)
{
RenderFrameCandidateRange range = ranges[rangeIndex];
int end = checked(range.Offset + range.Count);
@ -285,10 +389,32 @@ internal sealed class RenderScenePViewFrameProductController :
{
_firstMismatch = mismatch;
_log?.Invoke(
"[render-frame-product] first mismatch: " + mismatch);
"[render-frame-product] first mismatch: "
+ mismatch);
}
}
return new CandidateComparison(
mismatch is null,
expectedDigest,
actualDigest);
}
private void Compare(
in RenderFrameView view,
uint tupleLandblockId,
Vector3 cameraWorldPosition)
{
CandidateComparison candidates =
CompareCandidateMembership(in view);
RenderSceneHash128 expectedDigest =
candidates.ExpectedDigest;
RenderSceneHash128 actualDigest =
candidates.ActualDigest;
string? mismatch = candidates.Successful
? null
: _firstMismatch;
PackedInputComparison packed =
ComparePackedInput(in view, tupleLandblockId);
ClassifiedOutputComparison classified =
@ -312,7 +438,7 @@ internal sealed class RenderScenePViewFrameProductController :
Enabled: true,
ProductFrameSequence: view.FrameSequence,
CurrentPViewFrameSequence:
_current.Snapshot.CompletedPViewFrameSequence,
Current.Snapshot.CompletedPViewFrameSequence,
ComparisonCount: _comparisonCount,
SuccessfulComparisonCount: _successfulComparisonCount,
ExpectedCandidateCount: _expected.Count,
@ -395,7 +521,7 @@ internal sealed class RenderScenePViewFrameProductController :
_packedClassification =
dispatcher.PackedClassificationSnapshot;
IReadOnlyList<CurrentRenderDispatcherSubmission> expected =
_current.DispatcherSubmissions;
Current.DispatcherSubmissions;
IReadOnlyList<CurrentRenderDispatcherSubmission> actual =
dispatcher.PackedDispatcherSubmissions;
RenderSceneHash128 expectedDigest =
@ -426,7 +552,7 @@ internal sealed class RenderScenePViewFrameProductController :
}
IReadOnlyList<CurrentRenderSelectionFingerprint>
expectedSelection = _current.SelectionParts;
expectedSelection = Current.SelectionParts;
IReadOnlyList<CurrentRenderSelectionFingerprint>
actualSelection = dispatcher.PackedSelectionParts;
BuildSelectionKeys(
@ -628,7 +754,7 @@ internal sealed class RenderScenePViewFrameProductController :
_packedActual.Clear();
IReadOnlyList<CurrentRenderDispatcherFingerprint> expected =
_current.DispatcherCandidates;
Current.DispatcherCandidates;
if (_packedExpected.Capacity < expected.Count)
_packedExpected.Capacity = expected.Count;
for (int index = 0; index < expected.Count; index++)
@ -750,10 +876,10 @@ internal sealed class RenderScenePViewFrameProductController :
private string? ComparePackedInputKeys(int actualDrawCount)
{
if (_current.Snapshot.DispatcherDrawCount != actualDrawCount)
if (Current.Snapshot.DispatcherDrawCount != actualDrawCount)
{
return $"field=drawCount expected="
+ $"{_current.Snapshot.DispatcherDrawCount} "
+ $"{Current.Snapshot.DispatcherDrawCount} "
+ $"actual={actualDrawCount}";
}
if (_packedExpected.Count != _packedActual.Count)
@ -891,6 +1017,11 @@ internal sealed class RenderScenePViewFrameProductController :
RenderSceneHash128 ExpectedDigest,
RenderSceneHash128 ActualDigest);
private readonly record struct CandidateComparison(
bool Successful,
RenderSceneHash128 ExpectedDigest,
RenderSceneHash128 ActualDigest);
private readonly record struct ClassifiedOutputComparison(
bool Successful,
int ExpectedDrawCount,
@ -993,11 +1124,19 @@ internal sealed class RenderScenePViewFrameBuilder
private RenderProjectionRecord[] _outdoor = [];
private RenderProjectionRecord[] _dynamics = [];
private RenderProjectionRecord[] _cell = [];
private RenderProjectionRecord[] _dirty = [];
private RenderProjectionRecord[] _survivors = [];
private RenderProjectionRecord[] _cellRoute = [];
private readonly Dictionary<RenderProjectionId, int>
_outdoorPositions = [];
private readonly Dictionary<RenderProjectionId, int>
_dynamicPositions = [];
private int _outdoorCount;
private int _dynamicCount;
private int _cellRouteCount;
private int _dirtyCount;
private RenderSceneGeneration _indexGeneration;
private ulong _indexRevision;
public void Build(
RenderFrameExchange exchange,
@ -1023,6 +1162,7 @@ internal sealed class RenderScenePViewFrameBuilder
BuildCellStaticRoute(writer, in input);
BuildDynamicLastRoute(writer, in input);
writer.Publish();
AcknowledgeCachedDirtyRecords();
}
catch
{
@ -1034,21 +1174,97 @@ internal sealed class RenderScenePViewFrameBuilder
private void LoadSceneIndices(RenderSceneQuery scene)
{
RenderSceneIndexCounts counts = scene.IndexCounts;
EnsureCapacity(ref _outdoor, counts.OutdoorStatic);
_outdoorCount = scene.CopyIndexTo(
RenderSceneIndex.OutdoorStatic,
_outdoor);
_outdoorCount = CompactAndSort(
_outdoor,
_outdoorCount);
ulong revision = scene.IndexRevision;
if (scene.Generation != _indexGeneration
|| revision != _indexRevision)
{
EnsureCapacity(ref _outdoor, counts.OutdoorStatic);
_outdoorCount = scene.CopyIndexTo(
RenderSceneIndex.OutdoorStatic,
_outdoor);
_outdoorCount = CompactAndSort(
_outdoor,
_outdoorCount);
BuildPositionIndex(
_outdoor,
_outdoorCount,
_outdoorPositions);
EnsureCapacity(ref _dynamics, counts.Dynamic);
_dynamicCount = scene.CopyIndexTo(
RenderSceneIndex.Dynamic,
_dynamics);
_dynamicCount = CompactAndSort(
_dynamics,
_dynamicCount);
EnsureCapacity(ref _dynamics, counts.Dynamic);
_dynamicCount = scene.CopyIndexTo(
RenderSceneIndex.Dynamic,
_dynamics);
_dynamicCount = CompactAndSort(
_dynamics,
_dynamicCount);
BuildPositionIndex(
_dynamics,
_dynamicCount,
_dynamicPositions);
_indexGeneration = scene.Generation;
_indexRevision = revision;
}
EnsureCapacity(ref _dirty, counts.Dirty);
_dirtyCount = scene.CopyIndexTo(
RenderSceneIndex.Dirty,
_dirty);
for (int index = 0; index < _dirtyCount; index++)
{
RenderProjectionRecord record = _dirty[index];
if (_outdoorPositions.TryGetValue(
record.Id,
out int outdoorPosition))
{
_outdoor[outdoorPosition] = record;
}
if (_dynamicPositions.TryGetValue(
record.Id,
out int dynamicPosition))
{
_dynamics[dynamicPosition] = record;
}
}
}
private void AcknowledgeCachedDirtyRecords()
{
for (int index = 0; index < _dirtyCount; index++)
{
RenderProjectionId id = _dirty[index].Id;
if (_outdoorPositions.TryGetValue(
id,
out int outdoorPosition))
{
_outdoor[outdoorPosition] =
_outdoor[outdoorPosition] with
{
DirtyMask = RenderDirtyMask.None,
};
}
if (_dynamicPositions.TryGetValue(
id,
out int dynamicPosition))
{
_dynamics[dynamicPosition] =
_dynamics[dynamicPosition] with
{
DirtyMask = RenderDirtyMask.None,
};
}
}
}
private static void BuildPositionIndex(
RenderProjectionRecord[] records,
int count,
Dictionary<RenderProjectionId, int> positions)
{
positions.Clear();
positions.EnsureCapacity(count);
for (int index = 0; index < count; index++)
positions.Add(records[index].Id, index);
}
private void BuildOutdoorRoutes(