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
89
src/AcDream.App/Rendering/GpuRetiredTerrainSlotAllocator.cs
Normal file
89
src/AcDream.App/Rendering/GpuRetiredTerrainSlotAllocator.cs
Normal file
|
|
@ -0,0 +1,89 @@
|
|||
using AcDream.Core.Terrain;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Adds GPU-frame retirement to the terrain renderer's CPU slot allocator.
|
||||
/// Removing a landblock stops future draws immediately, but its VBO/EBO slot
|
||||
/// is not returned for overwrite until older submitted draws have completed.
|
||||
/// </summary>
|
||||
internal sealed class GpuRetiredTerrainSlotAllocator
|
||||
{
|
||||
private readonly TerrainSlotAllocator _allocator;
|
||||
private readonly GpuRetirementLedger _retirementLedger;
|
||||
private readonly Dictionary<int, RetryableGpuResourceRelease> _pendingReleases = [];
|
||||
|
||||
public GpuRetiredTerrainSlotAllocator(
|
||||
int initialCapacity,
|
||||
IGpuResourceRetirementQueue retirement)
|
||||
{
|
||||
_allocator = new TerrainSlotAllocator(initialCapacity);
|
||||
_retirementLedger = new GpuRetirementLedger(
|
||||
retirement ?? throw new ArgumentNullException(nameof(retirement)));
|
||||
}
|
||||
|
||||
public int Capacity => _allocator.Capacity;
|
||||
public int LoadedCount => _allocator.LoadedCount;
|
||||
internal int PendingReleaseCount => _pendingReleases.Count;
|
||||
|
||||
public int Allocate(out bool needsGrow) => _allocator.Allocate(out needsGrow);
|
||||
|
||||
public void GrowTo(int newCapacity) => _allocator.GrowTo(newCapacity);
|
||||
|
||||
/// <summary>
|
||||
/// Returns a slot whose contents were never published to a GPU draw. No
|
||||
/// retirement fence is required because no submitted command can refer to
|
||||
/// it.
|
||||
/// </summary>
|
||||
public void ReleaseUnsubmitted(int slot)
|
||||
{
|
||||
if (_pendingReleases.ContainsKey(slot))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"A GPU-submitted terrain slot cannot be released as unsubmitted.");
|
||||
}
|
||||
|
||||
_allocator.Free(slot);
|
||||
}
|
||||
|
||||
public void FreeAfterGpuUse(int slot)
|
||||
{
|
||||
if (_pendingReleases.TryGetValue(slot, out RetryableGpuResourceRelease? pending))
|
||||
{
|
||||
try
|
||||
{
|
||||
_retirementLedger.RetryPendingPublication(pending);
|
||||
}
|
||||
catch when (pending.IsComplete)
|
||||
{
|
||||
// Completion is stronger than publication acknowledgement.
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
var release = new RetryableGpuResourceRelease(
|
||||
() => _allocator.Free(slot),
|
||||
() =>
|
||||
{
|
||||
if (!_pendingReleases.Remove(slot))
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
"Terrain-slot retirement lost its ownership record.");
|
||||
}
|
||||
});
|
||||
_pendingReleases.Add(slot, release);
|
||||
|
||||
try
|
||||
{
|
||||
_retirementLedger.Retire(release);
|
||||
}
|
||||
catch when (release.IsComplete)
|
||||
{
|
||||
// An immediate retirement queue can complete before surfacing a
|
||||
// wrapper failure. The slot is already safely reusable.
|
||||
}
|
||||
}
|
||||
|
||||
public void RetryPendingPublications() =>
|
||||
_retirementLedger.RetryPendingPublications();
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue