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:
parent
fec0d94148
commit
c87b15303d
41 changed files with 3768 additions and 355 deletions
152
src/AcDream.App/Rendering/GlResourceCommand.cs
Normal file
152
src/AcDream.App/Rendering/GlResourceCommand.cs
Normal file
|
|
@ -0,0 +1,152 @@
|
|||
using System.Runtime.ExceptionServices;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
||||
/// <summary>
|
||||
/// Always-on commit boundary for GL resource commands. OpenGL reports ordinary
|
||||
/// command failures through its error flag, so an ownership state machine may
|
||||
/// advance only after the post-command check succeeds.
|
||||
/// </summary>
|
||||
internal static class GlResourceCommand
|
||||
{
|
||||
public static void Execute(GL gl, string context, Action command)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gl);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(context);
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)");
|
||||
command();
|
||||
GLHelpers.ThrowOnResourceError(gl, context);
|
||||
}
|
||||
|
||||
public static T Execute<T>(GL gl, string context, Func<T> command)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(gl);
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(context);
|
||||
ArgumentNullException.ThrowIfNull(command);
|
||||
|
||||
GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)");
|
||||
T result = command();
|
||||
GLHelpers.ThrowOnResourceError(gl, context);
|
||||
return result;
|
||||
}
|
||||
|
||||
public static uint CreateName(
|
||||
GL gl,
|
||||
string resourceName,
|
||||
Func<uint> create,
|
||||
Action<uint> delete)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(resourceName);
|
||||
ArgumentNullException.ThrowIfNull(create);
|
||||
ArgumentNullException.ThrowIfNull(delete);
|
||||
|
||||
return CreateNameCore(
|
||||
resourceName,
|
||||
() => GLHelpers.ThrowOnResourceError(
|
||||
gl,
|
||||
$"create {resourceName} (precondition)"),
|
||||
create,
|
||||
() => GLHelpers.ThrowOnResourceError(gl, $"create {resourceName}"),
|
||||
ownedName => Execute(
|
||||
gl,
|
||||
$"rollback {resourceName} name {ownedName}",
|
||||
() => delete(ownedName)));
|
||||
}
|
||||
|
||||
internal static uint CreateNameCore(
|
||||
string resourceName,
|
||||
Action precondition,
|
||||
Func<uint> create,
|
||||
Action postcondition,
|
||||
Action<uint> deleteChecked)
|
||||
{
|
||||
ArgumentException.ThrowIfNullOrWhiteSpace(resourceName);
|
||||
ArgumentNullException.ThrowIfNull(precondition);
|
||||
ArgumentNullException.ThrowIfNull(create);
|
||||
ArgumentNullException.ThrowIfNull(postcondition);
|
||||
ArgumentNullException.ThrowIfNull(deleteChecked);
|
||||
|
||||
uint name = 0;
|
||||
Exception? creationFailure = null;
|
||||
try
|
||||
{
|
||||
precondition();
|
||||
name = create();
|
||||
if (name == 0)
|
||||
throw new InvalidOperationException($"OpenGL returned no {resourceName} name.");
|
||||
postcondition();
|
||||
return name;
|
||||
}
|
||||
catch (Exception failure)
|
||||
{
|
||||
creationFailure = failure;
|
||||
}
|
||||
|
||||
if (name != 0)
|
||||
{
|
||||
var cleanup = new SingleNameCleanup(name, deleteChecked);
|
||||
try
|
||||
{
|
||||
cleanup.RetryCleanup();
|
||||
}
|
||||
catch (Exception cleanupFailure)
|
||||
{
|
||||
throw new GlResourceConstructionException(
|
||||
$"Creating {resourceName} failed and its returned GL name could not be released.",
|
||||
cleanup,
|
||||
[creationFailure, cleanupFailure]);
|
||||
}
|
||||
}
|
||||
|
||||
ExceptionDispatchInfo.Capture(creationFailure).Throw();
|
||||
throw new InvalidOperationException("Unreachable GL resource-creation path.");
|
||||
}
|
||||
|
||||
public static uint CreateTexture(GL gl, string context) =>
|
||||
CreateName(gl, context, gl.GenTexture, gl.DeleteTexture);
|
||||
|
||||
public static void DeleteTexture(GL gl, uint texture, string context) =>
|
||||
Execute(gl, context, () => gl.DeleteTexture(texture));
|
||||
|
||||
public static void DeleteShader(GL gl, uint shader, string context) =>
|
||||
Execute(gl, context, () => gl.DeleteShader(shader));
|
||||
|
||||
public static void DeleteProgram(GL gl, uint program, string context) =>
|
||||
Execute(gl, context, () => gl.DeleteProgram(program));
|
||||
|
||||
public static void DeleteBuffer(GL gl, uint buffer, string context) =>
|
||||
Execute(gl, context, () => gl.DeleteBuffer(buffer));
|
||||
|
||||
public static void DeleteVertexArray(GL gl, uint vertexArray, string context) =>
|
||||
Execute(gl, context, () => gl.DeleteVertexArray(vertexArray));
|
||||
|
||||
private sealed class SingleNameCleanup(uint name, Action<uint> delete)
|
||||
: IRetryableResourceCleanup
|
||||
{
|
||||
private uint _name = name;
|
||||
private bool _running;
|
||||
|
||||
public bool IsCleanupComplete => _name == 0;
|
||||
|
||||
public void RetryCleanup()
|
||||
{
|
||||
if (_running || _name == 0)
|
||||
return;
|
||||
|
||||
_running = true;
|
||||
try
|
||||
{
|
||||
delete(_name);
|
||||
_name = 0;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_running = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue