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

@ -42,6 +42,7 @@ public struct StagedEmitter {
/// Contains vertex data and per-batch index/texture info, but NO GPU resources.
/// </summary>
public class ObjectMeshData {
private long _estimatedUploadBytes = -1;
public ulong ObjectId { get; set; }
public bool IsSetup { get; set; }
public VertexPositionNormalTexture[] Vertices { get; set; } = Array.Empty<VertexPositionNormalTexture>();
@ -92,6 +93,36 @@ public class ObjectMeshData {
/// <summary>Edge line vertices for Environment wireframe rendering.</summary>
public Vector3[] EdgeLines { get; set; } = Array.Empty<Vector3>();
/// <summary>
/// Returns the immutable prepared payload's CPU-to-GPU work estimate.
/// Mesh extraction finishes populating the object before publishing it to
/// App, so this value can be cached once and reused by the CPU cache,
/// staging queue, retry path, and frame-budget planner without repeatedly
/// walking every material batch under their locks.
/// </summary>
public long GetEstimatedUploadBytes()
{
long cached = System.Threading.Volatile.Read(ref _estimatedUploadBytes);
if (cached >= 0)
return cached;
long bytes = IsSetup ? 1024L : 0L;
bytes = checked(bytes + (long)Vertices.Length * VertexPositionNormalTexture.Size);
foreach (List<TextureBatchData> batches in TextureBatches.Values)
{
foreach (TextureBatchData batch in batches)
{
bytes = checked(bytes + batch.TextureData.LongLength);
bytes = checked(bytes + (long)batch.Indices.Count * sizeof(ushort));
}
}
if (EnvCellGeometry is not null)
bytes = checked(bytes + EnvCellGeometry.GetEstimatedUploadBytes());
System.Threading.Interlocked.CompareExchange(ref _estimatedUploadBytes, bytes, -1);
return System.Threading.Volatile.Read(ref _estimatedUploadBytes);
}
}
/// <summary>