using System.Numerics; using System.Runtime.CompilerServices; using OpenTK.Graphics.OpenGL; namespace Dashboard.Drawing.OpenGL { public class MappableBuffer : IInitializer, IGLDisposable where T : struct { public int Handle { get; private set; } = 0; public int Capacity { get; set; } = BASE_CAPACITY; public IntPtr Pointer { get; private set; } = IntPtr.Zero; public bool IsInitialized => Handle != 0; private bool _isDisposed = false; private const int BASE_CAPACITY = 4 << 10; // 4 KiB private const int MAX_INCREMENT = 4 << 20; // 4 MiB ~MappableBuffer() { Dispose(true, false); } public void Initialize() { if (IsInitialized) return; 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; SetSize(count, false); } public void SetSize(int count, bool clear = false) { AssertInitialized(); Unmap(); int sz = Unsafe.SizeOf(); int oldsize = Capacity * sz; int request = count * sz; int newsize; if (request < BASE_CAPACITY) request = BASE_CAPACITY; 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(); if (clear) { GL.BindBuffer(BufferTarget.ArrayBuffer, dest); GL.BufferData(BufferTarget.ArrayBuffer, newsize, IntPtr.Zero, BufferUsage.DynamicDraw); } else { 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, Math.Min(newsize, oldsize)); } GL.DeleteBuffer(Handle); Handle = dest; Capacity = newsize / Unsafe.SizeOf(); } 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); Pointer = IntPtr.Zero; } public unsafe Span AsSpan() { if (Pointer == IntPtr.Zero) throw new InvalidOperationException("The buffer is not currently mapped."); AssertInitialized(); return new Span(Pointer.ToPointer(), Capacity); } private void AssertInitialized() { if (Handle == 0) throw new InvalidOperationException("The buffer is not initialized."); } private void Dispose(bool safeExit, bool disposing) { if (_isDisposed) return; _isDisposed = true; if (disposing) GC.SuppressFinalize(this); if (safeExit) ContextCollector.Global.DeleteBufffer(Handle); } public void Dispose() => Dispose(true, true); public void Dispose(bool safeExit) => Dispose(safeExit, true); } public class MappableBumpAllocator : MappableBuffer where T : struct { private int _top = 0; private int _previousTop = 0; public ref T Take(out int index) { index = _top; EnsureCapacity(++_top); Map(); return ref AsSpan()[index]; } public ref T Take() => ref Take(out _); public void Clear() { SetSize(0, true); _previousTop = _top; _top = 0; } } }