refactor(lifetime): own render resource prefixes

Give terrain, sky, retained UI, portal preparation, and the update/render frame pair explicit single owners. Make shader, texture, text, bindless, and GL construction prefixes checked and retryable so partial failure cannot lose or replay resource ownership.

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Erik 2026-07-22 14:42:14 +02:00
parent fec0d94148
commit c87b15303d
41 changed files with 3768 additions and 355 deletions

View file

@ -1,5 +1,6 @@
using Silk.NET.OpenGL;
using Silk.NET.OpenGL.Extensions.ARB;
using AcDream.App.Rendering;
namespace AcDream.App.Rendering.Wb;
@ -10,10 +11,12 @@ namespace AcDream.App.Rendering.Wb;
/// </summary>
public sealed class BindlessSupport
{
private readonly GL _gl;
private readonly ArbBindlessTexture _ext;
private BindlessSupport(ArbBindlessTexture extension)
private BindlessSupport(GL gl, ArbBindlessTexture extension)
{
_gl = gl;
_ext = extension;
}
@ -21,7 +24,7 @@ public sealed class BindlessSupport
{
if (gl.TryGetExtension<ArbBindlessTexture>(out var ext))
{
support = new BindlessSupport(ext);
support = new BindlessSupport(gl, ext);
return true;
}
support = null;
@ -32,17 +35,42 @@ public sealed class BindlessSupport
/// Idempotent: handle is the same for a given texture name.</summary>
public ulong GetResidentHandle(uint textureName)
{
ulong h = _ext.GetTextureHandle(textureName);
if (!_ext.IsTextureHandleResident(h))
_ext.MakeTextureHandleResident(h);
ulong h = GlResourceCommand.Execute(
_gl,
$"get bindless handle for texture {textureName}",
() => _ext.GetTextureHandle(textureName));
if (h == 0)
throw new InvalidOperationException(
$"OpenGL returned no bindless handle for texture {textureName}.");
bool resident = GlResourceCommand.Execute(
_gl,
$"query bindless handle {h} residency",
() => _ext.IsTextureHandleResident(h));
if (!resident)
{
GlResourceCommand.Execute(
_gl,
$"make bindless handle {h} resident",
() => _ext.MakeTextureHandleResident(h));
}
return h;
}
/// <summary>Release residency for a handle. Call before deleting the underlying texture.</summary>
public void MakeNonResident(ulong handle)
{
if (_ext.IsTextureHandleResident(handle))
_ext.MakeTextureHandleNonResident(handle);
bool resident = GlResourceCommand.Execute(
_gl,
$"query bindless handle {handle} residency before release",
() => _ext.IsTextureHandleResident(handle));
if (!resident)
return;
GlResourceCommand.Execute(
_gl,
$"make bindless handle {handle} non-resident",
() => _ext.MakeTextureHandleNonResident(handle));
}
// Phase N.5b note: a `SetSamplerHandleUniform` wrapper was added in T6

View file

@ -1,5 +1,6 @@
using Silk.NET.OpenGL;
using AcDream.App.Rendering;
using System.Runtime.ExceptionServices;
namespace AcDream.App.Rendering.Wb;
@ -96,41 +97,68 @@ internal static unsafe class TrackedGlResource
public static uint CreateBuffer(GL gl, string context)
{
GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)");
uint name = gl.GenBuffer();
try
{
if (name == 0)
throw new InvalidOperationException($"OpenGL returned buffer name zero. Context: {context}");
GLHelpers.ThrowOnResourceError(gl, context);
GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Buffer);
return name;
}
catch
{
if (name != 0)
gl.DeleteBuffer(name);
throw;
}
return CreateTrackedName(
gl,
"buffer",
context,
gl.GenBuffer,
name => GlResourceCommand.DeleteBuffer(
gl,
name,
$"rollback buffer {name} after failed {context}"),
() => GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Buffer));
}
public static uint CreateVertexArray(GL gl, string context)
{
GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)");
uint name = gl.GenVertexArray();
return CreateTrackedName(
gl,
"vertex-array",
context,
gl.GenVertexArray,
name => GlResourceCommand.DeleteVertexArray(
gl,
name,
$"rollback vertex array {name} after failed {context}"),
() => GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.VAO));
}
private static uint CreateTrackedName(
GL gl,
string resourceName,
string context,
Func<uint> create,
Action<uint> rollback,
Action publishAccounting)
{
uint name = GlResourceCommand.CreateName(
gl,
$"{resourceName} for {context}",
create,
rollback);
var cleanup = new ResourceCleanupGroup();
cleanup.Add($"{resourceName} name {name}", () => rollback(name));
try
{
if (name == 0)
throw new InvalidOperationException($"OpenGL returned vertex-array name zero. Context: {context}");
GLHelpers.ThrowOnResourceError(gl, context);
GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.VAO);
publishAccounting();
return name;
}
catch
catch (Exception publicationFailure)
{
if (name != 0)
gl.DeleteVertexArray(name);
throw;
try
{
cleanup.RetryCleanup();
}
catch (Exception cleanupFailure)
{
throw new GlResourceConstructionException(
$"Publishing {resourceName} accounting failed and GL name {name} could not be released.",
cleanup,
[publicationFailure, cleanupFailure]);
}
ExceptionDispatchInfo.Capture(publicationFailure).Throw();
throw new InvalidOperationException("Unreachable resource-publication path.");
}
}