aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
diff options
context:
space:
mode:
authorjhorv <38920027+jhorv@users.noreply.github.com>2024-04-07 17:07:32 -0400
committerGitHub <noreply@github.com>2024-04-07 18:07:32 -0300
commitead9a251418bb2402ffa19ece089406b0678544e (patch)
tree1ed0456224e75e7014f761203fd88149041bea5d /src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
parent3e0d67533f1d31f9d0d0ac0b48922c719c5d8424 (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.cs181
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;
+ }
+ }
+}