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>
89 lines
2.9 KiB
C#
89 lines
2.9 KiB
C#
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();
|
|
}
|