refactor(app): compose world rendering startup

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>
This commit is contained in:
Erik 2026-07-22 16:46:05 +02:00
parent cd7b519f78
commit 6a2fe98cc4
22 changed files with 1983 additions and 634 deletions

View file

@ -38,6 +38,7 @@ public sealed unsafe class BitmapFont : IDisposable
private readonly Glyph[] _glyphs;
private readonly int _firstChar;
private readonly int _numChars;
private readonly ResourceCleanupGroup _resources;
public uint TextureId { get; }
public float PixelHeight { get; }
@ -95,27 +96,65 @@ public sealed unsafe class BitmapFont : IDisposable
adv: bc.xadvance);
}
// Upload atlas as a single-channel GL texture (R8).
TextureId = _gl.GenTexture();
_gl.BindTexture(TextureTarget.Texture2D, TextureId);
_gl.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
fixed (byte* ptr = pixels)
// Upload atlas as a single-channel GL texture (R8). Publish the GL
// name into the construction ledger before any later upload/state
// command can fail.
var resources = new ResourceCleanupGroup();
uint texture = 0;
try
{
_gl.TexImage2D(TextureTarget.Texture2D, 0,
(int)InternalFormat.R8,
(uint)AtlasWidth, (uint)AtlasHeight, 0,
PixelFormat.Red, PixelType.UnsignedByte, ptr);
texture = GlResourceCommand.CreateTexture(_gl, "BitmapFont atlas");
uint ownedTexture = texture;
resources.Add(
"bitmap-font atlas",
() => GlResourceCommand.DeleteTexture(
_gl,
ownedTexture,
$"delete BitmapFont atlas {ownedTexture}"));
_gl.GetInteger(GetPName.TextureBinding2D, out int previousTexture);
_gl.GetInteger(GetPName.UnpackAlignment, out int previousAlignment);
GlResourceCommand.Execute(_gl, "initialize BitmapFont atlas", () =>
{
try
{
_gl.BindTexture(TextureTarget.Texture2D, texture);
_gl.PixelStore(PixelStoreParameter.UnpackAlignment, 1);
fixed (byte* ptr = pixels)
{
_gl.TexImage2D(TextureTarget.Texture2D, 0,
(int)InternalFormat.R8,
(uint)AtlasWidth, (uint)AtlasHeight, 0,
PixelFormat.Red, PixelType.UnsignedByte, ptr);
}
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
(int)TextureMinFilter.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
(int)TextureMagFilter.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,
(int)TextureWrapMode.ClampToEdge);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,
(int)TextureWrapMode.ClampToEdge);
}
finally
{
_gl.PixelStore(
PixelStoreParameter.UnpackAlignment,
previousAlignment);
_gl.BindTexture(
TextureTarget.Texture2D,
unchecked((uint)previousTexture));
}
});
}
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter,
(int)TextureMinFilter.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter,
(int)TextureMagFilter.Linear);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS,
(int)TextureWrapMode.ClampToEdge);
_gl.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT,
(int)TextureWrapMode.ClampToEdge);
_gl.PixelStore(PixelStoreParameter.UnpackAlignment, 4); // restore default
_gl.BindTexture(TextureTarget.Texture2D, 0);
catch (Exception constructionFailure)
{
resources.RollbackConstructionAndThrow(
"BitmapFont construction failed and its GL atlas did not cleanly roll back.",
constructionFailure);
}
TextureId = texture;
_resources = resources;
}
public bool TryGetGlyph(char c, out Glyph g)
@ -144,7 +183,7 @@ public sealed unsafe class BitmapFont : IDisposable
public void Dispose()
{
_gl.DeleteTexture(TextureId);
_resources.RetryCleanup();
}
/// <summary>