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