Move Region/environment, mandatory modern rendering, terrain, WB, texture, and sampler construction behind the typed Phase-4 composition boundary. Give every fallible GL constructor prefix retryable ownership so partial startup failure cannot leak or replay resource deletion while preserving the accepted render path and DAT inputs. Co-authored-by: Codex <codex@openai.com>
283 lines
9 KiB
C#
283 lines
9 KiB
C#
using Silk.NET.OpenGL;
|
|
using AcDream.App.Rendering;
|
|
using System.Runtime.ExceptionServices;
|
|
|
|
namespace AcDream.App.Rendering.Wb;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
internal static unsafe class TrackedGlResource
|
|
{
|
|
public static RetryableGpuResourceRelease CreateRetryableBufferDeletion(
|
|
GL gl,
|
|
uint buffer,
|
|
long capacityBytes,
|
|
string context) =>
|
|
CreateRetryableBufferDeletion(
|
|
gl,
|
|
buffer,
|
|
() => capacityBytes,
|
|
context);
|
|
|
|
public static RetryableGpuResourceRelease CreateRetryableBufferDeletion(
|
|
GL gl,
|
|
uint buffer,
|
|
Func<long> capacityBytes,
|
|
string context)
|
|
{
|
|
if (buffer == 0)
|
|
throw new ArgumentOutOfRangeException(nameof(buffer));
|
|
ArgumentNullException.ThrowIfNull(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);
|
|
},
|
|
() =>
|
|
{
|
|
long bytes = capacityBytes();
|
|
ArgumentOutOfRangeException.ThrowIfNegative(bytes);
|
|
if (bytes != 0)
|
|
GpuMemoryTracker.TrackDeallocation(bytes, 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)
|
|
{
|
|
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)
|
|
{
|
|
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
|
|
{
|
|
publishAccounting();
|
|
return name;
|
|
}
|
|
catch (Exception publicationFailure)
|
|
{
|
|
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.");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
}
|