mirror of
https://github.com/Ryujinx/Ryujinx.git
synced 2025-04-26 20:20:51 -07:00
* - 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>
360 lines
13 KiB
C#
360 lines
13 KiB
C#
using NUnit.Framework;
|
|
using Ryujinx.Common.Extensions;
|
|
using Ryujinx.Memory;
|
|
using System;
|
|
using System.Buffers;
|
|
using System.Buffers.Binary;
|
|
using System.Collections.Generic;
|
|
using System.Linq;
|
|
using System.Runtime.CompilerServices;
|
|
using System.Runtime.InteropServices;
|
|
|
|
namespace Ryujinx.Tests.Common.Extensions
|
|
{
|
|
public class SequenceReaderExtensionsTests
|
|
{
|
|
[TestCase(null)]
|
|
[TestCase(sizeof(int) + 1)]
|
|
public void GetRefOrRefToCopy_ReadsMultiSegmentedSequenceSuccessfully(int? maxSegmentSize)
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence =
|
|
CreateSegmentedByteSequence(originalStructs, maxSegmentSize ?? Unsafe.SizeOf<MyUnmanagedStruct>());
|
|
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
foreach (var original in originalStructs)
|
|
{
|
|
// Act
|
|
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
|
|
|
// Assert
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void GetRefOrRefToCopy_FragmentedSequenceReturnsRefToCopy()
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, 3);
|
|
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
foreach (var original in originalStructs)
|
|
{
|
|
// Act
|
|
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy);
|
|
|
|
// Assert
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, read, copy);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void GetRefOrRefToCopy_ContiguousSequenceReturnsRefToBuffer()
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
|
|
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
foreach (var original in originalStructs)
|
|
{
|
|
// Act
|
|
ref readonly MyUnmanagedStruct read = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out var copy);
|
|
|
|
// Assert
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
|
MyUnmanagedStruct.Assert(Assert.AreNotEqual, read, copy);
|
|
}
|
|
}
|
|
|
|
[Test]
|
|
public void GetRefOrRefToCopy_ThrowsWhenNotEnoughData()
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
|
|
|
|
// Act/Assert
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
{
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
sequenceReader.Advance(1);
|
|
|
|
ref readonly MyUnmanagedStruct result = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void ReadLittleEndian_Int32_RoundTripsSuccessfully()
|
|
{
|
|
// Arrange
|
|
const int TestValue = 0x1234abcd;
|
|
|
|
byte[] buffer = new byte[sizeof(int)];
|
|
|
|
BinaryPrimitives.WriteInt32LittleEndian(buffer.AsSpan(), TestValue);
|
|
|
|
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
|
|
|
|
// Act
|
|
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
|
|
|
|
// Assert
|
|
Assert.AreEqual(TestValue, roundTrippedValue);
|
|
}
|
|
|
|
[Test]
|
|
public void ReadLittleEndian_Int32_ResultIsNotBigEndian()
|
|
{
|
|
// Arrange
|
|
const int TestValue = 0x1234abcd;
|
|
|
|
byte[] buffer = new byte[sizeof(int)];
|
|
|
|
BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue);
|
|
|
|
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
|
|
|
|
// Act
|
|
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
|
|
|
|
// Assert
|
|
Assert.AreNotEqual(TestValue, roundTrippedValue);
|
|
}
|
|
|
|
[Test]
|
|
public void ReadLittleEndian_Int32_ThrowsWhenNotEnoughData()
|
|
{
|
|
// Arrange
|
|
const int TestValue = 0x1234abcd;
|
|
|
|
byte[] buffer = new byte[sizeof(int)];
|
|
|
|
BinaryPrimitives.WriteInt32BigEndian(buffer.AsSpan(), TestValue);
|
|
|
|
// Act/Assert
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
{
|
|
var sequenceReader = new SequenceReader<byte>(new ReadOnlySequence<byte>(buffer));
|
|
sequenceReader.Advance(1);
|
|
|
|
sequenceReader.ReadLittleEndian(out int roundTrippedValue);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void ReadUnmanaged_ContiguousSequence_Succeeds()
|
|
=> ReadUnmanaged_Succeeds(int.MaxValue);
|
|
|
|
[Test]
|
|
public void ReadUnmanaged_FragmentedSequence_Succeeds()
|
|
=> ReadUnmanaged_Succeeds(sizeof(int) + 1);
|
|
|
|
[Test]
|
|
public void ReadUnmanaged_ThrowsWhenNotEnoughData()
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(1).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, int.MaxValue);
|
|
|
|
// Act/Assert
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
{
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
sequenceReader.Advance(1);
|
|
|
|
sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read);
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public void SetConsumed_ContiguousSequence_SucceedsWhenValid()
|
|
=> SetConsumed_SucceedsWhenValid(int.MaxValue);
|
|
|
|
[Test]
|
|
public void SetConsumed_FragmentedSequence_SucceedsWhenValid()
|
|
=> SetConsumed_SucceedsWhenValid(sizeof(int) + 1);
|
|
|
|
[Test]
|
|
public void SetConsumed_ThrowsWhenBeyondActualLength()
|
|
{
|
|
const int StructCount = 2;
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(StructCount).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, MyUnmanagedStruct.SizeOf);
|
|
|
|
Assert.Throws<ArgumentOutOfRangeException>(() =>
|
|
{
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
sequenceReader.SetConsumed(MyUnmanagedStruct.SizeOf * StructCount + 1);
|
|
});
|
|
}
|
|
|
|
private static void ReadUnmanaged_Succeeds(int maxSegmentLength)
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(3).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength);
|
|
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
foreach (var original in originalStructs)
|
|
{
|
|
// Act
|
|
sequenceReader.ReadUnmanaged(out MyUnmanagedStruct read);
|
|
|
|
// Assert
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, original, read);
|
|
}
|
|
}
|
|
|
|
private static void SetConsumed_SucceedsWhenValid(int maxSegmentLength)
|
|
{
|
|
// Arrange
|
|
MyUnmanagedStruct[] originalStructs = EnumerateNewUnmanagedStructs().Take(2).ToArray();
|
|
|
|
ReadOnlySequence<byte> sequence = CreateSegmentedByteSequence(originalStructs, maxSegmentLength);
|
|
|
|
var sequenceReader = new SequenceReader<byte>(sequence);
|
|
|
|
static void SetConsumedAndAssert(scoped ref SequenceReader<byte> sequenceReader, long consumed)
|
|
{
|
|
sequenceReader.SetConsumed(consumed);
|
|
Assert.AreEqual(consumed, sequenceReader.Consumed);
|
|
}
|
|
|
|
// Act/Assert
|
|
ref readonly MyUnmanagedStruct struct0A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
|
|
|
Assert.AreEqual(sequenceReader.Consumed, MyUnmanagedStruct.SizeOf);
|
|
|
|
SetConsumedAndAssert(ref sequenceReader, 0);
|
|
|
|
ref readonly MyUnmanagedStruct struct0B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
|
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, struct0A, struct0B);
|
|
|
|
SetConsumedAndAssert(ref sequenceReader, 1);
|
|
|
|
SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf);
|
|
|
|
ref readonly MyUnmanagedStruct struct1A = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
|
|
|
SetConsumedAndAssert(ref sequenceReader, MyUnmanagedStruct.SizeOf);
|
|
|
|
ref readonly MyUnmanagedStruct struct1B = ref sequenceReader.GetRefOrRefToCopy<MyUnmanagedStruct>(out _);
|
|
|
|
MyUnmanagedStruct.Assert(Assert.AreEqual, struct1A, struct1B);
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1)]
|
|
private struct MyUnmanagedStruct
|
|
{
|
|
public int BehaviourSize;
|
|
public int MemoryPoolsSize;
|
|
public short VoicesSize;
|
|
public int VoiceResourcesSize;
|
|
public short EffectsSize;
|
|
public int RenderInfoSize;
|
|
|
|
public unsafe fixed byte Reserved[16];
|
|
|
|
public static readonly int SizeOf = Unsafe.SizeOf<MyUnmanagedStruct>();
|
|
|
|
public static unsafe MyUnmanagedStruct Generate(Random rng)
|
|
{
|
|
const int BaseInt32Value = 0x1234abcd;
|
|
const short BaseInt16Value = 0x5678;
|
|
|
|
var result = new MyUnmanagedStruct
|
|
{
|
|
BehaviourSize = BaseInt32Value ^ rng.Next(),
|
|
MemoryPoolsSize = BaseInt32Value ^ rng.Next(),
|
|
VoicesSize = (short)(BaseInt16Value ^ rng.Next()),
|
|
VoiceResourcesSize = BaseInt32Value ^ rng.Next(),
|
|
EffectsSize = (short)(BaseInt16Value ^ rng.Next()),
|
|
RenderInfoSize = BaseInt32Value ^ rng.Next(),
|
|
};
|
|
|
|
Unsafe.Write(result.Reserved, rng.NextInt64());
|
|
|
|
return result;
|
|
}
|
|
|
|
public static unsafe void Assert(Action<object, object> assert, in MyUnmanagedStruct expected, in MyUnmanagedStruct actual)
|
|
{
|
|
assert(expected.BehaviourSize, actual.BehaviourSize);
|
|
assert(expected.MemoryPoolsSize, actual.MemoryPoolsSize);
|
|
assert(expected.VoicesSize, actual.VoicesSize);
|
|
assert(expected.VoiceResourcesSize, actual.VoiceResourcesSize);
|
|
assert(expected.EffectsSize, actual.EffectsSize);
|
|
assert(expected.RenderInfoSize, actual.RenderInfoSize);
|
|
|
|
fixed (void* expectedReservedPtr = expected.Reserved)
|
|
fixed (void* actualReservedPtr = actual.Reserved)
|
|
{
|
|
long expectedReservedLong = Unsafe.Read<long>(expectedReservedPtr);
|
|
long actualReservedLong = Unsafe.Read<long>(actualReservedPtr);
|
|
|
|
assert(expectedReservedLong, actualReservedLong);
|
|
}
|
|
}
|
|
}
|
|
|
|
private static IEnumerable<MyUnmanagedStruct> EnumerateNewUnmanagedStructs()
|
|
{
|
|
var rng = new Random(0);
|
|
|
|
while (true)
|
|
{
|
|
yield return MyUnmanagedStruct.Generate(rng);
|
|
}
|
|
}
|
|
|
|
private static ReadOnlySequence<byte> CreateSegmentedByteSequence<T>(T[] array, int maxSegmentLength) where T : unmanaged
|
|
{
|
|
byte[] arrayBytes = MemoryMarshal.AsBytes(array.AsSpan()).ToArray();
|
|
var memory = new Memory<byte>(arrayBytes);
|
|
int index = 0;
|
|
|
|
BytesReadOnlySequenceSegment first = null, last = null;
|
|
|
|
while (index < memory.Length)
|
|
{
|
|
int nextSegmentLength = Math.Min(maxSegmentLength, memory.Length - index);
|
|
var nextSegment = memory.Slice(index, nextSegmentLength);
|
|
|
|
if (first == null)
|
|
{
|
|
first = last = new BytesReadOnlySequenceSegment(nextSegment);
|
|
}
|
|
else
|
|
{
|
|
last = last.Append(nextSegment);
|
|
}
|
|
|
|
index += nextSegmentLength;
|
|
}
|
|
|
|
return new ReadOnlySequence<byte>(first, 0, last, (int)(memory.Length - last.RunningIndex));
|
|
}
|
|
}
|
|
}
|