Audio rendering: reduce memory allocations (#6604)
* - WritableRegion: enable wrapping IMemoryOwner<byte>
- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl
* - BytesReadOnlySequenceSegment: move from Ryujinx.Common.Memory to Ryujinx.Memory
- BytesReadOnlySequenceSegment: add IsContiguousWith() and Replace() methods
- VirtualMemoryManagerBase:
- remove generic type parameters, instead use ulong for virtual addresses and nuint for host/physical addresses
- implement IWritableBlock
- add virtual GetReadOnlySequence() with coalescing of contiguous segments
- add virtual GetSpan()
- add virtual GetWritableRegion()
- add abstract IsMapped()
- add virtual MapForeign(ulong, nuint, ulong)
- add virtual Read<T>()
- add virtual Read(ulong, Span<byte>)
- add virtual ReadTracked<T>()
- add virtual SignalMemoryTracking()
- add virtual Write()
- add virtual Write<T>()
- add virtual WriteUntracked()
- add virtual WriteWithRedundancyCheck()
- VirtualMemoryManagerRefCountedBase: remove generic type parameters
- AddressSpaceManager: remove redundant methods, add required overrides
- HvMemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- MemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- MemoryManagerHostMapped: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- NativeMemoryManager: add get properties for Pointer and Length
- throughout: removed invalid <inheritdoc/> comments
* - WritableRegion: enable wrapping IMemoryOwner<byte>
- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl
* add PagedMemoryRange enumerator types, use them in IVirtualMemoryManager implementations to consolidate page-handling logic and add a new capability - the coalescing of pages for consolidating memory copies and segmentation.
* new: more tests for PagedMemoryRangeCoalescingEnumerator showing coalescing of contiguous segments
* make some struct properties readonly
* put braces around `foreach` bodies
* encourage inlining of some PagedMemoryRange*Enumerator members
* DynamicRingBuffer:
- use ByteMemoryPool
- make some methods return without locking when size/count argument = 0
- make generic Read<T>()/Write<T>() non-generic because its only usage is as T = byte
- change Read(byte[]...) to Read(Span<byte>...)
- change Write(byte[]...) to Write(Span<byte>...)
* change IAudioRenderer.RequestUpdate() to take a ReadOnlySequence<byte>, enabling zero-copy audio rendering
* HipcGenerator: support ReadOnlySequence<byte> as IPC method parameter
* change IAudioRenderer/AudioRenderer RequestUpdate* methods to take input as ReadOnlySequence<byte>
* MemoryManagerHostTracked: use rented memory when contiguous in `GetWritableRegion()`
* rebase cleanup
* dotnet format fixes
* format and comment fixes
* format long parameter list - take 2
* - add support to HipcGenerator for buffers of type `Memory<byte>`
- change `AudioRenderer` `RequestUpdate()` and `RequestUpdateAuto()` to use Memory<byte> for output buffers, removing another memory block allocation/copy
* SplitterContext `UpdateState()` and `UpdateData()` smooth out advance/rewind logic, only rewind if magic is invalid
* DynamicRingBuffer.Write(): change Span<byte> to ReadOnlySpan<byte>
2024-04-07 21:07:32 +00:00
using System ;
using System.Buffers ;
using System.Diagnostics ;
using System.Runtime.CompilerServices ;
using System.Runtime.InteropServices ;
namespace Ryujinx.Common.Extensions
{
public static class SequenceReaderExtensions
{
/// <summary>
/// Dumps the entire <see cref="SequenceReader{byte}"/> to a file, restoring its previous location afterward.
/// Useful for debugging purposes.
/// </summary>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to write to a file</param>
/// <param name="fileFullName">The path and name of the file to create and dump to</param>
public static void DumpToFile ( this ref SequenceReader < byte > reader , string fileFullName )
{
var initialConsumed = reader . Consumed ;
reader . Rewind ( initialConsumed ) ;
using ( var fileStream = System . IO . File . Create ( fileFullName , 4096 , System . IO . FileOptions . None ) )
{
while ( reader . End = = false )
{
var span = reader . CurrentSpan ;
fileStream . Write ( span ) ;
reader . Advance ( span . Length ) ;
}
}
reader . SetConsumed ( initialConsumed ) ;
}
/// <summary>
/// Returns a reference to the desired value. This ref should always be used. The argument passed in <paramref name="copyDestinationIfRequiredDoNotUse"/> should never be used, as this is only used for storage if the value
/// must be copied from multiple <see cref="ReadOnlyMemory{Byte}"/> segments held by the <see cref="SequenceReader{Byte}"/>.
/// </summary>
/// <typeparam name="T">Type to get</typeparam>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
/// <param name="copyDestinationIfRequiredDoNotUse">A location used as storage if (and only if) the value to be read spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments</param>
/// <returns>A reference to the desired value, either directly to memory in the <see cref="SequenceReader{Byte}"/>, or to <paramref name="copyDestinationIfRequiredDoNotUse"/> if it has been used for copying the value in to</returns>
/// <remarks>
/// DO NOT use <paramref name="copyDestinationIfRequiredDoNotUse"/> after calling this method, as it will only
/// contain a value if the value couldn't be referenced directly because it spans multiple <see cref="ReadOnlyMemory{Byte}"/> segments.
2024-04-19 12:45:51 +00:00
/// To discourage use, it is recommended to call this method like the following:
Audio rendering: reduce memory allocations (#6604)
* - WritableRegion: enable wrapping IMemoryOwner<byte>
- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl
* - BytesReadOnlySequenceSegment: move from Ryujinx.Common.Memory to Ryujinx.Memory
- BytesReadOnlySequenceSegment: add IsContiguousWith() and Replace() methods
- VirtualMemoryManagerBase:
- remove generic type parameters, instead use ulong for virtual addresses and nuint for host/physical addresses
- implement IWritableBlock
- add virtual GetReadOnlySequence() with coalescing of contiguous segments
- add virtual GetSpan()
- add virtual GetWritableRegion()
- add abstract IsMapped()
- add virtual MapForeign(ulong, nuint, ulong)
- add virtual Read<T>()
- add virtual Read(ulong, Span<byte>)
- add virtual ReadTracked<T>()
- add virtual SignalMemoryTracking()
- add virtual Write()
- add virtual Write<T>()
- add virtual WriteUntracked()
- add virtual WriteWithRedundancyCheck()
- VirtualMemoryManagerRefCountedBase: remove generic type parameters
- AddressSpaceManager: remove redundant methods, add required overrides
- HvMemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- MemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- MemoryManagerHostMapped: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling
- NativeMemoryManager: add get properties for Pointer and Length
- throughout: removed invalid <inheritdoc/> comments
* - WritableRegion: enable wrapping IMemoryOwner<byte>
- IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous.
- IVirtualMemoryManager: add GetReadOnlySequence() and impls
- ByteMemoryPool: add new method RentCopy()
- ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl
* add PagedMemoryRange enumerator types, use them in IVirtualMemoryManager implementations to consolidate page-handling logic and add a new capability - the coalescing of pages for consolidating memory copies and segmentation.
* new: more tests for PagedMemoryRangeCoalescingEnumerator showing coalescing of contiguous segments
* make some struct properties readonly
* put braces around `foreach` bodies
* encourage inlining of some PagedMemoryRange*Enumerator members
* DynamicRingBuffer:
- use ByteMemoryPool
- make some methods return without locking when size/count argument = 0
- make generic Read<T>()/Write<T>() non-generic because its only usage is as T = byte
- change Read(byte[]...) to Read(Span<byte>...)
- change Write(byte[]...) to Write(Span<byte>...)
* change IAudioRenderer.RequestUpdate() to take a ReadOnlySequence<byte>, enabling zero-copy audio rendering
* HipcGenerator: support ReadOnlySequence<byte> as IPC method parameter
* change IAudioRenderer/AudioRenderer RequestUpdate* methods to take input as ReadOnlySequence<byte>
* MemoryManagerHostTracked: use rented memory when contiguous in `GetWritableRegion()`
* rebase cleanup
* dotnet format fixes
* format and comment fixes
* format long parameter list - take 2
* - add support to HipcGenerator for buffers of type `Memory<byte>`
- change `AudioRenderer` `RequestUpdate()` and `RequestUpdateAuto()` to use Memory<byte> for output buffers, removing another memory block allocation/copy
* SplitterContext `UpdateState()` and `UpdateData()` smooth out advance/rewind logic, only rewind if magic is invalid
* DynamicRingBuffer.Write(): change Span<byte> to ReadOnlySpan<byte>
2024-04-07 21:07:32 +00:00
/// <c>
/// ref readonly MyStruct value = ref sequenceReader.GetRefOrRefToCopy{MyStruct}(out _);
/// </c>
/// </remarks>
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
public static ref readonly T GetRefOrRefToCopy < T > ( this scoped ref SequenceReader < byte > reader , out T copyDestinationIfRequiredDoNotUse ) where T : unmanaged
{
int lengthRequired = Unsafe . SizeOf < T > ( ) ;
ReadOnlySpan < byte > span = reader . UnreadSpan ;
if ( lengthRequired < = span . Length )
{
reader . Advance ( lengthRequired ) ;
copyDestinationIfRequiredDoNotUse = default ;
ReadOnlySpan < T > spanOfT = MemoryMarshal . Cast < byte , T > ( span ) ;
return ref spanOfT [ 0 ] ;
}
else
{
copyDestinationIfRequiredDoNotUse = default ;
Span < T > valueSpan = MemoryMarshal . CreateSpan ( ref copyDestinationIfRequiredDoNotUse , 1 ) ;
Span < byte > valueBytesSpan = MemoryMarshal . AsBytes ( valueSpan ) ;
if ( ! reader . TryCopyTo ( valueBytesSpan ) )
{
throw new ArgumentOutOfRangeException ( nameof ( reader ) , "The sequence is not long enough to read the desired value." ) ;
}
reader . Advance ( lengthRequired ) ;
return ref valueSpan [ 0 ] ;
}
}
/// <summary>
/// Reads an <see cref="int"/> as little endian.
/// </summary>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
/// <param name="value">A location to receive the read value</param>
/// <exception cref="ArgumentOutOfRangeException">Thrown if there wasn't enough data for an <see cref="int"/></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadLittleEndian ( this ref SequenceReader < byte > reader , out int value )
{
if ( ! reader . TryReadLittleEndian ( out value ) )
{
throw new ArgumentOutOfRangeException ( nameof ( value ) , "The sequence is not long enough to read the desired value." ) ;
}
}
/// <summary>
/// Reads the desired unmanaged value by copying it to the specified <paramref name="value"/>.
/// </summary>
/// <typeparam name="T">Type to read</typeparam>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to read from</param>
/// <param name="value">The target that will receive the read value</param>
/// <exception cref="ArgumentOutOfRangeException">The <see cref="SequenceReader{Byte}"/> does not contain enough data to read a value of type <typeparamref name="T"/></exception>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static void ReadUnmanaged < T > ( this ref SequenceReader < byte > reader , out T value ) where T : unmanaged
{
if ( ! reader . TryReadUnmanaged ( out value ) )
{
throw new ArgumentOutOfRangeException ( nameof ( value ) , "The sequence is not long enough to read the desired value." ) ;
}
}
/// <summary>
/// Sets the reader's position as bytes consumed.
/// </summary>
/// <param name="reader">The <see cref="SequenceReader{Byte}"/> to set the position</param>
/// <param name="consumed">The number of bytes consumed</param>
public static void SetConsumed ( ref this SequenceReader < byte > reader , long consumed )
{
reader . Rewind ( reader . Consumed ) ;
reader . Advance ( consumed ) ;
}
/// <summary>
/// Try to read the given type out of the buffer if possible. Warning: this is dangerous to use with arbitrary
/// structs - see remarks for full details.
/// </summary>
/// <typeparam name="T">Type to read</typeparam>
/// <remarks>
/// IMPORTANT: The read is a straight copy of bits. If a struct depends on specific state of it's members to
/// behave correctly this can lead to exceptions, etc. If reading endian specific integers, use the explicit
/// overloads such as <see cref="SequenceReader{T}.TryReadLittleEndian"/>
/// </remarks>
/// <returns>
/// True if successful. <paramref name="value"/> will be default if failed (due to lack of space).
/// </returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static unsafe bool TryReadUnmanaged < T > ( ref this SequenceReader < byte > reader , out T value ) where T : unmanaged
{
ReadOnlySpan < byte > span = reader . UnreadSpan ;
if ( span . Length < sizeof ( T ) )
{
return TryReadUnmanagedMultiSegment ( ref reader , out value ) ;
}
value = Unsafe . ReadUnaligned < T > ( ref MemoryMarshal . GetReference ( span ) ) ;
reader . Advance ( sizeof ( T ) ) ;
return true ;
}
private static unsafe bool TryReadUnmanagedMultiSegment < T > ( ref SequenceReader < byte > reader , out T value ) where T : unmanaged
{
Debug . Assert ( reader . UnreadSpan . Length < sizeof ( T ) ) ;
// Not enough data in the current segment, try to peek for the data we need.
T buffer = default ;
Span < byte > tempSpan = new Span < byte > ( & buffer , sizeof ( T ) ) ;
if ( ! reader . TryCopyTo ( tempSpan ) )
{
value = default ;
return false ;
}
value = Unsafe . ReadUnaligned < T > ( ref MemoryMarshal . GetReference ( tempSpan ) ) ;
reader . Advance ( sizeof ( T ) ) ;
return true ;
}
}
}