using System; using System.Runtime.InteropServices; 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 readonly uint _ubo; private bool _disposed; public SceneLightingUboBinding(GL gl) { _gl = gl ?? throw new ArgumentNullException(nameof(gl)); _ubo = _gl.GenBuffer(); _gl.BindBuffer(BufferTargetARB.UniformBuffer, _ubo); // Pre-allocate with the final size; BufferSubData each frame. _gl.BufferData( BufferTargetARB.UniformBuffer, (nuint)SceneLightingUbo.SizeInBytes, (void*)0, BufferUsageARB.DynamicDraw); _gl.BindBuffer(BufferTargetARB.UniformBuffer, 0); // Bind the buffer to the chosen binding point exactly once — shaders // that declare this binding in their layout block will read from it // on every draw without further intervention. _gl.BindBufferBase(BufferTargetARB.UniformBuffer, SceneLightingUbo.BindingPoint, _ubo); } /// /// 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) { _gl.BindBuffer(BufferTargetARB.UniformBuffer, _ubo); _gl.BufferSubData(BufferTargetARB.UniformBuffer, (nint)0, (nuint)SceneLightingUbo.SizeInBytes, &data); _gl.BindBuffer(BufferTargetARB.UniformBuffer, 0); } public void Dispose() { if (_disposed) return; _gl.DeleteBuffer(_ubo); _disposed = true; } }