using System; using System.Runtime.InteropServices; using AcDream.App.Rendering.Wb; using AcDream.Core.Lighting; using Silk.NET.OpenGL; namespace AcDream.App.Rendering; /// /// GL wrapper that owns the SceneLighting UBO buffer, updates its /// contents each frame, and keeps it bound at binding=1 so every /// shader sampling uLights[] / uFogColor / etc reads /// consistent data without per-shader re-upload. /// /// /// Usage (r12 §13.2 + r13 §12.3): /// /// Instantiate once at startup, after the GL context exists. /// Each frame, after , call with a freshly-built . /// Shader programs that declare layout(std140, binding = 1) uniform SceneLighting { ... } automatically pick up the data. /// /// /// public sealed unsafe class SceneLightingUboBinding : IDisposable { private readonly GL _gl; private uint _ubo; private readonly List[] _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)); } /// /// 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. /// 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 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++]; } /// /// Push the current frame's UBO contents to the GPU. Cheap (576 bytes) /// so fine to call unconditionally every frame. /// 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 buffers in _buffersByFrame) { foreach (uint buffer in buffers) { TrackedGlResource.DeleteBuffer( _gl, buffer, SceneLightingUbo.SizeInBytes, "SceneLighting frame UBO disposal"); } buffers.Clear(); } _disposed = true; } }