using System;
using System.Runtime.InteropServices;
using RefreshCS;
namespace Nerfed.Runtime.Graphics;
///
/// A data container that can efficiently transfer data to and from the GPU.
///
public unsafe class TransferBuffer : RefreshResource
{
protected override Action ReleaseFunction => Refresh.Refresh_ReleaseTransferBuffer;
///
/// Size in bytes.
///
public uint Size { get; }
#if DEBUG
public bool Mapped { get; private set; }
#endif
///
/// Creates a buffer of requested size given a type and element count.
///
/// The type that the buffer will contain.
/// The GraphicsDevice.
/// How many elements of type T the buffer will contain.
///
public unsafe static TransferBuffer Create(
GraphicsDevice device,
TransferBufferUsage usage,
uint elementCount
) where T : unmanaged
{
return new TransferBuffer(
device,
usage,
(uint) Marshal.SizeOf() * elementCount
);
}
///
/// Creates a TransferBuffer.
///
/// An initialized GraphicsDevice.
/// Whether this will be used to upload buffers or textures.
/// The length of the buffer. Cannot be resized.
public TransferBuffer(
GraphicsDevice device,
TransferBufferUsage usage,
uint sizeInBytes
) : base(device)
{
Handle = Refresh.Refresh_CreateTransferBuffer(
device.Handle,
(Refresh.TransferBufferUsage) usage,
sizeInBytes
);
Size = sizeInBytes;
}
///
/// 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.
///
public unsafe uint SetData(
Span source,
uint bufferOffsetInBytes,
bool cycle
) where T : unmanaged
{
int elementSize = Marshal.SizeOf();
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;
}
///
/// 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.
///
public unsafe uint SetData(
Span source,
bool cycle
) where T : unmanaged
{
return SetData(source, 0, cycle);
}
///
/// Immediately copies data from the TransferBuffer into a Span.
///
public unsafe void GetData(
Span destination,
uint bufferOffsetInBytes = 0
) where T : unmanaged
{
int elementSize = Marshal.SizeOf();
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
);
}
}
///
/// Maps the transfer buffer into application address space.
/// You must call Unmap before encoding transfer commands.
///
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
}
///
/// Unmaps the transfer buffer.
/// The pointer given by Map is no longer valid.
///
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
}