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

@ -1,6 +1,7 @@
using Chorizite.Core.Render;
using Chorizite.Core.Render.Enums;
using Chorizite.Core.Render.Vertex;
using AcDream.App.Rendering;
using Microsoft.Extensions.Logging;
using Silk.NET.OpenGL;
// IUniformBuffer is in Chorizite.Core.dll but under the Chorizite.OpenGLSDLBackend namespace
@ -149,38 +150,49 @@ namespace AcDream.App.Rendering.Wb {
HasBindless = false;
}
GL.GenBuffers(1, out uint instanceVbo);
InstanceVBO = instanceVbo;
var resources = new ResourceCleanupGroup();
try {
InstanceVBO = CreateConstructionBuffer(resources, "WB instance buffer");
// Query this immutable device limit once. Atlas construction can
// happen hundreds of times during portal streaming; repeating a
// driver GetFloat for every texture serialized the upload burst.
if (renderSettings.EnableAnisotropicFiltering) {
GL.GetFloat(GLEnum.MaxTextureMaxAnisotropy, out float maxAniso);
MaxSupportedAnisotropy = Math.Max(0f, maxAniso);
// Query this immutable device limit once. Atlas construction can
// happen hundreds of times during portal streaming; repeating a
// driver GetFloat for every texture serialized the upload burst.
if (renderSettings.EnableAnisotropicFiltering) {
MaxSupportedAnisotropy = GlResourceCommand.Execute(
GL,
"query maximum texture anisotropy",
() => {
GL.GetFloat(GLEnum.MaxTextureMaxAnisotropy, out float maxAniso);
return Math.Max(0f, maxAniso);
});
}
WrapSampler = CreateConstructionSampler(
resources,
TextureWrapMode.Repeat,
"WB repeat sampler");
ClampSampler = CreateConstructionSampler(
resources,
TextureWrapMode.ClampToEdge,
"WB clamp sampler");
_sceneDataBuffer = new ManagedGLUniformBuffer(
this,
BufferUsage.Dynamic,
Marshal.SizeOf<SceneData>());
ManagedGLUniformBuffer ownedSceneDataBuffer = _sceneDataBuffer;
resources.Add(
"WB scene-data uniform buffer",
ownedSceneDataBuffer.DisposeImmediately);
InitializeSharedDebugResources(resources);
resources.TransferAll();
} catch (Exception constructionFailure) {
resources.RollbackConstructionAndThrow(
"OpenGLGraphicsDevice construction failed and its GL prefix did not cleanly roll back.",
constructionFailure);
}
// Create sampler objects for wrap vs clamp
WrapSampler = GL.GenSampler();
GL.SamplerParameter(WrapSampler, SamplerParameterI.WrapS, (int)TextureWrapMode.Repeat);
GL.SamplerParameter(WrapSampler, SamplerParameterI.WrapT, (int)TextureWrapMode.Repeat);
GL.SamplerParameter(WrapSampler, SamplerParameterI.MinFilter, (int)TextureMinFilter.LinearMipmapLinear);
GL.SamplerParameter(WrapSampler, SamplerParameterI.MagFilter, (int)TextureMagFilter.Linear);
if (MaxSupportedAnisotropy > 0)
GL.SamplerParameter(WrapSampler, GLEnum.TextureMaxAnisotropy, MaxSupportedAnisotropy);
ClampSampler = GL.GenSampler();
GL.SamplerParameter(ClampSampler, SamplerParameterI.WrapS, (int)TextureWrapMode.ClampToEdge);
GL.SamplerParameter(ClampSampler, SamplerParameterI.WrapT, (int)TextureWrapMode.ClampToEdge);
GL.SamplerParameter(ClampSampler, SamplerParameterI.MinFilter, (int)TextureMinFilter.LinearMipmapLinear);
GL.SamplerParameter(ClampSampler, SamplerParameterI.MagFilter, (int)TextureMagFilter.Linear);
if (MaxSupportedAnisotropy > 0)
GL.SamplerParameter(ClampSampler, GLEnum.TextureMaxAnisotropy, MaxSupportedAnisotropy);
_sceneDataBuffer = new ManagedGLUniformBuffer(this, BufferUsage.Dynamic, Marshal.SizeOf<SceneData>());
InitializeSharedDebugResources();
// ParticleBatcher is constructed post-ctor by WbMeshAdapter (WbMeshAdapter.cs:78)
// after the adapter has wired up all dependencies. The null! here is overridden
// immediately after construction; it is not observable as null at runtime.
@ -196,7 +208,49 @@ namespace AcDream.App.Rendering.Wb {
internal AcDream.App.Rendering.IGpuResourceRetirementQueue ResourceRetirement =>
_resourceRetirement;
private void InitializeSharedDebugResources() {
private uint CreateConstructionBuffer(ResourceCleanupGroup resources, string name) {
uint buffer = GlResourceCommand.CreateName(GL, name, GL.GenBuffer, GL.DeleteBuffer);
resources.Add(
name,
() => GlResourceCommand.DeleteBuffer(
GL,
buffer,
$"delete {name} {buffer}"));
return buffer;
}
private uint CreateConstructionSampler(
ResourceCleanupGroup resources,
TextureWrapMode wrapMode,
string name) {
uint sampler = GlResourceCommand.CreateName(GL, name, GL.GenSampler, GL.DeleteSampler);
resources.Add(
name,
() => GlResourceCommand.Execute(
GL,
$"delete {name} {sampler}",
() => GL.DeleteSampler(sampler)));
GlResourceCommand.Execute(GL, $"configure {name}", () => {
GL.SamplerParameter(sampler, SamplerParameterI.WrapS, (int)wrapMode);
GL.SamplerParameter(sampler, SamplerParameterI.WrapT, (int)wrapMode);
GL.SamplerParameter(
sampler,
SamplerParameterI.MinFilter,
(int)TextureMinFilter.LinearMipmapLinear);
GL.SamplerParameter(
sampler,
SamplerParameterI.MagFilter,
(int)TextureMagFilter.Linear);
if (MaxSupportedAnisotropy > 0)
GL.SamplerParameter(
sampler,
GLEnum.TextureMaxAnisotropy,
MaxSupportedAnisotropy);
});
return sampler;
}
private void InitializeSharedDebugResources(ResourceCleanupGroup resources) {
// Unit quad vertices for two triangles (0 to 1 for length, -0.5 to 0.5 for thickness)
float[] quadVertices = {
0.0f, -0.5f,
@ -207,53 +261,74 @@ namespace AcDream.App.Rendering.Wb {
0.0f, 0.5f
};
GL.GenBuffers(1, out uint quadVbo);
SharedQuadVBO = quadVbo;
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
fixed (float* pQuad = quadVertices) {
GL.BufferData(GLEnum.ArrayBuffer, (nuint)(quadVertices.Length * sizeof(float)), pQuad, GLEnum.StaticDraw);
}
SharedQuadVBO = CreateConstructionBuffer(resources, "WB shared debug quad buffer");
SharedDebugInstanceVBO = CreateConstructionBuffer(
resources,
"WB shared debug instance buffer");
SharedDebugVAO = GlResourceCommand.CreateName(
GL,
"WB shared debug vertex array",
GL.GenVertexArray,
GL.DeleteVertexArray);
uint ownedDebugVao = SharedDebugVAO;
resources.Add(
"WB shared debug vertex array",
() => GlResourceCommand.DeleteVertexArray(
GL,
ownedDebugVao,
$"delete WB shared debug vertex array {ownedDebugVao}"));
GL.GenBuffers(1, out uint debugInstanceVbo);
SharedDebugInstanceVBO = debugInstanceVbo;
// Initial capacity for debug instances
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
GL.BufferData(GLEnum.ArrayBuffer, (nuint)(1024 * 44), (void*)0, GLEnum.StreamDraw); // 44 bytes is sizeof(LineInstance)
GlResourceCommand.Execute(GL, "configure WB shared debug resources", () => {
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
fixed (float* pQuad = quadVertices) {
GL.BufferData(
GLEnum.ArrayBuffer,
(nuint)(quadVertices.Length * sizeof(float)),
pQuad,
GLEnum.StaticDraw);
}
GL.GenVertexArrays(1, out uint debugVao);
SharedDebugVAO = debugVao;
GL.BindVertexArray(SharedDebugVAO);
// Initial capacity for debug instances.
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
GL.BufferData(
GLEnum.ArrayBuffer,
(nuint)(1024 * 44),
(void*)0,
GLEnum.StreamDraw); // 44 bytes is sizeof(LineInstance)
// Quad Pos attribute (location 0)
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 2, GLEnum.Float, false, 2 * sizeof(float), (void*)0);
GL.BindVertexArray(SharedDebugVAO);
// Instance attributes
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
uint lineInstanceSize = 44; // Marshal.SizeOf<LineInstance>() - we'll hardcode or use a constant later
// Quad Pos attribute (location 0)
GL.BindBuffer(GLEnum.ArrayBuffer, SharedQuadVBO);
GL.EnableVertexAttribArray(0);
GL.VertexAttribPointer(0, 2, GLEnum.Float, false, 2 * sizeof(float), (void*)0);
// aStart (location 1)
GL.EnableVertexAttribArray(1);
GL.VertexAttribPointer(1, 3, GLEnum.Float, false, lineInstanceSize, (void*)0);
GL.VertexAttribDivisor(1, 1);
// Instance attributes
GL.BindBuffer(GLEnum.ArrayBuffer, SharedDebugInstanceVBO);
uint lineInstanceSize = 44;
// aEnd (location 2)
GL.EnableVertexAttribArray(2);
GL.VertexAttribPointer(2, 3, GLEnum.Float, false, lineInstanceSize, (void*)12); // OffsetOf End
GL.VertexAttribDivisor(2, 1);
// aStart (location 1)
GL.EnableVertexAttribArray(1);
GL.VertexAttribPointer(1, 3, GLEnum.Float, false, lineInstanceSize, (void*)0);
GL.VertexAttribDivisor(1, 1);
// aColor (location 3)
GL.EnableVertexAttribArray(3);
GL.VertexAttribPointer(3, 4, GLEnum.Float, false, lineInstanceSize, (void*)24); // OffsetOf Color
GL.VertexAttribDivisor(3, 1);
// aEnd (location 2)
GL.EnableVertexAttribArray(2);
GL.VertexAttribPointer(2, 3, GLEnum.Float, false, lineInstanceSize, (void*)12);
GL.VertexAttribDivisor(2, 1);
// aThickness (location 4)
GL.EnableVertexAttribArray(4);
GL.VertexAttribPointer(4, 1, GLEnum.Float, false, lineInstanceSize, (void*)40); // OffsetOf Thickness
GL.VertexAttribDivisor(4, 1);
// aColor (location 3)
GL.EnableVertexAttribArray(3);
GL.VertexAttribPointer(3, 4, GLEnum.Float, false, lineInstanceSize, (void*)24);
GL.VertexAttribDivisor(3, 1);
GL.BindVertexArray(0);
// aThickness (location 4)
GL.EnableVertexAttribArray(4);
GL.VertexAttribPointer(4, 1, GLEnum.Float, false, lineInstanceSize, (void*)40);
GL.VertexAttribDivisor(4, 1);
GL.BindVertexArray(0);
});
}
public void EnsureInstanceBufferCapacity(int count, int stride, bool forceOrphan = false) {