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 }