fix(rendering): bound portal resource lifetime
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>
This commit is contained in:
parent
3971997689
commit
749e8ceeb1
225 changed files with 29107 additions and 3914 deletions
|
|
@ -1,8 +1,10 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Numerics;
|
||||
using System.Runtime.InteropServices;
|
||||
using AcDream.App.Rendering.Wb;
|
||||
using Silk.NET.OpenGL;
|
||||
|
||||
namespace AcDream.App.Rendering;
|
||||
|
|
@ -23,11 +25,25 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
|
||||
private readonly GL _gl;
|
||||
private readonly Shader _shader;
|
||||
private readonly uint _vao;
|
||||
private readonly uint _vbo;
|
||||
private uint _vao;
|
||||
private uint _vbo;
|
||||
private readonly uint _whiteTex; // 1×1 white, for solid fills routed through the sprite bucket
|
||||
private int _vboCapacityBytes;
|
||||
|
||||
private sealed class FrameBufferSet
|
||||
{
|
||||
public uint Vao;
|
||||
public uint Vbo;
|
||||
public int CapacityBytes;
|
||||
public int UsedBytes;
|
||||
}
|
||||
|
||||
private readonly FrameBufferSet[] _frameBuffers = new FrameBufferSet[3];
|
||||
private FrameBufferSet? _activeFrameBuffer;
|
||||
|
||||
internal long DynamicBufferCapacityBytes =>
|
||||
_frameBuffers.Sum(set => (long)set.CapacityBytes);
|
||||
|
||||
private readonly List<float> _textBuf = new(8192);
|
||||
private readonly List<float> _rectBuf = new(1024);
|
||||
// Submission-ordered sprite segments: consecutive DrawSprite calls with the
|
||||
|
|
@ -65,22 +81,8 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
Path.Combine(shaderDir, "ui_text.vert"),
|
||||
Path.Combine(shaderDir, "ui_text.frag"));
|
||||
|
||||
_vao = _gl.GenVertexArray();
|
||||
_vbo = _gl.GenBuffer();
|
||||
|
||||
_gl.BindVertexArray(_vao);
|
||||
_gl.BindBuffer(BufferTargetARB.ArrayBuffer, _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);
|
||||
for (int i = 0; i < _frameBuffers.Length; i++)
|
||||
_frameBuffers[i] = CreateFrameBufferSet();
|
||||
|
||||
// 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
|
||||
|
|
@ -97,6 +99,63 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_gl.BindTexture(TextureTarget.Texture2D, 0);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Selects the GPU-fenced frame slot and resets its append cursor. Every
|
||||
/// UI segment rendered during the frame receives a distinct byte range;
|
||||
/// later text or sprite batches cannot overwrite an earlier in-flight draw.
|
||||
/// </summary>
|
||||
public void BeginFrame(int frameSlot)
|
||||
{
|
||||
if ((uint)frameSlot >= (uint)_frameBuffers.Length)
|
||||
throw new ArgumentOutOfRangeException(nameof(frameSlot));
|
||||
|
||||
FrameBufferSet set = _frameBuffers[frameSlot];
|
||||
set.UsedBytes = 0;
|
||||
_activeFrameBuffer = set;
|
||||
_vao = set.Vao;
|
||||
_vbo = set.Vbo;
|
||||
_vboCapacityBytes = set.CapacityBytes;
|
||||
}
|
||||
|
||||
private FrameBufferSet CreateFrameBufferSet()
|
||||
{
|
||||
uint vao = 0;
|
||||
uint vbo = 0;
|
||||
try
|
||||
{
|
||||
vao = TrackedGlResource.CreateVertexArray(
|
||||
_gl,
|
||||
"TextRenderer frame VAO creation");
|
||||
vbo = TrackedGlResource.CreateBuffer(
|
||||
_gl,
|
||||
"TextRenderer frame VBO creation");
|
||||
var set = new FrameBufferSet { Vao = vao, Vbo = 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;
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Begin a HUD pass. Call once per frame before any Draw* calls.</summary>
|
||||
public void Begin(Vector2 screenSize)
|
||||
{
|
||||
|
|
@ -357,8 +416,8 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
var seg = spriteSegs[i];
|
||||
if (seg.Verts.Count == 0) continue;
|
||||
_gl.BindTexture(TextureTarget.Texture2D, seg.Texture);
|
||||
UploadBuffer(seg.Verts);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, 0, (uint)(seg.Verts.Count / FloatsPerVertex));
|
||||
int firstVertex = UploadBuffer(seg.Verts);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, firstVertex, (uint)(seg.Verts.Count / FloatsPerVertex));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -366,8 +425,8 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
if (rectVerts > 0)
|
||||
{
|
||||
_shader.SetInt("uUseTexture", 0);
|
||||
UploadBuffer(rectBuf);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, 0, (uint)rectVerts);
|
||||
int firstVertex = UploadBuffer(rectBuf);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, firstVertex, (uint)rectVerts);
|
||||
}
|
||||
|
||||
// 3. Textured debug-font text glyphs on top.
|
||||
|
|
@ -377,34 +436,59 @@ public sealed unsafe class TextRenderer : IDisposable
|
|||
_gl.ActiveTexture(TextureUnit.Texture0);
|
||||
_gl.BindTexture(TextureTarget.Texture2D, font.TextureId);
|
||||
_shader.SetInt("uTex", 0);
|
||||
UploadBuffer(textBuf);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, 0, (uint)textVerts);
|
||||
int firstVertex = UploadBuffer(textBuf);
|
||||
_gl.DrawArrays(PrimitiveType.Triangles, firstVertex, (uint)textVerts);
|
||||
}
|
||||
}
|
||||
|
||||
private void UploadBuffer(List<float> buf)
|
||||
private int UploadBuffer(List<float> buf)
|
||||
{
|
||||
int bytes = buf.Count * sizeof(float);
|
||||
if (bytes == 0) return;
|
||||
if (bytes == 0) return 0;
|
||||
FrameBufferSet set = _activeFrameBuffer
|
||||
?? throw new InvalidOperationException("BeginFrame must be called before rendering text.");
|
||||
int byteOffset = set.UsedBytes;
|
||||
int requiredBytes = checked(byteOffset + bytes);
|
||||
|
||||
if (bytes > _vboCapacityBytes)
|
||||
if (requiredBytes > _vboCapacityBytes)
|
||||
{
|
||||
fixed (float* p = CollectionsMarshal.AsSpan(buf))
|
||||
_gl.BufferData(BufferTargetARB.ArrayBuffer, (nuint)bytes, p, BufferUsageARB.DynamicDraw);
|
||||
_vboCapacityBytes = bytes;
|
||||
}
|
||||
else
|
||||
{
|
||||
fixed (float* p = CollectionsMarshal.AsSpan(buf))
|
||||
_gl.BufferSubData(BufferTargetARB.ArrayBuffer, 0, (nuint)bytes, p);
|
||||
int newCapacity = DynamicBufferCapacity.Grow(
|
||||
_vboCapacityBytes,
|
||||
requiredBytes);
|
||||
TrackedGlResource.AllocateBufferStorage(
|
||||
_gl,
|
||||
GLEnum.ArrayBuffer,
|
||||
_vbo,
|
||||
_vboCapacityBytes,
|
||||
newCapacity,
|
||||
GLEnum.DynamicDraw,
|
||||
"TextRenderer frame VBO growth");
|
||||
_vboCapacityBytes = newCapacity;
|
||||
}
|
||||
|
||||
fixed (float* p = CollectionsMarshal.AsSpan(buf))
|
||||
_gl.BufferSubData(BufferTargetARB.ArrayBuffer, (nint)byteOffset, (nuint)bytes, p);
|
||||
|
||||
set.UsedBytes = requiredBytes;
|
||||
set.CapacityBytes = _vboCapacityBytes;
|
||||
return byteOffset / (FloatsPerVertex * sizeof(float));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_gl.DeleteTexture(_whiteTex);
|
||||
_gl.DeleteBuffer(_vbo);
|
||||
_gl.DeleteVertexArray(_vao);
|
||||
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();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue