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

@ -1,4 +1,5 @@
using System.Collections.Concurrent;
using AcDream.Content;
using DatReaderWriter;
using Microsoft.Extensions.Logging.Abstractions;
using Silk.NET.OpenGL;
@ -13,7 +14,7 @@ namespace AcDream.App.Rendering;
/// </summary>
public sealed record RenderStack(
GL Gl,
DatReaderWriter.DatCollection Dats,
IDatReaderWriter Dats,
string ShaderDir,
Wb.BindlessSupport Bindless,
TextureCache TextureCache,
@ -26,17 +27,47 @@ public sealed record RenderStack(
AcDream.App.UI.UiDatFont? VitalsDatFont,
AcDream.App.UI.UiDatFont? LargeDatFont) : System.IDisposable
{
internal GpuFrameFlightController FrameFlights { get; init; } = null!;
private ResourceShutdownTransaction? _shutdown;
internal void BeginFrame()
{
FrameFlights.BeginFrame();
UiHost.TextRenderer.BeginFrame(FrameFlights.CurrentSlot);
}
internal void EndFrame() => FrameFlights.EndFrame();
/// <summary>Dispose the GL pieces this stack OWNS (everything created in
/// <see cref="RenderBootstrap.Create"/>). <see cref="Dats"/> + <see cref="Gl"/> are caller-owned
/// and NOT disposed here. Called once at studio teardown.</summary>
public void Dispose()
{
DrawDispatcher.Dispose();
MeshAdapter.Dispose();
TextureCache.Dispose();
MeshShader.Dispose();
LightingUbo.Dispose();
UiHost.Dispose();
_shutdown ??= new ResourceShutdownTransaction(
new ResourceShutdownStage("submitted GPU work",
[
new("frame flight drain", FrameFlights.WaitForSubmittedWork),
]),
new ResourceShutdownStage("draw frontend",
[
new("draw dispatcher", DrawDispatcher.Dispose),
]),
new ResourceShutdownStage("mesh adapter",
[
new("mesh adapter", MeshAdapter.Dispose),
]),
new ResourceShutdownStage("remaining render stack",
[
new("texture cache", TextureCache.Dispose),
new("mesh shader", MeshShader.Dispose),
new("lighting UBO", LightingUbo.Dispose),
new("UI host", UiHost.Dispose),
]),
new ResourceShutdownStage("frame flight owner",
[
new("frame flights", FrameFlights.Dispose),
]));
_shutdown.CompleteOrThrow();
}
/// <summary>
@ -108,7 +139,7 @@ public static class RenderBootstrap
/// </summary>
public static RenderStack Create(
GL gl,
DatReaderWriter.DatCollection dats,
IDatReaderWriter dats,
RenderBootstrapOptions opts)
{
// --- Bindless detection (GameWindow ~1701-1723) ---
@ -132,14 +163,15 @@ public static class RenderBootstrap
Path.Combine(shaderDir, "mesh_modern.frag"));
// --- TextureCache (GameWindow ~1774) ---
var textureCache = new TextureCache(gl, dats, bindless);
var frameFlights = new GpuFrameFlightController(gl);
var textureCache = new TextureCache(gl, dats, bindless, frameFlights);
// --- AnimLoader (GameWindow ~1240) ---
var animLoader = new AcDream.Content.Vfx.RetailAnimationLoader(dats);
// --- WbMeshAdapter (GameWindow ~2286-2287) ---
var wbLogger = NullLogger<Wb.WbMeshAdapter>.Instance;
var meshAdapter = new Wb.WbMeshAdapter(gl, dats, wbLogger);
var meshAdapter = new Wb.WbMeshAdapter(gl, dats, wbLogger, frameFlights);
// --- SequencerFactory (GameWindow ~2306-2334) ---
var capturedDats = dats;
@ -216,7 +248,10 @@ public static class RenderBootstrap
LightingUbo: lightingUbo,
UiHost: uiHost,
VitalsDatFont: vitalsDatFont,
LargeDatFont: largeDatFont);
LargeDatFont: largeDatFont)
{
FrameFlights = frameFlights,
};
// Pre-seed the font cache with the two already-uploaded atlas instances
// so ResolveDatFont(0x40000000) and ResolveDatFont(0x40000001) hit the cache