Dashboard/Dashboard.Drawing.OpenGL/MappableBuffer.cs

168 lines
4.7 KiB
C#

using System.Numerics;
using System.Runtime.CompilerServices;
using OpenTK.Graphics.OpenGL;
namespace Dashboard.Drawing.OpenGL
{
public class MappableBuffer<T> : 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<T>();
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<T>();
}
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<T> AsSpan()
{
if (Pointer == IntPtr.Zero)
throw new InvalidOperationException("The buffer is not currently mapped.");
AssertInitialized();
return new Span<T>(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<T> : MappableBuffer<T> 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;
}
}
}