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:
parent
3971997689
commit
749e8ceeb1
225 changed files with 29107 additions and 3914 deletions
|
|
@ -1,5 +1,6 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
|
|
@ -42,9 +43,18 @@ internal static class ParticleSubmissionOrdering
|
|||
/// </summary>
|
||||
internal sealed class ParticleMeshReferenceTracker : IDisposable
|
||||
{
|
||||
private sealed class ReferenceState
|
||||
{
|
||||
public required uint GfxObjId { get; init; }
|
||||
public bool Desired { get; set; }
|
||||
public bool Held { get; set; }
|
||||
public bool Reconciling { get; set; }
|
||||
}
|
||||
|
||||
private readonly Action<uint> _increment;
|
||||
private readonly Action<uint> _decrement;
|
||||
private readonly Dictionary<int, uint> _gfxByEmitter = new();
|
||||
private readonly Dictionary<int, ReferenceState> _referencesByEmitter = new();
|
||||
private bool _disposeRequested;
|
||||
private bool _disposed;
|
||||
|
||||
public ParticleMeshReferenceTracker(Action<uint> increment, Action<uint> decrement)
|
||||
|
|
@ -55,28 +65,137 @@ internal sealed class ParticleMeshReferenceTracker : IDisposable
|
|||
|
||||
public void Register(int emitterHandle, uint gfxObjId)
|
||||
{
|
||||
ObjectDisposedException.ThrowIf(_disposed, this);
|
||||
if (_gfxByEmitter.ContainsKey(emitterHandle))
|
||||
return;
|
||||
ObjectDisposedException.ThrowIf(_disposeRequested, this);
|
||||
if (!_referencesByEmitter.TryGetValue(emitterHandle, out ReferenceState? state))
|
||||
{
|
||||
state = new ReferenceState { GfxObjId = gfxObjId };
|
||||
_referencesByEmitter.Add(emitterHandle, state);
|
||||
}
|
||||
else if (state.GfxObjId != gfxObjId)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"Particle emitter {emitterHandle} is already associated with " +
|
||||
$"GfxObj 0x{state.GfxObjId:X8}, not 0x{gfxObjId:X8}.");
|
||||
}
|
||||
|
||||
_gfxByEmitter.Add(emitterHandle, gfxObjId);
|
||||
_increment(gfxObjId);
|
||||
state.Desired = true;
|
||||
Reconcile(emitterHandle, state);
|
||||
}
|
||||
|
||||
public void Release(int emitterHandle)
|
||||
{
|
||||
if (_disposed || !_gfxByEmitter.Remove(emitterHandle, out uint gfxObjId))
|
||||
if (_disposed || !_referencesByEmitter.TryGetValue(emitterHandle, out ReferenceState? state))
|
||||
return;
|
||||
_decrement(gfxObjId);
|
||||
|
||||
state.Desired = false;
|
||||
Reconcile(emitterHandle, state);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (_disposed)
|
||||
return;
|
||||
_disposed = true;
|
||||
foreach (uint gfxObjId in _gfxByEmitter.Values)
|
||||
_decrement(gfxObjId);
|
||||
_gfxByEmitter.Clear();
|
||||
|
||||
_disposeRequested = true;
|
||||
List<Exception>? failures = null;
|
||||
int[] emitterHandles = [.. _referencesByEmitter.Keys];
|
||||
for (int i = 0; i < emitterHandles.Length; i++)
|
||||
{
|
||||
int emitterHandle = emitterHandles[i];
|
||||
if (!_referencesByEmitter.TryGetValue(emitterHandle, out ReferenceState? state))
|
||||
continue;
|
||||
|
||||
state.Desired = false;
|
||||
try
|
||||
{
|
||||
Reconcile(emitterHandle, state);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
(failures ??= []).Add(error);
|
||||
}
|
||||
}
|
||||
|
||||
if (_referencesByEmitter.Count == 0)
|
||||
_disposed = true;
|
||||
|
||||
if (failures is not null)
|
||||
throw new AggregateException(
|
||||
"One or more particle mesh references failed to release.",
|
||||
failures);
|
||||
}
|
||||
|
||||
private void Reconcile(int emitterHandle, ReferenceState state)
|
||||
{
|
||||
if (state.Reconciling)
|
||||
return;
|
||||
|
||||
state.Reconciling = true;
|
||||
Exception? failure = null;
|
||||
try
|
||||
{
|
||||
while (state.Desired != state.Held)
|
||||
{
|
||||
if (state.Desired)
|
||||
{
|
||||
try
|
||||
{
|
||||
_increment(state.GfxObjId);
|
||||
state.Held = true;
|
||||
}
|
||||
catch (MeshReferenceMutationException error)
|
||||
{
|
||||
if (error.MutationCommitted)
|
||||
{
|
||||
state.Held = true;
|
||||
failure ??= error;
|
||||
continue;
|
||||
}
|
||||
failure = error;
|
||||
break;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
failure = error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
_decrement(state.GfxObjId);
|
||||
state.Held = false;
|
||||
}
|
||||
catch (MeshReferenceMutationException error)
|
||||
{
|
||||
if (error.MutationCommitted)
|
||||
{
|
||||
state.Held = false;
|
||||
failure ??= error;
|
||||
continue;
|
||||
}
|
||||
failure = error;
|
||||
break;
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
failure = error;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
state.Reconciling = false;
|
||||
if (!state.Desired && !state.Held)
|
||||
_referencesByEmitter.Remove(emitterHandle);
|
||||
if (_disposeRequested && _referencesByEmitter.Count == 0)
|
||||
_disposed = true;
|
||||
}
|
||||
|
||||
if (failure is not null)
|
||||
System.Runtime.ExceptionServices.ExceptionDispatchInfo.Capture(failure).Throw();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue