fix(streaming): retire stale portal-region entities
Port retail's 25-second leave-visibility lifetime over canonical live records, retaining spatially resident and owned entities while using the conservative ACE visibility envelope for nonresident records. Route expiry through the normal generation-safe F747 teardown so animations, effects, physics, and render owners unwind symmetrically. Replace the append-only modern mesh buffer with coalescing vertex/index ranges and upload each mesh's vertices once instead of once per material. Released zero-reference meshes can now reuse GPU ranges after portal and cache churn. A connected five-region round trip returned animation ownership to baseline, recreated the starting region on revisit, and held normal FPS. Release build succeeds and all 5,927 tests pass with five intentional skips. Co-authored-by: OpenAI Codex <codex@openai.com>
This commit is contained in:
parent
2cbf34a668
commit
3971997689
13 changed files with 918 additions and 117 deletions
|
|
@ -1,128 +1,252 @@
|
|||
using AcDream.Content;
|
||||
using Chorizite.Core.Render.Enums;
|
||||
using Silk.NET.OpenGL;
|
||||
using System;
|
||||
|
||||
namespace AcDream.App.Rendering.Wb {
|
||||
public class GlobalMeshBuffer : IDisposable {
|
||||
private readonly GL _gl;
|
||||
public uint VAO { get; private set; }
|
||||
public uint VBO { get; private set; }
|
||||
public uint IBO { get; private set; }
|
||||
namespace AcDream.App.Rendering.Wb;
|
||||
|
||||
private int _vboCapacity = 1024 * 1024; // 1M vertices (~32MB)
|
||||
private int _iboCapacity = 3 * 1024 * 1024; // 3M indices (~6MB)
|
||||
private int _vboOffset = 0;
|
||||
private int _iboOffset = 0;
|
||||
internal sealed record GlobalMeshAllocation(
|
||||
MeshBufferRange Vertices,
|
||||
MeshBufferRange Indices,
|
||||
IReadOnlyList<int> BatchFirstIndices);
|
||||
|
||||
public GlobalMeshBuffer(GL gl) {
|
||||
_gl = gl;
|
||||
InitBuffers();
|
||||
/// <summary>
|
||||
/// Shared modern-rendering vertex/index buffers with reclaimable ranges.
|
||||
/// ObjectMeshManager owns allocation lifetime and releases a mesh's ranges
|
||||
/// when its zero-reference LRU entry is evicted.
|
||||
/// </summary>
|
||||
public sealed class GlobalMeshBuffer : IDisposable
|
||||
{
|
||||
private readonly GL _gl;
|
||||
private readonly ContiguousRangeAllocator _vertices;
|
||||
private readonly ContiguousRangeAllocator _indices;
|
||||
|
||||
public uint VAO { get; private set; }
|
||||
public uint VBO { get; private set; }
|
||||
public uint IBO { get; private set; }
|
||||
|
||||
public GlobalMeshBuffer(GL gl)
|
||||
{
|
||||
_gl = gl;
|
||||
_vertices = new ContiguousRangeAllocator(1024 * 1024); // ~32 MB
|
||||
_indices = new ContiguousRangeAllocator(3 * 1024 * 1024); // ~6 MB
|
||||
InitBuffers();
|
||||
}
|
||||
|
||||
private unsafe void InitBuffers()
|
||||
{
|
||||
_gl.GenVertexArrays(1, out uint vao);
|
||||
VAO = vao;
|
||||
_gl.BindVertexArray(VAO);
|
||||
|
||||
_gl.GenBuffers(1, out uint vbo);
|
||||
VBO = vbo;
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
|
||||
_gl.BufferData(
|
||||
GLEnum.ArrayBuffer,
|
||||
(nuint)(_vertices.Capacity * VertexPositionNormalTexture.Size),
|
||||
null,
|
||||
GLEnum.StaticDraw);
|
||||
|
||||
int stride = VertexPositionNormalTexture.Size;
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
|
||||
_gl.EnableVertexAttribArray(1);
|
||||
_gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
|
||||
_gl.EnableVertexAttribArray(2);
|
||||
_gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
|
||||
|
||||
_gl.GenBuffers(1, out uint ibo);
|
||||
IBO = ibo;
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
|
||||
_gl.BufferData(
|
||||
GLEnum.ElementArrayBuffer,
|
||||
(nuint)(_indices.Capacity * sizeof(ushort)),
|
||||
null,
|
||||
GLEnum.StaticDraw);
|
||||
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
internal unsafe GlobalMeshAllocation UploadMesh(
|
||||
VertexPositionNormalTexture[] vertices,
|
||||
IReadOnlyList<ushort[]> indexBatches)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(vertices);
|
||||
ArgumentNullException.ThrowIfNull(indexBatches);
|
||||
if (vertices.Length == 0)
|
||||
throw new ArgumentException("A global mesh allocation requires vertices.", nameof(vertices));
|
||||
|
||||
int totalIndices = 0;
|
||||
for (int i = 0; i < indexBatches.Count; i++)
|
||||
{
|
||||
ushort[] batch = indexBatches[i]
|
||||
?? throw new ArgumentException("Index batches cannot contain null.", nameof(indexBatches));
|
||||
totalIndices = checked(totalIndices + batch.Length);
|
||||
}
|
||||
if (totalIndices == 0)
|
||||
throw new ArgumentException("A global mesh allocation requires indices.", nameof(indexBatches));
|
||||
|
||||
MeshBufferRange vertexRange = AllocateVertices(vertices.Length);
|
||||
MeshBufferRange indexRange;
|
||||
try
|
||||
{
|
||||
indexRange = AllocateIndices(totalIndices);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_vertices.Release(vertexRange);
|
||||
throw;
|
||||
}
|
||||
|
||||
private unsafe void InitBuffers() {
|
||||
_gl.GenVertexArrays(1, out uint vao);
|
||||
VAO = vao;
|
||||
_gl.BindVertexArray(VAO);
|
||||
|
||||
_gl.GenBuffers(1, out uint vbo);
|
||||
VBO = vbo;
|
||||
var firstIndices = new int[indexBatches.Count];
|
||||
try
|
||||
{
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
|
||||
_gl.BufferData(GLEnum.ArrayBuffer, (nuint)(_vboCapacity * VertexPositionNormalTexture.Size), null, GLEnum.StaticDraw);
|
||||
|
||||
int stride = VertexPositionNormalTexture.Size;
|
||||
_gl.EnableVertexAttribArray(0);
|
||||
_gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
|
||||
_gl.EnableVertexAttribArray(1);
|
||||
_gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
|
||||
_gl.EnableVertexAttribArray(2);
|
||||
_gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
|
||||
|
||||
_gl.GenBuffers(1, out uint ibo);
|
||||
IBO = ibo;
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
|
||||
_gl.BufferData(GLEnum.ElementArrayBuffer, (nuint)(_iboCapacity * sizeof(ushort)), null, GLEnum.StaticDraw);
|
||||
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
public unsafe (int baseVertex, int firstIndex) Append(VertexPositionNormalTexture[] vertices, ushort[] indices) {
|
||||
if (vertices.Length == 0 || indices.Length == 0) return (0, 0);
|
||||
|
||||
// Check capacity
|
||||
if (_vboOffset + vertices.Length > _vboCapacity) {
|
||||
ResizeVBO(Math.Max(_vboCapacity * 2, _vboCapacity + vertices.Length));
|
||||
}
|
||||
if (_iboOffset + indices.Length > _iboCapacity) {
|
||||
ResizeIBO(Math.Max(_iboCapacity * 2, _iboCapacity + indices.Length));
|
||||
}
|
||||
|
||||
int baseVertex = _vboOffset;
|
||||
int firstIndex = _iboOffset;
|
||||
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
|
||||
fixed (VertexPositionNormalTexture* ptr = vertices) {
|
||||
_gl.BufferSubData(GLEnum.ArrayBuffer, (nint)(baseVertex * VertexPositionNormalTexture.Size), (nuint)(vertices.Length * VertexPositionNormalTexture.Size), ptr);
|
||||
fixed (VertexPositionNormalTexture* ptr = vertices)
|
||||
{
|
||||
_gl.BufferSubData(
|
||||
GLEnum.ArrayBuffer,
|
||||
(nint)(vertexRange.Offset * VertexPositionNormalTexture.Size),
|
||||
(nuint)(vertices.Length * VertexPositionNormalTexture.Size),
|
||||
ptr);
|
||||
}
|
||||
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
|
||||
fixed (ushort* ptr = indices) {
|
||||
_gl.BufferSubData(GLEnum.ElementArrayBuffer, (nint)(firstIndex * sizeof(ushort)), (nuint)(indices.Length * sizeof(ushort)), ptr);
|
||||
int indexOffset = indexRange.Offset;
|
||||
for (int i = 0; i < indexBatches.Count; i++)
|
||||
{
|
||||
ushort[] batch = indexBatches[i];
|
||||
firstIndices[i] = indexOffset;
|
||||
if (batch.Length > 0)
|
||||
{
|
||||
fixed (ushort* ptr = batch)
|
||||
{
|
||||
_gl.BufferSubData(
|
||||
GLEnum.ElementArrayBuffer,
|
||||
(nint)(indexOffset * sizeof(ushort)),
|
||||
(nuint)(batch.Length * sizeof(ushort)),
|
||||
ptr);
|
||||
}
|
||||
indexOffset += batch.Length;
|
||||
}
|
||||
}
|
||||
|
||||
_vboOffset += vertices.Length;
|
||||
_iboOffset += indices.Length;
|
||||
|
||||
return (baseVertex, firstIndex);
|
||||
}
|
||||
catch
|
||||
{
|
||||
_indices.Release(indexRange);
|
||||
_vertices.Release(vertexRange);
|
||||
throw;
|
||||
}
|
||||
|
||||
private unsafe void ResizeVBO(int newCapacity) {
|
||||
_gl.GenBuffers(1, out uint newVbo);
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, newVbo);
|
||||
_gl.BufferData(GLEnum.ArrayBuffer, (nuint)(newCapacity * VertexPositionNormalTexture.Size), null, GLEnum.StaticDraw);
|
||||
return new GlobalMeshAllocation(vertexRange, indexRange, firstIndices);
|
||||
}
|
||||
|
||||
_gl.BindBuffer(GLEnum.CopyReadBuffer, VBO);
|
||||
_gl.BindBuffer(GLEnum.CopyWriteBuffer, newVbo);
|
||||
_gl.CopyBufferSubData(GLEnum.CopyReadBuffer, GLEnum.CopyWriteBuffer, 0, 0, (nuint)(_vboOffset * VertexPositionNormalTexture.Size));
|
||||
internal void Release(GlobalMeshAllocation allocation)
|
||||
{
|
||||
ArgumentNullException.ThrowIfNull(allocation);
|
||||
_indices.Release(allocation.Indices);
|
||||
_vertices.Release(allocation.Vertices);
|
||||
}
|
||||
|
||||
private MeshBufferRange AllocateVertices(int count)
|
||||
{
|
||||
if (_vertices.TryAllocate(count, out MeshBufferRange allocation))
|
||||
return allocation;
|
||||
|
||||
int newCapacity = GrowCapacity(_vertices.Capacity, count);
|
||||
ResizeVBO(newCapacity);
|
||||
_vertices.Grow(newCapacity);
|
||||
if (!_vertices.TryAllocate(count, out allocation))
|
||||
throw new InvalidOperationException("Failed to allocate a vertex range after growing the buffer.");
|
||||
return allocation;
|
||||
}
|
||||
|
||||
private MeshBufferRange AllocateIndices(int count)
|
||||
{
|
||||
if (_indices.TryAllocate(count, out MeshBufferRange allocation))
|
||||
return allocation;
|
||||
|
||||
int newCapacity = GrowCapacity(_indices.Capacity, count);
|
||||
ResizeIBO(newCapacity);
|
||||
_indices.Grow(newCapacity);
|
||||
if (!_indices.TryAllocate(count, out allocation))
|
||||
throw new InvalidOperationException("Failed to allocate an index range after growing the buffer.");
|
||||
return allocation;
|
||||
}
|
||||
|
||||
private static int GrowCapacity(int capacity, int requiredContiguousLength)
|
||||
{
|
||||
int minimum = checked(capacity + requiredContiguousLength);
|
||||
int doubled = capacity <= int.MaxValue / 2 ? capacity * 2 : int.MaxValue;
|
||||
return Math.Max(doubled, minimum);
|
||||
}
|
||||
|
||||
private unsafe void ResizeVBO(int newCapacity)
|
||||
{
|
||||
_gl.GenBuffers(1, out uint newVbo);
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, newVbo);
|
||||
_gl.BufferData(
|
||||
GLEnum.ArrayBuffer,
|
||||
(nuint)(newCapacity * VertexPositionNormalTexture.Size),
|
||||
null,
|
||||
GLEnum.StaticDraw);
|
||||
|
||||
_gl.BindBuffer(GLEnum.CopyReadBuffer, VBO);
|
||||
_gl.BindBuffer(GLEnum.CopyWriteBuffer, newVbo);
|
||||
_gl.CopyBufferSubData(
|
||||
GLEnum.CopyReadBuffer,
|
||||
GLEnum.CopyWriteBuffer,
|
||||
0,
|
||||
0,
|
||||
(nuint)(_vertices.HighWaterMark * VertexPositionNormalTexture.Size));
|
||||
|
||||
_gl.DeleteBuffer(VBO);
|
||||
VBO = newVbo;
|
||||
|
||||
_gl.BindVertexArray(VAO);
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
|
||||
int stride = VertexPositionNormalTexture.Size;
|
||||
_gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
|
||||
_gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
|
||||
_gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
private unsafe void ResizeIBO(int newCapacity)
|
||||
{
|
||||
_gl.GenBuffers(1, out uint newIbo);
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, newIbo);
|
||||
_gl.BufferData(
|
||||
GLEnum.ElementArrayBuffer,
|
||||
(nuint)(newCapacity * sizeof(ushort)),
|
||||
null,
|
||||
GLEnum.StaticDraw);
|
||||
|
||||
_gl.BindBuffer(GLEnum.CopyReadBuffer, IBO);
|
||||
_gl.BindBuffer(GLEnum.CopyWriteBuffer, newIbo);
|
||||
_gl.CopyBufferSubData(
|
||||
GLEnum.CopyReadBuffer,
|
||||
GLEnum.CopyWriteBuffer,
|
||||
0,
|
||||
0,
|
||||
(nuint)(_indices.HighWaterMark * sizeof(ushort)));
|
||||
|
||||
_gl.DeleteBuffer(IBO);
|
||||
IBO = newIbo;
|
||||
|
||||
_gl.BindVertexArray(VAO);
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (VAO != 0)
|
||||
_gl.DeleteVertexArray(VAO);
|
||||
if (VBO != 0)
|
||||
_gl.DeleteBuffer(VBO);
|
||||
VBO = newVbo;
|
||||
_vboCapacity = newCapacity;
|
||||
|
||||
// Re-bind to VAO
|
||||
_gl.BindVertexArray(VAO);
|
||||
_gl.BindBuffer(GLEnum.ArrayBuffer, VBO);
|
||||
int stride = VertexPositionNormalTexture.Size;
|
||||
_gl.VertexAttribPointer(0, 3, GLEnum.Float, false, (uint)stride, (void*)0);
|
||||
_gl.VertexAttribPointer(1, 3, GLEnum.Float, false, (uint)stride, (void*)(3 * sizeof(float)));
|
||||
_gl.VertexAttribPointer(2, 2, GLEnum.Float, false, (uint)stride, (void*)(6 * sizeof(float)));
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
private unsafe void ResizeIBO(int newCapacity) {
|
||||
_gl.GenBuffers(1, out uint newIbo);
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, newIbo);
|
||||
_gl.BufferData(GLEnum.ElementArrayBuffer, (nuint)(newCapacity * sizeof(ushort)), null, GLEnum.StaticDraw);
|
||||
|
||||
_gl.BindBuffer(GLEnum.CopyReadBuffer, IBO);
|
||||
_gl.BindBuffer(GLEnum.CopyWriteBuffer, newIbo);
|
||||
_gl.CopyBufferSubData(GLEnum.CopyReadBuffer, GLEnum.CopyWriteBuffer, 0, 0, (nuint)(_iboOffset * sizeof(ushort)));
|
||||
|
||||
if (IBO != 0)
|
||||
_gl.DeleteBuffer(IBO);
|
||||
IBO = newIbo;
|
||||
_iboCapacity = newCapacity;
|
||||
|
||||
// Re-bind to VAO
|
||||
_gl.BindVertexArray(VAO);
|
||||
_gl.BindBuffer(GLEnum.ElementArrayBuffer, IBO);
|
||||
_gl.BindVertexArray(0);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
if (VAO != 0) _gl.DeleteVertexArray(VAO);
|
||||
if (VBO != 0) _gl.DeleteBuffer(VBO);
|
||||
if (IBO != 0) _gl.DeleteBuffer(IBO);
|
||||
VAO = VBO = IBO = 0;
|
||||
}
|
||||
VAO = VBO = IBO = 0;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue