diff options
| author | jhorv <38920027+jhorv@users.noreply.github.com> | 2024-04-07 17:07:32 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-07 18:07:32 -0300 |
| commit | ead9a251418bb2402ffa19ece089406b0678544e (patch) | |
| tree | 1ed0456224e75e7014f761203fd88149041bea5d /src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs | |
| parent | 3e0d67533f1d31f9d0d0ac0b48922c719c5d8424 (diff) | |
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>
Diffstat (limited to 'src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs')
| -rw-r--r-- | src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs | 181 |
1 files changed, 181 insertions, 0 deletions
diff --git a/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs new file mode 100644 index 00000000..5403c87c --- /dev/null +++ b/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs @@ -0,0 +1,181 @@ +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. + /// To discourage use, it is recommended to to call this method like the following: + /// <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; + } + } +} |
