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>
125 lines
4.2 KiB
C#
125 lines
4.2 KiB
C#
using System;
|
|
using System.Runtime.InteropServices;
|
|
using AcDream.App.Rendering.Wb;
|
|
using AcDream.Core.Lighting;
|
|
using Silk.NET.OpenGL;
|
|
|
|
namespace AcDream.App.Rendering;
|
|
|
|
/// <summary>
|
|
/// GL wrapper that owns the SceneLighting UBO buffer, updates its
|
|
/// contents each frame, and keeps it bound at binding=1 so every
|
|
/// shader sampling <c>uLights[]</c> / <c>uFogColor</c> / etc reads
|
|
/// consistent data without per-shader re-upload.
|
|
///
|
|
/// <para>
|
|
/// Usage (r12 §13.2 + r13 §12.3):
|
|
/// <list type="number">
|
|
/// <item><description>Instantiate once at startup, after the GL context exists.</description></item>
|
|
/// <item><description>Each frame, after <see cref="LightManager.Tick"/>, call <see cref="Upload"/> with a freshly-built <see cref="SceneLightingUbo"/>.</description></item>
|
|
/// <item><description>Shader programs that declare <c>layout(std140, binding = 1) uniform SceneLighting { ... }</c> automatically pick up the data.</description></item>
|
|
/// </list>
|
|
/// </para>
|
|
/// </summary>
|
|
public sealed unsafe class SceneLightingUboBinding : IDisposable
|
|
{
|
|
private readonly GL _gl;
|
|
private uint _ubo;
|
|
private readonly List<uint>[] _buffersByFrame = [[], [], []];
|
|
private int _frameSlot;
|
|
private int _bufferCursor;
|
|
private bool _frameStarted;
|
|
private bool _disposed;
|
|
|
|
internal int DynamicBufferCount => _buffersByFrame.Sum(buffers => buffers.Count);
|
|
|
|
public SceneLightingUboBinding(GL gl)
|
|
{
|
|
_gl = gl ?? throw new ArgumentNullException(nameof(gl));
|
|
}
|
|
|
|
/// <summary>
|
|
/// Resets the lighting-submission cursor for a GPU-fenced frame slot.
|
|
/// World, portal-space, and paperdoll draws can each upload different
|
|
/// lighting in one frame, so every upload receives distinct storage.
|
|
/// </summary>
|
|
public void BeginFrame(int frameSlot)
|
|
{
|
|
if ((uint)frameSlot >= (uint)_buffersByFrame.Length)
|
|
throw new ArgumentOutOfRangeException(nameof(frameSlot));
|
|
|
|
_frameSlot = frameSlot;
|
|
_bufferCursor = 0;
|
|
_frameStarted = true;
|
|
}
|
|
|
|
private void ActivateNextBuffer()
|
|
{
|
|
if (!_frameStarted)
|
|
throw new InvalidOperationException("BeginFrame must be called before uploading scene lighting.");
|
|
|
|
List<uint> buffers = _buffersByFrame[_frameSlot];
|
|
if (_bufferCursor == buffers.Count)
|
|
{
|
|
uint buffer = TrackedGlResource.CreateBuffer(
|
|
_gl,
|
|
"SceneLighting frame UBO creation");
|
|
try
|
|
{
|
|
TrackedGlResource.AllocateBufferStorage(
|
|
_gl,
|
|
GLEnum.UniformBuffer,
|
|
buffer,
|
|
0,
|
|
SceneLightingUbo.SizeInBytes,
|
|
GLEnum.DynamicDraw,
|
|
"SceneLighting frame UBO allocation");
|
|
buffers.Add(buffer);
|
|
}
|
|
catch
|
|
{
|
|
TrackedGlResource.DeleteBuffer(
|
|
_gl,
|
|
buffer,
|
|
0,
|
|
"SceneLighting frame UBO rollback");
|
|
throw;
|
|
}
|
|
}
|
|
|
|
_ubo = buffers[_bufferCursor++];
|
|
}
|
|
|
|
/// <summary>
|
|
/// Push the current frame's UBO contents to the GPU. Cheap (576 bytes)
|
|
/// so fine to call unconditionally every frame.
|
|
/// </summary>
|
|
public void Upload(SceneLightingUbo data)
|
|
{
|
|
ActivateNextBuffer();
|
|
_gl.BindBuffer(BufferTargetARB.UniformBuffer, _ubo);
|
|
_gl.BufferSubData(BufferTargetARB.UniformBuffer,
|
|
(nint)0, (nuint)SceneLightingUbo.SizeInBytes, &data);
|
|
_gl.BindBufferBase(BufferTargetARB.UniformBuffer,
|
|
SceneLightingUbo.BindingPoint, _ubo);
|
|
_gl.BindBuffer(BufferTargetARB.UniformBuffer, 0);
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_disposed) return;
|
|
foreach (List<uint> buffers in _buffersByFrame)
|
|
{
|
|
foreach (uint buffer in buffers)
|
|
{
|
|
TrackedGlResource.DeleteBuffer(
|
|
_gl,
|
|
buffer,
|
|
SceneLightingUbo.SizeInBytes,
|
|
"SceneLighting frame UBO disposal");
|
|
}
|
|
buffers.Clear();
|
|
}
|
|
_disposed = true;
|
|
}
|
|
}
|