210 lines
5.1 KiB
C#
210 lines
5.1 KiB
C#
|
using System;
|
||
|
using System.Runtime.InteropServices;
|
||
|
using RefreshCS;
|
||
|
|
||
|
namespace Nerfed.Runtime.Graphics;
|
||
|
|
||
|
/// <summary>
|
||
|
/// A data container that can efficiently transfer data to and from the GPU.
|
||
|
/// </summary>
|
||
|
public unsafe class TransferBuffer : RefreshResource
|
||
|
{
|
||
|
protected override Action<IntPtr, IntPtr> ReleaseFunction => Refresh.Refresh_ReleaseTransferBuffer;
|
||
|
|
||
|
/// <summary>
|
||
|
/// Size in bytes.
|
||
|
/// </summary>
|
||
|
public uint Size { get; }
|
||
|
|
||
|
#if DEBUG
|
||
|
public bool Mapped { get; private set; }
|
||
|
#endif
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a buffer of requested size given a type and element count.
|
||
|
/// </summary>
|
||
|
/// <typeparam name="T">The type that the buffer will contain.</typeparam>
|
||
|
/// <param name="device">The GraphicsDevice.</param>
|
||
|
/// <param name="elementCount">How many elements of type T the buffer will contain.</param>
|
||
|
/// <returns></returns>
|
||
|
public unsafe static TransferBuffer Create<T>(
|
||
|
GraphicsDevice device,
|
||
|
TransferBufferUsage usage,
|
||
|
uint elementCount
|
||
|
) where T : unmanaged
|
||
|
{
|
||
|
return new TransferBuffer(
|
||
|
device,
|
||
|
usage,
|
||
|
(uint) Marshal.SizeOf<T>() * elementCount
|
||
|
);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Creates a TransferBuffer.
|
||
|
/// </summary>
|
||
|
/// <param name="device">An initialized GraphicsDevice.</param>
|
||
|
/// <param name="usage">Whether this will be used to upload buffers or textures.</param>
|
||
|
/// <param name="sizeInBytes">The length of the buffer. Cannot be resized.</param>
|
||
|
public TransferBuffer(
|
||
|
GraphicsDevice device,
|
||
|
TransferBufferUsage usage,
|
||
|
uint sizeInBytes
|
||
|
) : base(device)
|
||
|
{
|
||
|
Handle = Refresh.Refresh_CreateTransferBuffer(
|
||
|
device.Handle,
|
||
|
(Refresh.TransferBufferUsage) usage,
|
||
|
sizeInBytes
|
||
|
);
|
||
|
Size = sizeInBytes;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Immediately copies data from a Span to the TransferBuffer.
|
||
|
/// Returns the length of the copy in bytes.
|
||
|
///
|
||
|
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
|
||
|
/// that command will still use the correct data at the cost of increased memory usage.
|
||
|
///
|
||
|
/// If cycle is set to false, the data will be overwritten immediately,
|
||
|
/// which could cause a data race.
|
||
|
/// </summary>
|
||
|
public unsafe uint SetData<T>(
|
||
|
Span<T> source,
|
||
|
uint bufferOffsetInBytes,
|
||
|
bool cycle
|
||
|
) where T : unmanaged
|
||
|
{
|
||
|
int elementSize = Marshal.SizeOf<T>();
|
||
|
uint dataLengthInBytes = (uint) (elementSize * source.Length);
|
||
|
|
||
|
#if DEBUG
|
||
|
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
|
||
|
AssertNotMapped();
|
||
|
#endif
|
||
|
|
||
|
fixed (T* dataPtr = source)
|
||
|
{
|
||
|
Refresh.Refresh_SetTransferData(
|
||
|
Device.Handle,
|
||
|
(nint) dataPtr,
|
||
|
new Refresh.TransferBufferRegion
|
||
|
{
|
||
|
TransferBuffer = Handle,
|
||
|
Offset = bufferOffsetInBytes,
|
||
|
Size = dataLengthInBytes
|
||
|
},
|
||
|
Conversions.BoolToInt(cycle)
|
||
|
);
|
||
|
}
|
||
|
|
||
|
return dataLengthInBytes;
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Immediately copies data from a Span to the TransferBuffer.
|
||
|
/// Returns the length of the copy in bytes.
|
||
|
///
|
||
|
/// If cycle is set to true and this TransferBuffer was used in an Upload command,
|
||
|
/// that command will still use the corret data at the cost of increased memory usage.
|
||
|
///
|
||
|
/// If cycle is set to false, the data will be overwritten immediately,
|
||
|
/// which could cause a data race.
|
||
|
/// </summary>
|
||
|
public unsafe uint SetData<T>(
|
||
|
Span<T> source,
|
||
|
bool cycle
|
||
|
) where T : unmanaged
|
||
|
{
|
||
|
return SetData(source, 0, cycle);
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Immediately copies data from the TransferBuffer into a Span.
|
||
|
/// </summary>
|
||
|
public unsafe void GetData<T>(
|
||
|
Span<T> destination,
|
||
|
uint bufferOffsetInBytes = 0
|
||
|
) where T : unmanaged
|
||
|
{
|
||
|
int elementSize = Marshal.SizeOf<T>();
|
||
|
uint dataLengthInBytes = (uint) (elementSize * destination.Length);
|
||
|
|
||
|
#if DEBUG
|
||
|
AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes);
|
||
|
AssertNotMapped();
|
||
|
#endif
|
||
|
|
||
|
fixed (T* dataPtr = destination)
|
||
|
{
|
||
|
Refresh.Refresh_GetTransferData(
|
||
|
Device.Handle,
|
||
|
new Refresh.TransferBufferRegion
|
||
|
{
|
||
|
TransferBuffer = Handle,
|
||
|
Offset = bufferOffsetInBytes,
|
||
|
Size = dataLengthInBytes
|
||
|
},
|
||
|
(nint) dataPtr
|
||
|
);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Maps the transfer buffer into application address space.
|
||
|
/// You must call Unmap before encoding transfer commands.
|
||
|
/// </summary>
|
||
|
public unsafe void Map(bool cycle, out byte* data)
|
||
|
{
|
||
|
#if DEBUG
|
||
|
AssertNotMapped();
|
||
|
#endif
|
||
|
|
||
|
Refresh.Refresh_MapTransferBuffer(
|
||
|
Device.Handle,
|
||
|
Handle,
|
||
|
Conversions.BoolToInt(cycle),
|
||
|
out data
|
||
|
);
|
||
|
|
||
|
#if DEBUG
|
||
|
Mapped = true;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
/// <summary>
|
||
|
/// Unmaps the transfer buffer.
|
||
|
/// The pointer given by Map is no longer valid.
|
||
|
/// </summary>
|
||
|
public void Unmap()
|
||
|
{
|
||
|
Refresh.Refresh_UnmapTransferBuffer(
|
||
|
Device.Handle,
|
||
|
Handle
|
||
|
);
|
||
|
|
||
|
#if DEBUG
|
||
|
Mapped = false;
|
||
|
#endif
|
||
|
}
|
||
|
|
||
|
#if DEBUG
|
||
|
private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes)
|
||
|
{
|
||
|
if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes)
|
||
|
{
|
||
|
throw new InvalidOperationException($"Data overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}");
|
||
|
}
|
||
|
}
|
||
|
|
||
|
private void AssertNotMapped()
|
||
|
{
|
||
|
if (Mapped)
|
||
|
{
|
||
|
throw new InvalidOperationException("Transfer buffer must not be mapped!");
|
||
|
}
|
||
|
}
|
||
|
#endif
|
||
|
}
|