using System.Runtime.ExceptionServices; using AcDream.App.Rendering.Wb; using Silk.NET.OpenGL; namespace AcDream.App.Rendering; /// /// 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. /// 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(GL gl, string context, Func 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 create, Action 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 create, Action postcondition, Action 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 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; } } } }