fix(rendering): bound portal resource lifetime

Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-18 21:35:16 +02:00
parent 3971997689
commit 749e8ceeb1
225 changed files with 29107 additions and 3914 deletions

View file

@ -15,7 +15,10 @@ namespace AcDream.App.Rendering.Vfx;
/// (<c>0x0051D180</c>). This registry is the modern, read-only seam exposing
/// those same final frames without coupling Core effects to the renderer.
/// </remarks>
public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityEffectCellSource
public sealed class EntityEffectPoseRegistry :
IEntityEffectPoseSource,
IEntityEffectCellSource,
IEntityEffectPoseChangeSource
{
private sealed class PoseRecord
{
@ -27,6 +30,8 @@ public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityE
private readonly Dictionary<uint, PoseRecord> _poses = new();
public event Action<uint>? EffectPoseChanged;
public int Count => _poses.Count;
public void Publish(WorldEntity entity, IReadOnlyList<Matrix4x4> partLocal)
@ -60,17 +65,24 @@ public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityE
Matrix4x4 rootWorld = Matrix4x4.CreateFromQuaternion(entity.Rotation)
* Matrix4x4.CreateTranslation(entity.Position);
bool changed;
if (!_poses.TryGetValue(entity.Id, out PoseRecord? record))
{
record = new PoseRecord();
_poses.Add(entity.Id, record);
changed = true;
}
else
{
changed = record.RootWorld != rootWorld
|| record.CellId != (entity.EffectCellId ?? entity.ParentCellId ?? 0u);
}
record.RootWorld = rootWorld;
record.CellId = entity.EffectCellId ?? entity.ParentCellId ?? 0u;
if (entity.IndexedPartTransforms.Count > 0)
{
CopyParts(
changed |= CopyParts(
record,
entity.IndexedPartTransforms,
entity.IndexedPartAvailable);
@ -78,15 +90,26 @@ public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityE
else
{
if (record.PartLocal.Length != entity.MeshRefs.Count)
{
record.PartLocal = new Matrix4x4[entity.MeshRefs.Count];
changed = true;
}
if (record.PartAvailable.Length != entity.MeshRefs.Count)
{
record.PartAvailable = new bool[entity.MeshRefs.Count];
changed = true;
}
for (int i = 0; i < entity.MeshRefs.Count; i++)
{
changed |= record.PartLocal[i] != entity.MeshRefs[i].PartTransform
|| !record.PartAvailable[i];
record.PartLocal[i] = entity.MeshRefs[i].PartTransform;
record.PartAvailable[i] = true;
}
}
if (changed)
EffectPoseChanged?.Invoke(entity.Id);
}
public void Publish(
@ -100,15 +123,23 @@ public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityE
return;
ArgumentNullException.ThrowIfNull(partLocal);
bool changed;
if (!_poses.TryGetValue(localEntityId, out PoseRecord? record))
{
record = new PoseRecord();
_poses.Add(localEntityId, record);
changed = true;
}
else
{
changed = record.RootWorld != rootWorld || record.CellId != cellId;
}
record.RootWorld = rootWorld;
record.CellId = cellId;
CopyParts(record, partLocal, availability);
changed |= CopyParts(record, partLocal, availability);
if (changed)
EffectPoseChanged?.Invoke(localEntityId);
}
/// <summary>Refresh only the moving root while retaining current part poses.</summary>
@ -117,15 +148,36 @@ public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityE
ArgumentNullException.ThrowIfNull(entity);
if (!_poses.TryGetValue(entity.Id, out PoseRecord? record))
return false;
record.RootWorld = Matrix4x4.CreateFromQuaternion(entity.Rotation)
Matrix4x4 rootWorld = Matrix4x4.CreateFromQuaternion(entity.Rotation)
* Matrix4x4.CreateTranslation(entity.Position);
record.CellId = entity.EffectCellId ?? entity.ParentCellId ?? 0u;
uint cellId = entity.EffectCellId ?? entity.ParentCellId ?? 0u;
if (record.RootWorld == rootWorld && record.CellId == cellId)
return true;
record.RootWorld = rootWorld;
record.CellId = cellId;
EffectPoseChanged?.Invoke(entity.Id);
return true;
}
public bool Remove(uint localEntityId) => _poses.Remove(localEntityId);
public bool Remove(uint localEntityId)
{
if (!_poses.Remove(localEntityId))
return false;
EffectPoseChanged?.Invoke(localEntityId);
return true;
}
public void Clear() => _poses.Clear();
public void Clear()
{
if (_poses.Count == 0)
return;
uint[] removedOwners = _poses.Keys.ToArray();
_poses.Clear();
foreach (uint owner in removedOwners)
EffectPoseChanged?.Invoke(owner);
}
public bool TryGetRootPose(uint localEntityId, out Matrix4x4 rootWorld)
{
@ -196,21 +248,32 @@ public sealed class EntityEffectPoseRegistry : IEntityEffectPoseSource, IEntityE
return false;
}
private static void CopyParts(
private static bool CopyParts(
PoseRecord record,
IReadOnlyList<Matrix4x4> partLocal,
IReadOnlyList<bool>? availability)
{
if (availability is not null && availability.Count != partLocal.Count)
throw new ArgumentException("Part pose and availability counts must match.");
bool changed = false;
if (record.PartLocal.Length != partLocal.Count)
{
record.PartLocal = new Matrix4x4[partLocal.Count];
changed = true;
}
if (record.PartAvailable.Length != partLocal.Count)
{
record.PartAvailable = new bool[partLocal.Count];
changed = true;
}
for (int i = 0; i < partLocal.Count; i++)
{
bool partAvailable = availability is null || availability[i];
changed |= record.PartLocal[i] != partLocal[i]
|| record.PartAvailable[i] != partAvailable;
record.PartLocal[i] = partLocal[i];
record.PartAvailable[i] = availability is null || availability[i];
record.PartAvailable[i] = partAvailable;
}
return changed;
}
}