using Chorizite.Core.Render.Enums; using Chorizite.Core.Render.Vertex; using Microsoft.Extensions.Logging; using Silk.NET.OpenGL; using System.Buffers; using System.Runtime.InteropServices; using BufferUsage = Chorizite.Core.Render.Enums.BufferUsage; namespace AcDream.App.Rendering.Wb { /// /// OpenGL vertex buffer /// public unsafe class ManagedGLVertexBuffer : IVertexBuffer { private uint bufferId; private readonly OpenGLGraphicsDevice _device; private void* _mappedPtr; private GL GL => _device.GL; /// public int Size { get; private set; } /// public BufferUsage Usage { get; private set; } /// /// Initializes a new instance of the class. /// /// Buffer usage /// The size of the buffer, in bytes public unsafe ManagedGLVertexBuffer(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 vertex buffer."); } GpuMemoryTracker.TrackResourceAllocation(GpuResourceType.Buffer); GLHelpers.CheckErrors(GL); // Allocate the buffer with the specified size but no initial data GL.BindBuffer(GLEnum.ArrayBuffer, bufferId); GLHelpers.CheckErrors(GL); if (_device.HasBufferStorage) { var flags = BufferStorageMask.MapWriteBit | BufferStorageMask.MapPersistentBit | BufferStorageMask.MapCoherentBit | BufferStorageMask.DynamicStorageBit; GL.BufferStorage(GLEnum.ArrayBuffer, (uint)Size, (void*)0, flags); _mappedPtr = GL.MapBufferRange(GLEnum.ArrayBuffer, 0, (nuint)Size, MapBufferAccessMask.WriteBit | MapBufferAccessMask.PersistentBit | MapBufferAccessMask.CoherentBit); } else { GL.BufferData( GLEnum.ArrayBuffer, (uint)Size, (void*)0, // No initial data Usage.ToGL()); } GLHelpers.CheckErrors(GL); GpuMemoryTracker.TrackAllocation(Size, GpuResourceType.Buffer); } /// public unsafe void SetData(T[] data) where T : IVertex { SetData(data.AsSpan()); } /// public unsafe void SetData(Span data) where T : IVertex { 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)."); } if (_mappedPtr != null) { Span mappedSpan = new Span(_mappedPtr, data.Length); data.CopyTo(mappedSpan); } else { GL.BindBuffer(GLEnum.ArrayBuffer, bufferId); GLHelpers.CheckErrors(GL); // Map the buffer for writing void* mappedPtr = GL.MapBufferRange( GLEnum.ArrayBuffer, 0, // offset dataSize, MapBufferAccessMask.WriteBit | MapBufferAccessMask.InvalidateBufferBit // Overwrite entire buffer ); if (mappedPtr == null) { throw new Exception("Failed to map buffer for writing."); } try { // Copy data directly to mapped memory Span mappedSpan = new Span(mappedPtr, data.Length); data.CopyTo(mappedSpan); } finally { // Unmap the buffer GL.UnmapBuffer(GLEnum.ArrayBuffer); GLHelpers.CheckErrors(GL); } } } public unsafe void SetSubData(T[] data, int destinationOffsetBytes, int sourceOffsetElements = 0, int lengthElements = 0) where T : IVertex { SetSubData(data.AsSpan(), destinationOffsetBytes, sourceOffsetElements, lengthElements); } /// public unsafe void SetSubData(Span data, int destinationOffsetBytes, int sourceOffsetElements = 0, int lengthElements = 0) where T : IVertex { if (Usage != BufferUsage.Dynamic) { throw new InvalidOperationException("Cannot update a buffer that is not dynamic."); } 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}"); } if (_mappedPtr != null) { Span mappedSpan = new Span((byte*)_mappedPtr + destinationOffsetBytes, lengthElements); data.Slice(sourceOffsetElements, lengthElements).CopyTo(mappedSpan); } else { GL.BindBuffer(GLEnum.ArrayBuffer, bufferId); GLHelpers.CheckErrors(GL); // Map the specific range of the buffer void* mappedPtr = GL.MapBufferRange( GLEnum.ArrayBuffer, destinationOffsetBytes, dataSizeBytes, MapBufferAccessMask.WriteBit // Write access for partial update ); if (mappedPtr == null) { throw new Exception("Failed to map buffer for writing."); } try { // Copy the specified range of data to the mapped memory Span mappedSpan = new Span(mappedPtr, lengthElements); data.Slice(sourceOffsetElements, lengthElements).CopyTo(mappedSpan); } finally { // Unmap the buffer GL.UnmapBuffer(GLEnum.ArrayBuffer); GLHelpers.CheckErrors(GL); } } } public void Bind() { GL.BindBuffer(GLEnum.ArrayBuffer, bufferId); GLHelpers.CheckErrors(GL); } public void Unbind() { GL.BindBuffer(GLEnum.ArrayBuffer, 0); GLHelpers.CheckErrors(GL); } public unsafe 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; _mappedPtr = null; } }); } } }