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