refactor(render): delete the packed dispatcher
This commit is contained in:
parent
8d8aa715ee
commit
73ba1d3c32
7 changed files with 115 additions and 1659 deletions
|
|
@ -117,7 +117,6 @@ internal interface IRenderFrameEntityPassExecutor
|
|||
/// </summary>
|
||||
internal sealed partial class RetailPViewPassExecutor :
|
||||
IRetailPViewPassExecutor,
|
||||
IRenderFrameEntityPassExecutor,
|
||||
IOutdoorSceneParticleOwnerSource
|
||||
{
|
||||
private readonly IWorldPassSurface _surface;
|
||||
|
|
@ -214,30 +213,6 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
internal (int Width, int Height)? WalkAttachmentExtent =>
|
||||
_entities.WalkAttachmentExtent;
|
||||
|
||||
public void BeginEntityFrame(in RenderFrameView view) =>
|
||||
_entities.BeginPackedProductionFrame(in view);
|
||||
|
||||
public bool DrawEntityRoute(
|
||||
ICamera camera,
|
||||
in RenderFrameView view,
|
||||
RenderFrameCandidateRoute route,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
uint tupleLandblockId) =>
|
||||
_entities.DrawPackedProductionRoute(
|
||||
camera,
|
||||
in view,
|
||||
route,
|
||||
routeIndex,
|
||||
cellId,
|
||||
tupleLandblockId);
|
||||
|
||||
public void CompleteEntityFrame(in RenderFrameView view) =>
|
||||
_entities.CompletePackedProductionFrame(in view);
|
||||
|
||||
public void AbortEntityFrame() =>
|
||||
_entities.AbortPackedProductionFrame();
|
||||
|
||||
public void AbortFrame()
|
||||
{
|
||||
List<Exception>? failures = null;
|
||||
|
|
@ -406,18 +381,7 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
_terrainDiagnostics.Complete();
|
||||
|
||||
DisableClipDistances();
|
||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
||||
{
|
||||
RenderFrameView drawView = request.View;
|
||||
_entities.DrawPackedProductionRoute(
|
||||
frame.Camera,
|
||||
in drawView,
|
||||
request.Route,
|
||||
request.RouteIndex,
|
||||
request.CellId,
|
||||
request.TupleLandblockId);
|
||||
}
|
||||
else if (context.OutdoorEntities.Count > 0)
|
||||
if (context.OutdoorEntities.Count > 0)
|
||||
{
|
||||
var sceneryEntry = (
|
||||
frame.PlayerLandblockId ?? 0u,
|
||||
|
|
@ -449,18 +413,7 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
_surface.BindTerrainClip();
|
||||
|
||||
DisableClipDistances();
|
||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
||||
{
|
||||
RenderFrameView drawView = request.View;
|
||||
_entities.DrawPackedProductionRoute(
|
||||
frame.Camera,
|
||||
in drawView,
|
||||
request.Route,
|
||||
request.RouteIndex,
|
||||
request.CellId,
|
||||
request.TupleLandblockId);
|
||||
}
|
||||
else if (context.Dynamics.Count > 0)
|
||||
if (context.Dynamics.Count > 0)
|
||||
{
|
||||
var dynamicsEntry = (
|
||||
frame.PlayerLandblockId ?? 0u,
|
||||
|
|
@ -558,18 +511,7 @@ internal sealed partial class RetailPViewPassExecutor :
|
|||
_surface.BindTerrainClip();
|
||||
DisableClipDistances();
|
||||
|
||||
if (context.EntityDraw is RenderFrameEntityDrawRequest request)
|
||||
{
|
||||
RenderFrameView drawView = request.View;
|
||||
_entities.DrawPackedProductionRoute(
|
||||
frame.Camera,
|
||||
in drawView,
|
||||
request.Route,
|
||||
request.RouteIndex,
|
||||
request.CellId,
|
||||
request.TupleLandblockId);
|
||||
}
|
||||
else if (context.BuildingShells.Count > 0)
|
||||
if (context.BuildingShells.Count > 0)
|
||||
{
|
||||
var buildingEntry = (
|
||||
frame.PlayerLandblockId ?? 0u,
|
||||
|
|
|
|||
|
|
@ -1,251 +0,0 @@
|
|||
using System.Numerics;
|
||||
using System.Runtime.CompilerServices;
|
||||
using AcDream.App.Rendering.Scene;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
/// <summary>
|
||||
/// The appearance-only identity of one retained scene projection. Root
|
||||
/// transform, clip slot, light selection, and selection lighting deliberately
|
||||
/// stay outside this key: G3 updates those instance fields without rebuilding
|
||||
/// mesh/material classification.
|
||||
/// </summary>
|
||||
internal readonly record struct PackedClassificationIdentity(
|
||||
RenderOwnerIncarnation Incarnation,
|
||||
RenderMeshSet MeshSet,
|
||||
RenderMaterialVariant Material,
|
||||
RenderDegradeState DegradeState,
|
||||
RenderSceneHash128 Geometry,
|
||||
RenderSceneHash128 Appearance)
|
||||
{
|
||||
public static PackedClassificationIdentity From(
|
||||
in RenderProjectionRecord projection) =>
|
||||
new(
|
||||
projection.OwnerIncarnation,
|
||||
projection.MeshSet,
|
||||
projection.Material,
|
||||
projection.DegradeState,
|
||||
projection.Source.GeometryFingerprint,
|
||||
projection.Source.AppearanceFingerprint);
|
||||
}
|
||||
|
||||
internal readonly record struct PackedClassifiedBatch(
|
||||
GroupKey Key,
|
||||
Matrix4x4 RestPose,
|
||||
Vector3 LocalSortCenter);
|
||||
|
||||
internal readonly record struct PackedClassifiedSelectionPart(
|
||||
int PartIndex,
|
||||
uint GfxObjId,
|
||||
Matrix4x4 RestPose);
|
||||
|
||||
/// <summary>
|
||||
/// Reusable variable-size payload for one projection. Lists grow only when an
|
||||
/// appearance first exposes a higher batch/part count and are cleared in
|
||||
/// place on the next appearance rebuild.
|
||||
/// </summary>
|
||||
internal sealed class PackedProjectionClassificationEntry
|
||||
{
|
||||
public PackedClassificationIdentity Identity;
|
||||
public readonly List<PackedClassifiedBatch> Batches = [];
|
||||
public readonly List<PackedClassifiedSelectionPart> SelectionParts = [];
|
||||
public long LastBuiltFrame;
|
||||
public long LastSeenFrame;
|
||||
public bool ReusableAcrossFrames;
|
||||
public long AccountedPayloadBytes;
|
||||
|
||||
public void BeginRebuild(
|
||||
in PackedClassificationIdentity identity,
|
||||
long frame)
|
||||
{
|
||||
Identity = identity;
|
||||
Batches.Clear();
|
||||
SelectionParts.Clear();
|
||||
LastBuiltFrame = frame;
|
||||
LastSeenFrame = frame;
|
||||
ReusableAcrossFrames = false;
|
||||
}
|
||||
|
||||
public void ReleaseStorage()
|
||||
{
|
||||
Batches.Clear();
|
||||
SelectionParts.Clear();
|
||||
Batches.TrimExcess();
|
||||
SelectionParts.TrimExcess();
|
||||
}
|
||||
}
|
||||
|
||||
internal readonly record struct PackedClassificationCacheSnapshot(
|
||||
int ProjectionCount,
|
||||
int StaticRebuildCount,
|
||||
int SameFrameReuseCount,
|
||||
int CrossFrameReuseCount,
|
||||
int AnimatedClassificationCount,
|
||||
int RetiredProjectionCount,
|
||||
long RetainedPayloadBytes);
|
||||
|
||||
/// <summary>
|
||||
/// G3 retained classification owner. Structural classification is rebuilt
|
||||
/// only for first sight, incarnation/appearance changes, incomplete resource
|
||||
/// readiness, or active animation. Warm unchanged projections replay their
|
||||
/// retained batch templates while current transforms, lights, clip slots, and
|
||||
/// selection lighting are refreshed.
|
||||
/// </summary>
|
||||
internal sealed class PackedProjectionClassificationCache
|
||||
{
|
||||
private const RenderDirtyMask ClassificationDirtyMask =
|
||||
RenderDirtyMask.Appearance;
|
||||
|
||||
private readonly Dictionary<
|
||||
RenderProjectionId,
|
||||
PackedProjectionClassificationEntry> _entries = [];
|
||||
private readonly List<RenderProjectionId> _retired = [];
|
||||
private RenderSceneGeneration _generation;
|
||||
private bool _generationSet;
|
||||
private long _frame;
|
||||
private int _staticRebuildCount;
|
||||
private int _sameFrameReuseCount;
|
||||
private int _crossFrameReuseCount;
|
||||
private int _animatedClassificationCount;
|
||||
private int _retiredProjectionCount;
|
||||
private long _retainedPayloadBytes;
|
||||
|
||||
public long Frame => _frame;
|
||||
|
||||
public PackedClassificationCacheSnapshot Snapshot =>
|
||||
new(
|
||||
_entries.Count,
|
||||
_staticRebuildCount,
|
||||
_sameFrameReuseCount,
|
||||
_crossFrameReuseCount,
|
||||
_animatedClassificationCount,
|
||||
_retiredProjectionCount,
|
||||
_retainedPayloadBytes);
|
||||
|
||||
public void BeginFrame(RenderSceneGeneration generation)
|
||||
{
|
||||
if (_frame == long.MaxValue)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Packed classification frame identity was exhausted.");
|
||||
}
|
||||
|
||||
if (!_generationSet || generation != _generation)
|
||||
{
|
||||
Clear();
|
||||
_generation = generation;
|
||||
_generationSet = true;
|
||||
}
|
||||
|
||||
_frame++;
|
||||
_staticRebuildCount = 0;
|
||||
_sameFrameReuseCount = 0;
|
||||
_crossFrameReuseCount = 0;
|
||||
_animatedClassificationCount = 0;
|
||||
_retiredProjectionCount = 0;
|
||||
}
|
||||
|
||||
public bool TryGetReusable(
|
||||
RenderProjectionId id,
|
||||
in PackedClassificationIdentity identity,
|
||||
RenderDirtyMask dirtyMask,
|
||||
out PackedProjectionClassificationEntry? entry)
|
||||
{
|
||||
if (!_entries.TryGetValue(id, out entry)
|
||||
|| entry.Identity != identity)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
entry.LastSeenFrame = _frame;
|
||||
if (entry.LastBuiltFrame == _frame)
|
||||
{
|
||||
_sameFrameReuseCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!entry.ReusableAcrossFrames
|
||||
|| (dirtyMask & ClassificationDirtyMask) != 0)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
_crossFrameReuseCount++;
|
||||
return true;
|
||||
}
|
||||
|
||||
public PackedProjectionClassificationEntry BeginRebuild(
|
||||
RenderProjectionId id,
|
||||
in PackedClassificationIdentity identity)
|
||||
{
|
||||
if (!_entries.TryGetValue(id, out var entry))
|
||||
{
|
||||
entry = new PackedProjectionClassificationEntry();
|
||||
_entries.Add(id, entry);
|
||||
}
|
||||
|
||||
entry.BeginRebuild(in identity, _frame);
|
||||
_staticRebuildCount++;
|
||||
return entry;
|
||||
}
|
||||
|
||||
public void CompleteRebuild(
|
||||
PackedProjectionClassificationEntry entry,
|
||||
bool reusableAcrossFrames)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(entry);
|
||||
if (entry.LastBuiltFrame != _frame)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Only an entry rebuilt in the active frame can be completed.");
|
||||
}
|
||||
|
||||
entry.ReusableAcrossFrames = reusableAcrossFrames;
|
||||
long payloadBytes = checked(
|
||||
(long)entry.Batches.Capacity
|
||||
* Unsafe.SizeOf<PackedClassifiedBatch>()
|
||||
+ (long)entry.SelectionParts.Capacity
|
||||
* Unsafe.SizeOf<PackedClassifiedSelectionPart>());
|
||||
_retainedPayloadBytes = checked(
|
||||
_retainedPayloadBytes
|
||||
- entry.AccountedPayloadBytes
|
||||
+ payloadBytes);
|
||||
entry.AccountedPayloadBytes = payloadBytes;
|
||||
}
|
||||
|
||||
public void RecordAnimatedClassification() =>
|
||||
_animatedClassificationCount++;
|
||||
|
||||
public void EndFrame()
|
||||
{
|
||||
_retired.Clear();
|
||||
foreach ((RenderProjectionId id, var entry) in _entries)
|
||||
{
|
||||
if (entry.LastSeenFrame != _frame)
|
||||
{
|
||||
_retainedPayloadBytes -= entry.AccountedPayloadBytes;
|
||||
entry.AccountedPayloadBytes = 0;
|
||||
entry.ReleaseStorage();
|
||||
_retired.Add(id);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < _retired.Count; i++)
|
||||
_entries.Remove(_retired[i]);
|
||||
_retiredProjectionCount = _retired.Count;
|
||||
_retired.Clear();
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
foreach (PackedProjectionClassificationEntry entry
|
||||
in _entries.Values)
|
||||
{
|
||||
entry.ReleaseStorage();
|
||||
}
|
||||
_entries.Clear();
|
||||
_retired.Clear();
|
||||
_frame = 0;
|
||||
_retainedPayloadBytes = 0;
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load diff
|
|
@ -2,6 +2,7 @@ using System.Numerics;
|
|||
using AcDream.App.Rendering.Scene;
|
||||
using AcDream.App.Rendering.Selection;
|
||||
using AcDream.App.Rendering.Walk;
|
||||
using AcDream.Core.Lighting;
|
||||
using AcDream.Core.Meshing;
|
||||
using AcDream.Core.World;
|
||||
using DatReaderWriter.Enums;
|
||||
|
|
@ -166,7 +167,7 @@ public sealed partial class WbDrawDispatcher
|
|||
/// Classifies one static entity for the walk populator: resolves its clip
|
||||
/// slot / light set / selection lighting exactly as
|
||||
/// <c>ClassifyPackedEntity</c> does (via the shared
|
||||
/// <see cref="ResolveSlotForFrame"/> / <c>ResolvePackedLightSet</c>
|
||||
/// <see cref="ResolveSlotForFrame"/> / <c>ResolveWalkLightSet</c>
|
||||
/// helpers), walks its Setup parts or single mesh (mirroring
|
||||
/// <c>ClassifyPackedEntity</c>'s own shape), and appends one
|
||||
/// <see cref="WalkClassifiedBatch"/> per surviving batch to
|
||||
|
|
@ -204,7 +205,7 @@ public sealed partial class WbDrawDispatcher
|
|||
if (culled)
|
||||
return;
|
||||
|
||||
ResolvePackedLightSet(in entity, out InstanceLightSet lights, out bool indoor);
|
||||
ResolveWalkLightSet(in entity, out InstanceLightSet lights, out bool indoor);
|
||||
Vector2 selectionLighting =
|
||||
_selectionLighting?.TryGetLighting(
|
||||
entity.ServerGuid, entity.LocalEntityId, out RetailSelectionLighting lighting) == true
|
||||
|
|
@ -261,7 +262,7 @@ public sealed partial class WbDrawDispatcher
|
|||
}
|
||||
|
||||
float opacity = liveDynamic
|
||||
? PackedPartOpacity(
|
||||
? WalkPartOpacity(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
(uint)setupPartIndex)
|
||||
|
|
@ -298,7 +299,7 @@ public sealed partial class WbDrawDispatcher
|
|||
else
|
||||
{
|
||||
float opacity = liveDynamic
|
||||
? PackedPartOpacity(
|
||||
? WalkPartOpacity(
|
||||
entity.ServerGuid,
|
||||
entity.LocalEntityId,
|
||||
(uint)partIndex)
|
||||
|
|
@ -330,6 +331,110 @@ public sealed partial class WbDrawDispatcher
|
|||
}
|
||||
}
|
||||
|
||||
private float WalkPartOpacity(
|
||||
uint serverGuid,
|
||||
uint localEntityId,
|
||||
uint setupPartIndex)
|
||||
{
|
||||
float opacity = EntityOpacity(serverGuid);
|
||||
if (opacity <= 0f)
|
||||
return 0f;
|
||||
if (!_translucencyFades.TryGetCurrentValue(
|
||||
localEntityId,
|
||||
setupPartIndex,
|
||||
out float translucency))
|
||||
{
|
||||
return opacity;
|
||||
}
|
||||
|
||||
return translucency >= 1f
|
||||
? 0f
|
||||
: opacity * (1f - translucency);
|
||||
}
|
||||
|
||||
private void ResolveWalkLightSet(
|
||||
in RenderInstanceCandidate entity,
|
||||
out InstanceLightSet lights,
|
||||
out bool indoor)
|
||||
{
|
||||
indoor = IndoorObjectReceivesTorches(entity.ParentCell);
|
||||
lights = InstanceLightSet.Disabled;
|
||||
IReadOnlyList<LightSource>? snapshot = _pointSnapshot;
|
||||
if (!indoor || snapshot is null || snapshot.Count == 0)
|
||||
return;
|
||||
|
||||
Vector3 center =
|
||||
(entity.Bounds.Minimum + entity.Bounds.Maximum) * 0.5f;
|
||||
float radius =
|
||||
(entity.Bounds.Maximum - entity.Bounds.Minimum)
|
||||
.Length() * 0.5f;
|
||||
Span<int> selected =
|
||||
stackalloc int[LightManager.MaxLightsPerObject];
|
||||
selected.Fill(-1);
|
||||
LightManager.SelectForObject(
|
||||
snapshot,
|
||||
center,
|
||||
radius,
|
||||
selected);
|
||||
lights = InstanceLightSet.From(selected);
|
||||
}
|
||||
|
||||
private static bool PartVisibleInLookInTurn(
|
||||
IWalkLookInViewSource? lookInViews,
|
||||
int routeIndex,
|
||||
uint cellId,
|
||||
in RenderInstanceCandidate entity,
|
||||
int partIndex,
|
||||
uint gfxObjId,
|
||||
ObjectRenderData renderData,
|
||||
Matrix4x4 localToWorld)
|
||||
{
|
||||
if (lookInViews is null)
|
||||
return true;
|
||||
if (renderData.SelectionSphere is not { Radius: > 0f } sphere)
|
||||
return true;
|
||||
|
||||
return LookInDrawingSphereVisible(
|
||||
lookInViews,
|
||||
routeIndex,
|
||||
sphere,
|
||||
localToWorld,
|
||||
out _,
|
||||
out _);
|
||||
}
|
||||
|
||||
internal static bool LookInDrawingSphereVisible(
|
||||
IWalkLookInViewSource lookInViews,
|
||||
int routeIndex,
|
||||
DatReaderWriter.Types.Sphere sphere,
|
||||
Matrix4x4 localToWorld,
|
||||
out Vector3 center,
|
||||
out float radius)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(lookInViews);
|
||||
ArgumentNullException.ThrowIfNull(sphere);
|
||||
|
||||
center = Vector3.Transform(sphere.Origin, localToWorld);
|
||||
float scaleX = new Vector3(
|
||||
localToWorld.M11,
|
||||
localToWorld.M12,
|
||||
localToWorld.M13).Length();
|
||||
float scaleY = new Vector3(
|
||||
localToWorld.M21,
|
||||
localToWorld.M22,
|
||||
localToWorld.M23).Length();
|
||||
float scaleZ = new Vector3(
|
||||
localToWorld.M31,
|
||||
localToWorld.M32,
|
||||
localToWorld.M33).Length();
|
||||
radius = sphere.Radius
|
||||
* MathF.Max(scaleX, MathF.Max(scaleY, scaleZ));
|
||||
return lookInViews.SphereVisibleInLookInTurn(
|
||||
routeIndex,
|
||||
in center,
|
||||
radius);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walks one resolved mesh's batches through <see cref="TryClassifyBatch"/>
|
||||
/// and appends every surviving one to <paramref name="sink"/> at
|
||||
|
|
|
|||
|
|
@ -2305,18 +2305,9 @@ public sealed partial class WbDrawDispatcher : IDisposable
|
|||
|
||||
/// <summary>
|
||||
/// Campaign VM VM6 review fix round 3 (N2a): the ONE shared
|
||||
/// InstanceGroup-from-key construction seam. Before this there were two
|
||||
/// near-identical <c>new InstanceGroup { ... }</c> initializers — the
|
||||
/// classic route's <see cref="GetOrCreateInstanceGroup"/> and the packed
|
||||
/// route's <c>GetOrCreatePackedGroup</c> — that had already drifted once
|
||||
/// (the packed route's F1 bug: FoliageFlags copied on one side, dropped
|
||||
/// on the other). <paramref name="registration"/> and
|
||||
/// <paramref name="frame"/> are the only two values that legitimately
|
||||
/// differ per route (each owns its own registration counter and frame
|
||||
/// serial); every other field comes from <paramref name="key"/>, so a
|
||||
/// field neither site remembers to set can no longer exist. Same
|
||||
/// precedent as <see cref="AppendPackedInstance"/> — one writer, tested
|
||||
/// once, called from both routes.
|
||||
/// InstanceGroup-from-key construction seam retained by the classic
|
||||
/// diagnostic route. The production walk writes its ordered stream
|
||||
/// directly and the former packed twin was deleted at Campaign FW4.
|
||||
/// </summary>
|
||||
internal static InstanceGroup CreateGroupFromKey(
|
||||
GroupKey key,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue