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

@ -8,6 +8,7 @@ using AcDream.Core.Physics;
using AcDream.Core.Physics.Motion;
using AcDream.Core.World;
using DatReaderWriter;
using AcDream.Content;
using DatReaderWriter.DBObjs;
using DatReaderWriter.Types;
using Silk.NET.OpenGL;
@ -44,7 +45,6 @@ public sealed class PortalTunnelPresentation : IDisposable
private readonly GL _gl;
private readonly WbDrawDispatcher _dispatcher;
private readonly SceneLightingUboBinding _lightUbo;
private readonly IWbMeshAdapter _meshAdapter;
private readonly Setup _setup;
private readonly uint _setupDid;
private readonly uint _animationDid;
@ -54,7 +54,7 @@ public sealed class PortalTunnelPresentation : IDisposable
private readonly PortalTunnelCamera _camera = new();
private readonly Random _random;
private readonly Action<string>? _displayNotice;
private readonly uint[] _registeredGfxObjects;
private readonly PortalMeshReferenceOwner _meshReferences;
private bool _visible;
private double _rotationElapsed;
@ -62,6 +62,8 @@ public sealed class PortalTunnelPresentation : IDisposable
private float _rotationStartAngle;
private float _rotationEndAngle;
private float _rotationCurrentAngle;
private bool _disposeRequested;
private bool _disposing;
private bool _disposed;
private PortalTunnelPresentation(
@ -80,7 +82,6 @@ public sealed class PortalTunnelPresentation : IDisposable
_gl = gl;
_dispatcher = dispatcher;
_lightUbo = lightUbo;
_meshAdapter = meshAdapter;
_setup = setup;
_setupDid = setupDid;
_animationDid = animationDid;
@ -99,13 +100,9 @@ public sealed class PortalTunnelPresentation : IDisposable
MeshRefs = SetupMesh.Flatten(setup),
};
_registeredGfxObjects = setup.Parts
.Select(part => (uint)part)
.Where(id => id != 0u)
.Distinct()
.ToArray();
foreach (uint gfxObjId in _registeredGfxObjects)
_meshAdapter.IncrementRefCount(gfxObjId);
_meshReferences = new PortalMeshReferenceOwner(
meshAdapter,
setup.Parts.Select(part => (ulong)(uint)part));
}
/// <summary>
@ -116,7 +113,7 @@ public sealed class PortalTunnelPresentation : IDisposable
/// </summary>
public static PortalTunnelPresentation CreateRequired(
GL gl,
DatCollection dats,
IDatReaderWriter dats,
IAnimationLoader animationLoader,
IAnimationHookSink hookSink,
WbDrawDispatcher dispatcher,
@ -180,6 +177,17 @@ public sealed class PortalTunnelPresentation : IDisposable
public uint SetupDid => _setupDid;
public uint AnimationDid => _animationDid;
/// <summary>
/// Publishes the synthetic scene's mesh ownership after the presentation
/// itself has been stored by <c>GameWindow</c>. A partial acquisition stays
/// reachable and is resumed or released by the same lifetime owner.
/// </summary>
internal void PrepareResources()
{
ThrowIfDisposed();
_meshReferences.Acquire();
}
/// <summary>
/// Retail <c>set_sequence_animation(anim, clear=1, low=1, fps=40)</c>
/// on the edge where portal space becomes visible.
@ -332,7 +340,8 @@ public sealed class PortalTunnelPresentation : IDisposable
});
}
private void ThrowIfDisposed() => ObjectDisposedException.ThrowIf(_disposed, this);
private void ThrowIfDisposed() =>
ObjectDisposedException.ThrowIf(_disposeRequested || _disposed, this);
/// <summary>
/// Retail stages animation hooks while advancing the sequence, then drains
@ -372,12 +381,23 @@ public sealed class PortalTunnelPresentation : IDisposable
public void Dispose()
{
if (_disposed)
if (_disposed || _disposing)
return;
Exit();
_animationHooks.Clear();
foreach (uint gfxObjId in _registeredGfxObjects)
_meshAdapter.DecrementRefCount(gfxObjId);
_disposed = true;
_disposeRequested = true;
_disposing = true;
try
{
_visible = false;
_animationHooks.Clear();
_sequence.ClearAnimations();
_meshReferences.Dispose();
if (_meshReferences.IsDisposed)
_disposed = true;
}
finally
{
_disposing = false;
}
}
}