using Chorizite.Core.Render; using Chorizite.Core.Render.Enums; using Silk.NET.OpenGL; using System.Runtime.InteropServices; using BufferUsage = Chorizite.Core.Render.Enums.BufferUsage; // IUniformBuffer is in Chorizite.Core.dll but under the Chorizite.OpenGLSDLBackend namespace using IUniformBuffer = Chorizite.OpenGLSDLBackend.IUniformBuffer; namespace AcDream.App.Rendering.Wb { /// /// OpenGL uniform buffer /// public unsafe class ManagedGLUniformBuffer : IUniformBuffer { private uint bufferId; private readonly OpenGLGraphicsDevice _device; private GL GL => _device.GL; /// public int Size { get; private set; } /// public BufferUsage Usage { get; private set; } /// /// Initializes a new instance of the class. /// /// Graphics device /// Buffer usage /// The size of the buffer, in bytes public unsafe ManagedGLUniformBuffer(OpenGLGraphicsDevice device, BufferUsage usage, int size) { _device = device; Size = size; Usage = usage; // Generate the buffer bufferId = GL.GenBuffer(); if (bufferId == 0) { throw new Exception("Failed to generate uniform buffer."); } GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Buffer); GLHelpers.CheckErrors(GL); // Allocate the buffer with the specified size GL.BindBuffer(GLEnum.UniformBuffer, bufferId); GLHelpers.CheckErrors(GL); GL.BufferData( GLEnum.UniformBuffer, (uint)Size, (void*)0, // No initial data GLEnum.DynamicDraw); GLHelpers.CheckErrors(GL); GpuMemoryTracker.TrackAllocation(Size, GpuResourceType.Buffer); } /// public unsafe void SetData(T[] data) where T : unmanaged { SetData(data.AsSpan()); } /// public unsafe void SetData(Span data) where T : unmanaged { uint dataSize = (uint)data.Length * (uint)Marshal.SizeOf(); // Ensure the buffer size is sufficient if (dataSize > Size) { throw new ArgumentException($"Data size ({dataSize} bytes) exceeds buffer size ({Size} bytes)."); } GL.BindBuffer(GLEnum.UniformBuffer, bufferId); fixed (T* ptr = data) { GL.BufferSubData(GLEnum.UniformBuffer, 0, (nuint)dataSize, ptr); } } /// public unsafe void SetSubData(T[] data, int destinationOffsetBytes, int sourceOffsetElements = 0, int lengthElements = 0) where T : unmanaged { SetSubData(data.AsSpan(), destinationOffsetBytes, sourceOffsetElements, lengthElements); } /// public unsafe void SetSubData(Span data, int destinationOffsetBytes, int sourceOffsetElements = 0, int lengthElements = 0) where T : unmanaged { if (lengthElements <= 0) { lengthElements = data.Length - sourceOffsetElements; } uint dataSizeBytes = (uint)lengthElements * (uint)Marshal.SizeOf(); // Validate buffer bounds if (destinationOffsetBytes + dataSizeBytes > Size) { throw new ArgumentException($"Update would exceed buffer size. Buffer size: {Size}, Update range: {destinationOffsetBytes} to {destinationOffsetBytes + dataSizeBytes}"); } GL.BindBuffer(GLEnum.UniformBuffer, bufferId); fixed (T* ptr = data.Slice(sourceOffsetElements, lengthElements)) { GL.BufferSubData(GLEnum.UniformBuffer, (nint)destinationOffsetBytes, (nuint)dataSizeBytes, ptr); } } /// /// Sets a single piece of data in the buffer. /// public unsafe void SetData(ref T data) where T : unmanaged { fixed (T* pData = &data) { SetData(new Span(pData, 1)); } } /// /// Binds the buffer to the specified binding point. /// /// The binding point to bind to public void Bind(uint bindingPoint) { GL.BindBufferBase(GLEnum.UniformBuffer, bindingPoint, bufferId); GLHelpers.CheckErrors(GL); } /// public void Bind() { GL.BindBuffer(GLEnum.UniformBuffer, bufferId); GLHelpers.CheckErrors(GL); } /// public void Unbind() { GL.BindBuffer(GLEnum.UniformBuffer, 0); GLHelpers.CheckErrors(GL); } public void Dispose() { _device.QueueGLAction(GL => { if (bufferId != 0) { GL.DeleteBuffer(bufferId); GpuMemoryTracker.TrackResourceDeallocation(GpuResourceType.Buffer); GLHelpers.CheckErrors(GL); GpuMemoryTracker.TrackDeallocation(Size, GpuResourceType.Buffer); bufferId = 0; } }); } } }