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:
parent
c7d7848dd2
commit
ef1d263337
13 changed files with 1333 additions and 267 deletions
|
|
@ -1,4 +1,5 @@
|
|||
using System.Numerics;
|
||||
using AcDream.Core.Rendering;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Selection;
|
||||
using AcDream.Core.Lighting;
|
||||
|
|
@ -10,10 +11,10 @@ using DatReaderWriter.Enums;
|
|||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// G2 compare-only classification of the scene-built frame product. This file
|
||||
/// deliberately shares the production dispatcher's exact mesh, texture,
|
||||
/// light, translucency, selection, grouping, and ordering owners while keeping
|
||||
/// its group storage separate. It never uploads or draws.
|
||||
/// Scene-frame classification and production submission. The retained group
|
||||
/// storage is also consumed by the G2/G3 compare oracle, while production
|
||||
/// routes reuse the dispatcher's exact mesh, texture, light, translucency,
|
||||
/// selection, upload, and draw owners.
|
||||
/// </summary>
|
||||
public sealed unsafe partial class WbDrawDispatcher
|
||||
{
|
||||
|
|
@ -35,6 +36,10 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
private long _packedGroupFrame;
|
||||
private long _nextPackedGroupRegistration = 1;
|
||||
private int _nextPackedInstanceSubmissionOrder;
|
||||
private bool _packedProductionFrameOpen;
|
||||
private RenderSceneGeneration _packedProductionGeneration;
|
||||
private ulong _packedProductionFrameSequence;
|
||||
private int _packedProductionNextRange;
|
||||
|
||||
internal IReadOnlyList<CurrentRenderDispatcherSubmission>
|
||||
PackedDispatcherSubmissions => _packedSubmissions;
|
||||
|
|
@ -51,104 +56,28 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
uint tupleLandblockId,
|
||||
Vector3 cameraWorldPosition)
|
||||
{
|
||||
if (_packedGroupFrame == long.MaxValue)
|
||||
if (_packedProductionFrameOpen)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed dispatcher frame identity was exhausted.");
|
||||
"The packed dispatcher oracle cannot replace an active production frame.");
|
||||
}
|
||||
|
||||
_packedGroupFrame++;
|
||||
PruneInstanceGroupsUnusedBeforeFrame(
|
||||
_packedGroups,
|
||||
_packedRetiredGroupKeys,
|
||||
_packedGroupFrame - 1);
|
||||
_packedEntityById.Clear();
|
||||
_packedSubmissions.Clear();
|
||||
_packedSelectionParts.Clear();
|
||||
_packedSelectionKeys.Clear();
|
||||
_packedClassificationCache.BeginFrame(view.Generation);
|
||||
|
||||
ReadOnlySpan<RenderFrameEntityCandidate> entities =
|
||||
view.EntityCandidates;
|
||||
for (int i = 0; i < entities.Length; i++)
|
||||
{
|
||||
if (!_packedEntityById.TryAdd(
|
||||
entities[i].Projection.Id,
|
||||
entities[i]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed dispatcher received duplicate projection "
|
||||
+ $"{entities[i].Projection.Id}.");
|
||||
}
|
||||
}
|
||||
|
||||
ReadOnlySpan<RenderFrameMeshPart> meshParts = view.MeshParts;
|
||||
ReadOnlySpan<RenderProjectionRecord> routeCandidates =
|
||||
view.RouteCandidates;
|
||||
BeginPackedFrameStorage(in view);
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
for (int rangeIndex = 0;
|
||||
rangeIndex < ranges.Length;
|
||||
rangeIndex++)
|
||||
{
|
||||
_nextPackedInstanceSubmissionOrder = 0;
|
||||
uint anyVao = 0;
|
||||
foreach (InstanceGroup group in _packedGroups.Values)
|
||||
group.ClearPerInstanceData();
|
||||
|
||||
RenderFrameCandidateRange range = ranges[rangeIndex];
|
||||
int end = checked(range.Offset + range.Count);
|
||||
if ((uint)range.Offset > (uint)routeCandidates.Length
|
||||
|| (uint)end > (uint)routeCandidates.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed dispatcher route {rangeIndex} exceeds "
|
||||
+ "candidate storage.");
|
||||
}
|
||||
|
||||
for (int routeIndex = range.Offset;
|
||||
routeIndex < end;
|
||||
routeIndex++)
|
||||
{
|
||||
RenderProjectionRecord projection =
|
||||
routeCandidates[routeIndex];
|
||||
if ((projection.Flags & RenderProjectionFlags.Draw) == 0)
|
||||
continue;
|
||||
if (!_packedEntityById.TryGetValue(
|
||||
projection.Id,
|
||||
out RenderFrameEntityCandidate source))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed route references absent entity "
|
||||
+ $"{projection.Id}.");
|
||||
}
|
||||
|
||||
int meshEnd = checked(
|
||||
source.MeshPartOffset + source.MeshPartCount);
|
||||
if ((uint)source.MeshPartOffset
|
||||
> (uint)meshParts.Length
|
||||
|| (uint)meshEnd > (uint)meshParts.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed entity {projection.Id} exceeds mesh "
|
||||
+ "part storage.");
|
||||
}
|
||||
|
||||
RenderInstanceCandidate candidate =
|
||||
RenderInstanceCandidate.FromFrame(
|
||||
in source,
|
||||
tupleLandblockId);
|
||||
ClassifyPackedEntity(
|
||||
in projection,
|
||||
in candidate,
|
||||
meshParts.Slice(
|
||||
source.MeshPartOffset,
|
||||
source.MeshPartCount),
|
||||
ref anyVao);
|
||||
}
|
||||
PackedRangeClassification classified =
|
||||
ClassifyPackedRange(
|
||||
in view,
|
||||
rangeIndex,
|
||||
tupleLandblockId,
|
||||
publishSelection: false);
|
||||
|
||||
bool deferTransparent = ShouldDeferPackedTransparent(
|
||||
anyVao,
|
||||
classified.AnyVao,
|
||||
_alphaQueue?.IsCollecting == true);
|
||||
InstanceLayoutCounts counts = PartitionInstanceGroups(
|
||||
_packedGroups.Values,
|
||||
|
|
@ -176,11 +105,236 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
_packedClassificationCache.EndFrame();
|
||||
}
|
||||
|
||||
internal void BeginPackedProductionFrame(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
if (_packedProductionFrameOpen)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The packed dispatcher cannot begin a second production frame.");
|
||||
}
|
||||
|
||||
BeginPackedFrameStorage(in view);
|
||||
_packedProductionFrameOpen = true;
|
||||
_packedProductionGeneration = view.Generation;
|
||||
_packedProductionFrameSequence = view.FrameSequence;
|
||||
_packedProductionNextRange = 0;
|
||||
}
|
||||
|
||||
internal bool DrawPackedProductionRoute(
|
||||
ICamera camera,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
uint tupleLandblockId)
|
||||
{
|
||||
ValidatePackedProductionView(in view);
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
if (_packedProductionNextRange >= ranges.Length)
|
||||
return false;
|
||||
|
||||
RenderFrameCandidateRange range =
|
||||
ranges[_packedProductionNextRange];
|
||||
if (range.Route != route
|
||||
|| range.RouteIndex != routeIndex
|
||||
|| range.CellId != cellId)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
int rangePosition = _packedProductionNextRange++;
|
||||
bool diagnosticsEnabled = BeginEntityDispatch(
|
||||
camera,
|
||||
out Matrix4x4 viewProjection,
|
||||
out Vector3 cameraWorldPosition);
|
||||
PackedRangeClassification classified =
|
||||
ClassifyPackedRange(
|
||||
in view,
|
||||
rangePosition,
|
||||
tupleLandblockId,
|
||||
publishSelection: true);
|
||||
ExecuteClassifiedGroups(
|
||||
viewProjection,
|
||||
cameraWorldPosition,
|
||||
classified.AnyVao,
|
||||
_packedGroups.Values,
|
||||
EntitySet.All,
|
||||
classified.CandidateCount,
|
||||
classified.MeshPartCount,
|
||||
diagnosticsEnabled,
|
||||
observeCurrentPath: false);
|
||||
return true;
|
||||
}
|
||||
|
||||
internal void CompletePackedProductionFrame(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
ValidatePackedProductionView(in view);
|
||||
int expected = view.RouteRanges.Length;
|
||||
if (_packedProductionNextRange != expected)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The packed dispatcher did not consume the complete route stream: "
|
||||
+ $"consumed={_packedProductionNextRange} expected={expected}.");
|
||||
}
|
||||
|
||||
_packedClassificationCache.EndFrame();
|
||||
ResetPackedProductionFrame();
|
||||
}
|
||||
|
||||
internal void AbortPackedProductionFrame()
|
||||
{
|
||||
if (!_packedProductionFrameOpen)
|
||||
return;
|
||||
|
||||
_packedClassificationCache.EndFrame();
|
||||
ResetPackedProductionFrame();
|
||||
}
|
||||
|
||||
private void BeginPackedFrameStorage(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
if (_packedGroupFrame == long.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed dispatcher frame identity was exhausted.");
|
||||
}
|
||||
|
||||
_packedGroupFrame++;
|
||||
PruneInstanceGroupsUnusedBeforeFrame(
|
||||
_packedGroups,
|
||||
_packedRetiredGroupKeys,
|
||||
_packedGroupFrame - 1);
|
||||
_packedEntityById.Clear();
|
||||
_packedSubmissions.Clear();
|
||||
_packedSelectionParts.Clear();
|
||||
_packedSelectionKeys.Clear();
|
||||
_packedClassificationCache.BeginFrame(view.Generation);
|
||||
|
||||
ReadOnlySpan<RenderFrameEntityCandidate> entities =
|
||||
view.EntityCandidates;
|
||||
for (int index = 0; index < entities.Length; index++)
|
||||
{
|
||||
if (!_packedEntityById.TryAdd(
|
||||
entities[index].Projection.Id,
|
||||
entities[index]))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed dispatcher received duplicate projection "
|
||||
+ $"{entities[index].Projection.Id}.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private PackedRangeClassification ClassifyPackedRange(
|
||||
in RenderFrameView view,
|
||||
int rangeIndex,
|
||||
uint tupleLandblockId,
|
||||
bool publishSelection)
|
||||
{
|
||||
ReadOnlySpan<RenderFrameCandidateRange> ranges =
|
||||
view.RouteRanges;
|
||||
if ((uint)rangeIndex >= (uint)ranges.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(rangeIndex));
|
||||
|
||||
_nextPackedInstanceSubmissionOrder = 0;
|
||||
foreach (InstanceGroup group in _packedGroups.Values)
|
||||
group.ClearPerInstanceData();
|
||||
|
||||
uint anyVao = 0;
|
||||
int meshPartCount = 0;
|
||||
RenderFrameCandidateRange range = ranges[rangeIndex];
|
||||
ReadOnlySpan<RenderProjectionRecord> routeCandidates =
|
||||
view.RouteCandidates;
|
||||
ReadOnlySpan<RenderFrameMeshPart> meshParts = view.MeshParts;
|
||||
int end = checked(range.Offset + range.Count);
|
||||
if ((uint)range.Offset > (uint)routeCandidates.Length
|
||||
|| (uint)end > (uint)routeCandidates.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed dispatcher route {rangeIndex} exceeds candidate storage.");
|
||||
}
|
||||
|
||||
for (int candidateIndex = range.Offset;
|
||||
candidateIndex < end;
|
||||
candidateIndex++)
|
||||
{
|
||||
RenderProjectionRecord projection =
|
||||
routeCandidates[candidateIndex];
|
||||
if ((projection.Flags & RenderProjectionFlags.Draw) == 0)
|
||||
continue;
|
||||
if (!_packedEntityById.TryGetValue(
|
||||
projection.Id,
|
||||
out RenderFrameEntityCandidate source))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed route references absent entity {projection.Id}.");
|
||||
}
|
||||
|
||||
int meshEnd = checked(
|
||||
source.MeshPartOffset + source.MeshPartCount);
|
||||
if ((uint)source.MeshPartOffset > (uint)meshParts.Length
|
||||
|| (uint)meshEnd > (uint)meshParts.Length)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Packed entity {projection.Id} exceeds mesh part storage.");
|
||||
}
|
||||
|
||||
meshPartCount = checked(
|
||||
meshPartCount + source.MeshPartCount);
|
||||
RenderInstanceCandidate candidate =
|
||||
RenderInstanceCandidate.FromFrame(
|
||||
in source,
|
||||
tupleLandblockId);
|
||||
ClassifyPackedEntity(
|
||||
in projection,
|
||||
in candidate,
|
||||
meshParts.Slice(
|
||||
source.MeshPartOffset,
|
||||
source.MeshPartCount),
|
||||
ref anyVao,
|
||||
publishSelection);
|
||||
}
|
||||
|
||||
return new PackedRangeClassification(
|
||||
anyVao,
|
||||
range.Count,
|
||||
meshPartCount);
|
||||
}
|
||||
|
||||
private void ValidatePackedProductionView(
|
||||
in RenderFrameView view)
|
||||
{
|
||||
if (!_packedProductionFrameOpen
|
||||
|| view.Generation != _packedProductionGeneration
|
||||
|| view.FrameSequence != _packedProductionFrameSequence)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"The packed dispatcher received a stale or foreign production frame.");
|
||||
}
|
||||
}
|
||||
|
||||
private void ResetPackedProductionFrame()
|
||||
{
|
||||
_packedProductionFrameOpen = false;
|
||||
_packedProductionGeneration = default;
|
||||
_packedProductionFrameSequence = 0;
|
||||
_packedProductionNextRange = 0;
|
||||
}
|
||||
|
||||
private readonly record struct PackedRangeClassification(
|
||||
uint AnyVao,
|
||||
int CandidateCount,
|
||||
int MeshPartCount);
|
||||
|
||||
private void ClassifyPackedEntity(
|
||||
in RenderProjectionRecord projection,
|
||||
in RenderInstanceCandidate entity,
|
||||
ReadOnlySpan<RenderFrameMeshPart> meshParts,
|
||||
ref uint anyVao)
|
||||
ref uint anyVao,
|
||||
bool publishSelection)
|
||||
{
|
||||
(uint slot, bool culled) = ResolveSlotForFrame(
|
||||
_clipRoutingActive,
|
||||
|
|
@ -231,7 +385,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
slot,
|
||||
lights,
|
||||
indoor,
|
||||
selectionLighting);
|
||||
selectionLighting,
|
||||
publishSelection);
|
||||
return;
|
||||
}
|
||||
|
||||
|
|
@ -272,6 +427,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (renderData is null)
|
||||
{
|
||||
reusableAcrossFrames = false;
|
||||
if (_missRequested.Add(meshRef.GfxObjId))
|
||||
_meshAdapter.EnsureLoaded(meshRef.GfxObjId);
|
||||
continue;
|
||||
}
|
||||
if (anyVao == 0)
|
||||
|
|
@ -291,6 +448,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
if (partData is null)
|
||||
{
|
||||
reusableAcrossFrames = false;
|
||||
if (_missRequested.Add(gfxObjId))
|
||||
_meshAdapter.EnsureLoaded(gfxObjId);
|
||||
continue;
|
||||
}
|
||||
|
||||
|
|
@ -334,7 +493,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in entity,
|
||||
selectionPartIndex,
|
||||
(uint)gfxObjId,
|
||||
model);
|
||||
model,
|
||||
publishSelection);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
|
@ -374,7 +534,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in entity,
|
||||
partIndex,
|
||||
meshRef.GfxObjId,
|
||||
model);
|
||||
model,
|
||||
publishSelection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -481,7 +642,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
uint slot,
|
||||
InstanceLightSet lights,
|
||||
bool indoor,
|
||||
Vector2 selectionLighting)
|
||||
Vector2 selectionLighting,
|
||||
bool publishSelection)
|
||||
{
|
||||
for (int i = 0; i < entry.Batches.Count; i++)
|
||||
{
|
||||
|
|
@ -504,7 +666,8 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in entity,
|
||||
part.PartIndex,
|
||||
part.GfxObjId,
|
||||
part.RestPose * entity.RootWorld);
|
||||
part.RestPose * entity.RootWorld,
|
||||
publishSelection);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -593,13 +756,29 @@ public sealed unsafe partial class WbDrawDispatcher
|
|||
in RenderInstanceCandidate entity,
|
||||
int partIndex,
|
||||
uint gfxObjId,
|
||||
Matrix4x4 localToWorld)
|
||||
Matrix4x4 localToWorld,
|
||||
bool publishSelection)
|
||||
{
|
||||
if (_selectionSink is not IRetailSelectionRenderOracle oracle
|
||||
|| !_packedSelectionKeys.Add(new PackedSelectionKey(
|
||||
if (!_packedSelectionKeys.Add(new PackedSelectionKey(
|
||||
entity.LocalEntityId,
|
||||
partIndex,
|
||||
gfxObjId))
|
||||
gfxObjId)))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (publishSelection)
|
||||
{
|
||||
_selectionSink?.AddVisiblePart(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
partIndex,
|
||||
gfxObjId,
|
||||
localToWorld);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_selectionSink is not IRetailSelectionRenderOracle oracle
|
||||
|| !oracle.TryCreateVisiblePart(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue