using System; using System.Runtime.InteropServices; using RefreshCS; namespace MoonWorks.Graphics { public unsafe class TransferBuffer : RefreshResource { protected override Action QueueDestroyFunction => Refresh.Refresh_QueueDestroyTransferBuffer; /// /// Size in bytes. /// public uint Size { get; } /// /// 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, uint elementCount ) where T : unmanaged { return new TransferBuffer( device, (uint) Marshal.SizeOf() * elementCount ); } /// /// Creates a TransferBuffer. /// /// An initialized GraphicsDevice. /// The length of the buffer. Cannot be resized. public TransferBuffer( GraphicsDevice device, uint sizeInBytes ) : base(device) { Handle = Refresh.Refresh_CreateTransferBuffer( device.Handle, sizeInBytes ); Size = sizeInBytes; } /// /// Immediately copies data from a Span to the TransferBuffer. /// Returns the length of the copy in bytes. /// /// If setDataOption is DISCARD 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 setDataOption is OVERWRITE and this TransferBuffer was used in an Upload command, /// the data will be overwritten immediately, which could cause a data race. /// public unsafe uint SetData( Span data, uint bufferOffsetInBytes, SetDataOptions setDataOption ) where T : unmanaged { var elementSize = Marshal.SizeOf(); var dataLengthInBytes = (uint) (elementSize * data.Length); #if DEBUG AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes); #endif fixed (T* dataPtr = data) { Refresh.Refresh_SetData( Device.Handle, (nint) dataPtr, Handle, new Refresh.BufferCopy { srcOffset = 0, dstOffset = bufferOffsetInBytes, size = dataLengthInBytes }, (Refresh.SetDataOptions) setDataOption ); } return dataLengthInBytes; } /// /// Immediately copies data from a Span to the TransferBuffer. /// Returns the length of the copy in bytes. /// /// If setDataOption is DISCARD 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 setDataOption is OVERWRITE and this TransferBuffer was used in an Upload command, /// the data will be overwritten immediately, which could cause a data race. /// public unsafe uint SetData( Span data, SetDataOptions setDataOption ) where T : unmanaged { return SetData(data, 0, setDataOption); } /// /// Immediately copies data from the TransferBuffer into a Span. /// public unsafe void GetData( Span data, uint bufferOffsetInBytes = 0 ) where T : unmanaged { var elementSize = Marshal.SizeOf(); var dataLengthInBytes = (uint) (elementSize * data.Length); #if DEBUG AssertBufferBoundsCheck(Size, bufferOffsetInBytes, dataLengthInBytes); #endif fixed (T* dataPtr = data) { Refresh.Refresh_GetData( Device.Handle, Handle, (nint) dataPtr, new Refresh.BufferCopy { srcOffset = bufferOffsetInBytes, dstOffset = 0, size = dataLengthInBytes } ); } } #if DEBUG private void AssertBufferBoundsCheck(uint bufferLengthInBytes, uint offsetInBytes, uint copyLengthInBytes) { if (copyLengthInBytes > bufferLengthInBytes + offsetInBytes) { throw new InvalidOperationException($"SetData overflow! Transfer buffer length {bufferLengthInBytes}, offset {offsetInBytes}, copy length {copyLengthInBytes}"); } } #endif } }