Separate logical ownership, render publication, and GPU retirement across live entities, landblocks, particles, textures, mesh arenas, portal/UI teardown, and per-frame scratch storage. Add bounded DAT/texture caches, upload budgets, three-frame fence retirement, exact-incarnation appearance reconciliation, frame pacing, and extensive lifetime conformance coverage.\n\nThe seven-destination connected route now cuts peak working/private memory roughly in half, returns Caul to 125-153 FPS locally, and produces no WER or AMD reset.\n\nCo-authored-by: OpenAI Codex <codex@openai.com>
242 lines
7.8 KiB
C#
242 lines
7.8 KiB
C#
using Silk.NET.OpenGL;
|
|
using AcDream.App.Rendering;
|
|
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|