using Silk.NET.OpenGL; using AcDream.App.Rendering; namespace AcDream.App.Rendering.Wb; /// /// Always-on transaction boundary and accounting for raw dynamic GL objects. /// Growth keeps the published CPU capacity unchanged until BufferData succeeds; /// OpenGL leaves the previous data store intact when allocation reports an /// error, so the caller can continue using the old capacity or unwind. /// internal static unsafe class TrackedGlResource { public static RetryableGpuResourceRelease CreateRetryableBufferDeletion( GL gl, uint buffer, long capacityBytes, string context) { if (buffer == 0) throw new ArgumentOutOfRangeException(nameof(buffer)); ArgumentOutOfRangeException.ThrowIfNegative(capacityBytes); return new RetryableGpuResourceRelease( () => GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"), () => { gl.DeleteBuffer(buffer); // Per the GL error contract, a command which generates an // error does not change object state. Keep mutation and its // validation in one retryable stage so that failed deletion // is issued again, while later accounting remains untouched. GLHelpers.ThrowOnResourceError(gl, context); }, () => { if (capacityBytes != 0) GpuMemoryTracker.TrackDeallocation(capacityBytes, GpuResourceType.Buffer); }, () => GpuMemoryTracker.TrackResourceDeallocation(GpuResourceType.Buffer)); } public static RetryableGpuResourceRelease CreateRetryableVertexArrayDeletion( GL gl, uint vertexArray, string context, Action? trackDeallocation = null) { if (vertexArray == 0) throw new ArgumentOutOfRangeException(nameof(vertexArray)); return new RetryableGpuResourceRelease( () => GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"), () => { gl.DeleteVertexArray(vertexArray); GLHelpers.ThrowOnResourceError(gl, context); }, trackDeallocation ?? (() => GpuMemoryTracker.TrackResourceDeallocation(GpuResourceType.VAO))); } public static void AllocateBufferStorage( GL gl, BufferTargetARB target, uint buffer, long previousBytes, long newBytes, BufferUsageARB usage, string context) => AllocateBufferStorage( gl, (GLEnum)target, buffer, previousBytes, newBytes, (GLEnum)usage, context); public static void AllocateBufferStorage( GL gl, BufferTargetARB target, uint buffer, long previousBytes, long newBytes, BufferUsageARB usage, void* data, string context) => AllocateBufferStorage( gl, (GLEnum)target, buffer, previousBytes, newBytes, (GLEnum)usage, data, context); 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; } } public static uint CreateVertexArray(GL gl, string context) { GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"); uint name = gl.GenVertexArray(); try { if (name == 0) throw new InvalidOperationException($"OpenGL returned vertex-array name zero. Context: {context}"); GLHelpers.ThrowOnResourceError(gl, context); GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.VAO); return name; } catch { if (name != 0) gl.DeleteVertexArray(name); throw; } } public static void AllocateBufferStorage( GL gl, GLEnum target, uint buffer, long previousBytes, long newBytes, GLEnum usage, string context) => AllocateBufferStorage( gl, target, buffer, previousBytes, newBytes, usage, null, context); public static void AllocateBufferStorage( GL gl, GLEnum target, uint buffer, long previousBytes, long newBytes, GLEnum usage, void* data, string context) { ArgumentOutOfRangeException.ThrowIfNegative(previousBytes); ArgumentOutOfRangeException.ThrowIfLessThan(newBytes, 1); if (buffer == 0) throw new ArgumentOutOfRangeException(nameof(buffer)); GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"); gl.BindBuffer(target, buffer); gl.BufferData(target, checked((nuint)newBytes), data, usage); GLHelpers.ThrowOnResourceError(gl, context); long delta = checked(newBytes - previousBytes); if (delta > 0) GpuMemoryTracker.TrackAllocation(delta, GpuResourceType.Buffer); else if (delta < 0) GpuMemoryTracker.TrackDeallocation(-delta, GpuResourceType.Buffer); } public static void UpdateBufferSubData( GL gl, BufferTargetARB target, uint buffer, nint byteOffset, long byteCount, void* data, string context) => UpdateBufferSubData( gl, (GLEnum)target, buffer, byteOffset, byteCount, data, context); public static void UpdateBufferSubData( GL gl, GLEnum target, uint buffer, nint byteOffset, long byteCount, void* data, string context) { ArgumentOutOfRangeException.ThrowIfNegative(byteOffset); ArgumentOutOfRangeException.ThrowIfLessThan(byteCount, 1); if (buffer == 0) throw new ArgumentOutOfRangeException(nameof(buffer)); ArgumentNullException.ThrowIfNull(data); GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"); gl.BindBuffer(target, buffer); gl.BufferSubData(target, byteOffset, checked((nuint)byteCount), data); GLHelpers.ThrowOnResourceError(gl, context); } public static void DeleteBuffer(GL gl, uint buffer, long capacityBytes, string context) { if (buffer == 0) return; ArgumentOutOfRangeException.ThrowIfNegative(capacityBytes); GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"); gl.DeleteBuffer(buffer); GLHelpers.ThrowOnResourceError(gl, context); if (capacityBytes != 0) GpuMemoryTracker.TrackDeallocation(capacityBytes, GpuResourceType.Buffer); GpuMemoryTracker.TrackResourceDeallocation(GpuResourceType.Buffer); } public static void DeleteVertexArray(GL gl, uint vertexArray, string context) { if (vertexArray == 0) return; GLHelpers.ThrowOnResourceError(gl, $"{context} (precondition)"); gl.DeleteVertexArray(vertexArray); GLHelpers.ThrowOnResourceError(gl, context); GpuMemoryTracker.TrackResourceDeallocation(GpuResourceType.VAO); } }