using System.Numerics; using System.Runtime.CompilerServices; using OpenTK.Graphics.OpenGL; namespace Dashboard.Drawing.OpenGL { public class MappableBuffer : IDisposable { public int Handle { get; private set; } = 0; public int Capacity { get; set; } = BASE_CAPACITY; public IntPtr Pointer { get; private set; } = IntPtr.Zero; private bool _isDisposed = false; private const int BASE_CAPACITY = 4 << 10; // 4 KiB private const int MAX_INCREMENT = 4 << 20; // 4 MiB ~MappableBuffer() { Dispose(false); } public void Initialize() { Handle = GL.GenBuffer(); GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); GL.BufferData(BufferTarget.ArrayBuffer, Capacity, IntPtr.Zero, BufferUsage.DynamicDraw); } public void EnsureCapacity(int count) { if (count < 0) throw new ArgumentOutOfRangeException(nameof(count)); if (Capacity >= count) return; AssertInitialized(); Unmap(); int sz = Unsafe.SizeOf(); int oldsize = Capacity * sz; int request = count * sz; int newsize; if (request > MAX_INCREMENT) { newsize = ((request + MAX_INCREMENT - 1) / MAX_INCREMENT) * MAX_INCREMENT; } else { newsize = checked((int)BitOperations.RoundUpToPowerOf2((ulong)request)); } int dest = GL.GenBuffer(); GL.BindBuffer(BufferTarget.CopyWriteBuffer, dest); GL.BindBuffer(BufferTarget.CopyReadBuffer, Handle); GL.BufferData(BufferTarget.CopyWriteBuffer, newsize, IntPtr.Zero, BufferUsage.DynamicDraw); GL.CopyBufferSubData(CopyBufferSubDataTarget.CopyReadBuffer, CopyBufferSubDataTarget.CopyWriteBuffer, 0, 0, oldsize); GL.DeleteBuffer(Handle); Handle = dest; Capacity = newsize; } public unsafe void Map() { if (Pointer != IntPtr.Zero) return; AssertInitialized(); GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); Pointer = (IntPtr)GL.MapBuffer(BufferTarget.ArrayBuffer, BufferAccess.ReadWrite); } public void Unmap() { if (Pointer == IntPtr.Zero) return; AssertInitialized(); GL.BindBuffer(BufferTarget.ArrayBuffer, Handle); GL.UnmapBuffer(BufferTarget.ArrayBuffer); } public unsafe Span AsSpan() { if (Pointer == IntPtr.Zero) throw new InvalidOperationException("The buffer is not currently mapped."); AssertInitialized(); return new Span(Pointer.ToPointer(), Capacity / Unsafe.SizeOf()); } private void AssertInitialized() { if (Handle == 0) throw new InvalidOperationException("The buffer is not initialized."); } private void Dispose(bool disposing) { if (_isDisposed) return; _isDisposed = true; if (disposing) GC.SuppressFinalize(this); ContextCollector.Global.DeleteBufffer(Handle); } public void Dispose() => Dispose(true); } }