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>
152 lines
4.9 KiB
C#
152 lines
4.9 KiB
C#
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;
|
|
}
|
|
}
|
|
}
|
|
}
|