refactor(lifetime): own render resource prefixes
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>
This commit is contained in:
parent
fec0d94148
commit
c87b15303d
41 changed files with 3768 additions and 355 deletions
|
|
@ -26,6 +26,7 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
private readonly GL _gl;
|
||||
private readonly ITextRenderGlStateApi _glState;
|
||||
private readonly Shader _shader;
|
||||
private readonly ResourceCleanupGroup _resources;
|
||||
private uint _vao;
|
||||
private uint _vbo;
|
||||
private readonly uint _whiteTex; // 1×1 white, for solid fills routed through the sprite bucket
|
||||
|
|
@ -39,7 +40,7 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
public int UsedBytes;
|
||||
}
|
||||
|
||||
private readonly FrameBufferSet[] _frameBuffers = new FrameBufferSet[3];
|
||||
private readonly FrameBufferSet[] _frameBuffers;
|
||||
private FrameBufferSet? _activeFrameBuffer;
|
||||
|
||||
internal long DynamicBufferCapacityBytes =>
|
||||
|
|
@ -80,26 +81,70 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
{
|
||||
_gl = gl;
|
||||
_glState = new SilkTextRenderGlStateApi(gl);
|
||||
_shader = new Shader(gl,
|
||||
Path.Combine(shaderDir, "ui_text.vert"),
|
||||
Path.Combine(shaderDir, "ui_text.frag"));
|
||||
var resources = new ResourceCleanupGroup();
|
||||
Shader? shader = null;
|
||||
var frameBuffers = new FrameBufferSet[3];
|
||||
uint whiteTexture = 0;
|
||||
try
|
||||
{
|
||||
shader = new Shader(gl,
|
||||
Path.Combine(shaderDir, "ui_text.vert"),
|
||||
Path.Combine(shaderDir, "ui_text.frag"));
|
||||
resources.Add("text shader", shader.Dispose);
|
||||
|
||||
for (int i = 0; i < _frameBuffers.Length; i++)
|
||||
_frameBuffers[i] = CreateFrameBufferSet();
|
||||
for (int i = 0; i < frameBuffers.Length; i++)
|
||||
frameBuffers[i] = CreateFrameBufferSet(resources);
|
||||
|
||||
// 1×1 white texture so DrawFill can route solid-colour quads through the SPRITE
|
||||
// bucket (the shader multiplies texel×color → white×color = color). Lets a panel
|
||||
// background draw UNDER its text in painter order, which DrawRect's separate
|
||||
// bucket cannot (it always composites after all sprites).
|
||||
_whiteTex = _gl.GenTexture();
|
||||
_gl.BindTexture(TextureTarget.Texture2D, _whiteTex);
|
||||
Span<byte> whitePixel = stackalloc byte[] { 255, 255, 255, 255 };
|
||||
fixed (byte* wp = whitePixel)
|
||||
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgba8, 1, 1, 0,
|
||||
PixelFormat.Rgba, PixelType.UnsignedByte, wp);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMinFilter.Nearest);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||
// 1×1 white texture so DrawFill can route solid-colour quads through the SPRITE
|
||||
// bucket (the shader multiplies texel×color → white×color = color). Lets a panel
|
||||
// background draw UNDER its text in painter order, which DrawRect's separate
|
||||
// bucket cannot (it always composites after all sprites).
|
||||
whiteTexture = GlResourceCommand.CreateTexture(
|
||||
_gl,
|
||||
"TextRenderer white texture");
|
||||
uint ownedWhiteTexture = whiteTexture;
|
||||
resources.Add(
|
||||
"white texture",
|
||||
() => GlResourceCommand.DeleteTexture(
|
||||
_gl,
|
||||
ownedWhiteTexture,
|
||||
$"delete TextRenderer white texture {ownedWhiteTexture}"));
|
||||
GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"initialize TextRenderer white texture",
|
||||
() =>
|
||||
{
|
||||
_gl.BindTexture(TextureTarget.Texture2D, whiteTexture);
|
||||
Span<byte> whitePixel = stackalloc byte[] { 255, 255, 255, 255 };
|
||||
fixed (byte* wp = whitePixel)
|
||||
_gl.TexImage2D(TextureTarget.Texture2D, 0, (int)InternalFormat.Rgba8, 1, 1, 0,
|
||||
PixelFormat.Rgba, PixelType.UnsignedByte, wp);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Nearest);
|
||||
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMinFilter.Nearest);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||
});
|
||||
}
|
||||
catch (Exception constructionFailure)
|
||||
{
|
||||
try
|
||||
{
|
||||
resources.RetryCleanup();
|
||||
}
|
||||
catch (Exception cleanupFailure)
|
||||
{
|
||||
throw new GlResourceConstructionException(
|
||||
"TextRenderer construction failed and its published GL resources did not cleanly roll back.",
|
||||
resources,
|
||||
[constructionFailure, cleanupFailure]);
|
||||
}
|
||||
|
||||
throw;
|
||||
}
|
||||
|
||||
_resources = resources;
|
||||
_shader = shader;
|
||||
_frameBuffers = frameBuffers;
|
||||
_whiteTex = whiteTexture;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
|
@ -120,43 +165,54 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_vboCapacityBytes = set.CapacityBytes;
|
||||
}
|
||||
|
||||
private FrameBufferSet CreateFrameBufferSet()
|
||||
private FrameBufferSet CreateFrameBufferSet(ResourceCleanupGroup resources)
|
||||
{
|
||||
uint vao = 0;
|
||||
uint vbo = 0;
|
||||
try
|
||||
{
|
||||
vao = TrackedGlResource.CreateVertexArray(
|
||||
uint vao = TrackedGlResource.CreateVertexArray(
|
||||
_gl,
|
||||
"TextRenderer frame VAO creation");
|
||||
RetryableGpuResourceRelease vaoRelease =
|
||||
TrackedGlResource.CreateRetryableVertexArrayDeletion(
|
||||
_gl,
|
||||
"TextRenderer frame VAO creation");
|
||||
vbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"TextRenderer frame VBO creation");
|
||||
var set = new FrameBufferSet { Vao = vao, Vbo = vbo };
|
||||
vao,
|
||||
"TextRenderer frame VAO disposal");
|
||||
resources.Add("frame VAO", vaoRelease.Run);
|
||||
var set = new FrameBufferSet { Vao = vao };
|
||||
|
||||
_gl.BindVertexArray(set.Vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, set.Vbo);
|
||||
uint stride = FloatsPerVertex * sizeof(float);
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||
_gl.EnableVertexAttribArray(1);
|
||||
_gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, (void*)(2 * sizeof(float)));
|
||||
_gl.EnableVertexAttribArray(2);
|
||||
_gl.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, stride, (void*)(4 * sizeof(float)));
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
|
||||
_gl.BindVertexArray(0);
|
||||
return set;
|
||||
}
|
||||
catch
|
||||
{
|
||||
if (vbo != 0)
|
||||
TrackedGlResource.DeleteBuffer(
|
||||
_gl, vbo, 0, "TextRenderer frame VBO rollback");
|
||||
if (vao != 0)
|
||||
TrackedGlResource.DeleteVertexArray(
|
||||
_gl, vao, "TextRenderer frame VAO rollback");
|
||||
throw;
|
||||
}
|
||||
uint vbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"TextRenderer frame VBO creation");
|
||||
set.Vbo = vbo;
|
||||
RetryableGpuResourceRelease? vboRelease = null;
|
||||
resources.Add(
|
||||
"frame VBO",
|
||||
() =>
|
||||
{
|
||||
vboRelease ??= TrackedGlResource.CreateRetryableBufferDeletion(
|
||||
_gl,
|
||||
vbo,
|
||||
set.CapacityBytes,
|
||||
"TextRenderer frame VBO disposal");
|
||||
vboRelease.Run();
|
||||
});
|
||||
|
||||
GlResourceCommand.Execute(
|
||||
_gl,
|
||||
"initialize TextRenderer frame VAO and VBO",
|
||||
() =>
|
||||
{
|
||||
_gl.BindVertexArray(set.Vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, set.Vbo);
|
||||
uint stride = FloatsPerVertex * sizeof(float);
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, stride, (void*)0);
|
||||
_gl.EnableVertexAttribArray(1);
|
||||
_gl.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, stride, (void*)(2 * sizeof(float)));
|
||||
_gl.EnableVertexAttribArray(2);
|
||||
_gl.VertexAttribPointer(2, 4, VertexAttribPointerType.Float, false, stride, (void*)(4 * sizeof(float)));
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, 0);
|
||||
_gl.BindVertexArray(0);
|
||||
});
|
||||
return set;
|
||||
}
|
||||
|
||||
/// <summary>Begin a HUD pass. Call once per frame before any Draw* calls.</summary>
|
||||
|
|
@ -471,19 +527,6 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.DeleteTexture(_whiteTex);
|
||||
foreach (FrameBufferSet set in _frameBuffers)
|
||||
{
|
||||
TrackedGlResource.DeleteBuffer(
|
||||
_gl,
|
||||
set.Vbo,
|
||||
set.CapacityBytes,
|
||||
"TextRenderer frame VBO disposal");
|
||||
TrackedGlResource.DeleteVertexArray(
|
||||
_gl,
|
||||
set.Vao,
|
||||
"TextRenderer frame VAO disposal");
|
||||
}
|
||||
_shader.Dispose();
|
||||
_resources.RetryCleanup();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue