diff options
32 files changed, 847 insertions, 262 deletions
diff --git a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs index 05dd2162..b95e5bed 100644 --- a/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs +++ b/src/Ryujinx.Audio/Backends/Common/DynamicRingBuffer.cs @@ -1,5 +1,7 @@ using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; namespace Ryujinx.Audio.Backends.Common { @@ -12,7 +14,8 @@ namespace Ryujinx.Audio.Backends.Common private readonly object _lock = new(); - private byte[] _buffer; + private IMemoryOwner<byte> _bufferOwner; + private Memory<byte> _buffer; private int _size; private int _headOffset; private int _tailOffset; @@ -21,7 +24,8 @@ namespace Ryujinx.Audio.Backends.Common public DynamicRingBuffer(int initialCapacity = RingBufferAlignment) { - _buffer = new byte[initialCapacity]; + _bufferOwner = ByteMemoryPool.RentCleared(initialCapacity); + _buffer = _bufferOwner.Memory; } public void Clear() @@ -33,6 +37,11 @@ namespace Ryujinx.Audio.Backends.Common public void Clear(int size) { + if (size == 0) + { + return; + } + lock (_lock) { if (size > _size) @@ -40,11 +49,6 @@ namespace Ryujinx.Audio.Backends.Common size = _size; } - if (size == 0) - { - return; - } - _headOffset = (_headOffset + size) % _buffer.Length; _size -= size; @@ -58,28 +62,31 @@ namespace Ryujinx.Audio.Backends.Common private void SetCapacityLocked(int capacity) { - byte[] buffer = new byte[capacity]; + IMemoryOwner<byte> newBufferOwner = ByteMemoryPool.RentCleared(capacity); + Memory<byte> newBuffer = newBufferOwner.Memory; if (_size > 0) { if (_headOffset < _tailOffset) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _size); + _buffer.Slice(_headOffset, _size).CopyTo(newBuffer); } else { - Buffer.BlockCopy(_buffer, _headOffset, buffer, 0, _buffer.Length - _headOffset); - Buffer.BlockCopy(_buffer, 0, buffer, _buffer.Length - _headOffset, _tailOffset); + _buffer[_headOffset..].CopyTo(newBuffer); + _buffer[.._tailOffset].CopyTo(newBuffer[(_buffer.Length - _headOffset)..]); } } - _buffer = buffer; + _bufferOwner.Dispose(); + + _bufferOwner = newBufferOwner; + _buffer = newBuffer; _headOffset = 0; _tailOffset = _size; } - - public void Write<T>(T[] buffer, int index, int count) + public void Write(ReadOnlySpan<byte> buffer, int index, int count) { if (count == 0) { @@ -99,17 +106,17 @@ namespace Ryujinx.Audio.Backends.Common if (tailLength >= count) { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } else { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, tailLength); - Buffer.BlockCopy(buffer, index + tailLength, _buffer, 0, count - tailLength); + buffer.Slice(index, tailLength).CopyTo(_buffer.Span[_tailOffset..]); + buffer.Slice(index + tailLength, count - tailLength).CopyTo(_buffer.Span); } } else { - Buffer.BlockCopy(buffer, index, _buffer, _tailOffset, count); + buffer.Slice(index, count).CopyTo(_buffer.Span[_tailOffset..]); } _size += count; @@ -117,8 +124,13 @@ namespace Ryujinx.Audio.Backends.Common } } - public int Read<T>(T[] buffer, int index, int count) + public int Read(Span<byte> buffer, int index, int count) { + if (count == 0) + { + return 0; + } + lock (_lock) { if (count > _size) @@ -126,14 +138,9 @@ namespace Ryujinx.Audio.Backends.Common count = _size; } - if (count == 0) - { - return 0; - } - if (_headOffset < _tailOffset) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { @@ -141,12 +148,12 @@ namespace Ryujinx.Audio.Backends.Common if (tailLength >= count) { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, count); + _buffer.Span.Slice(_headOffset, count).CopyTo(buffer[index..]); } else { - Buffer.BlockCopy(_buffer, _headOffset, buffer, index, tailLength); - Buffer.BlockCopy(_buffer, 0, buffer, index + tailLength, count - tailLength); + _buffer.Span.Slice(_headOffset, tailLength).CopyTo(buffer[index..]); + _buffer.Span[..(count - tailLength)].CopyTo(buffer[(index + tailLength)..]); } } diff --git a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs index b0963c93..3b8d15dc 100644 --- a/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs +++ b/src/Ryujinx.Audio/Renderer/Common/BehaviourParameter.cs @@ -25,7 +25,7 @@ namespace Ryujinx.Audio.Renderer.Common public ulong Flags; /// <summary> - /// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>. + /// Represents an error during <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>. /// </summary> [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct ErrorInfo diff --git a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs index 7efe3b02..98b224eb 100644 --- a/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs +++ b/src/Ryujinx.Audio/Renderer/Common/UpdateDataHeader.cs @@ -4,7 +4,7 @@ using System.Runtime.CompilerServices; namespace Ryujinx.Audio.Renderer.Common { /// <summary> - /// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.ReadOnlyMemory{byte})"/>. + /// Update data header used for input and output of <see cref="Server.AudioRenderSystem.Update(System.Memory{byte}, System.Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/>. /// </summary> public struct UpdateDataHeader { diff --git a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs index 5a0565dc..72438be0 100644 --- a/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs +++ b/src/Ryujinx.Audio/Renderer/Parameter/BehaviourErrorInfoOutStatus.cs @@ -8,7 +8,7 @@ namespace Ryujinx.Audio.Renderer.Parameter /// <summary> /// Output information for behaviour. /// </summary> - /// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, ReadOnlyMemory{byte})"/> processing.</remarks> + /// <remarks>This is used to report errors to the user during <see cref="Server.AudioRenderSystem.Update(Memory{byte}, Memory{byte}, System.Buffers.ReadOnlySequence{byte})"/> processing.</remarks> [StructLayout(LayoutKind.Sequential, Pack = 1)] public struct BehaviourErrorInfoOutStatus { diff --git a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs index 7bb8ae5b..9b56f5cb 100644 --- a/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs +++ b/src/Ryujinx.Audio/Renderer/Server/AudioRenderSystem.cs @@ -386,7 +386,7 @@ namespace Ryujinx.Audio.Renderer.Server } } - public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlyMemory<byte> input) + public ResultCode Update(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input) { lock (_lock) { @@ -419,14 +419,16 @@ namespace Ryujinx.Audio.Renderer.Server return result; } - result = stateUpdater.UpdateVoices(_voiceContext, _memoryPools); + PoolMapper poolMapper = new PoolMapper(_processHandle, _memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + + result = stateUpdater.UpdateVoices(_voiceContext, poolMapper); if (result != ResultCode.Success) { return result; } - result = stateUpdater.UpdateEffects(_effectContext, _isActive, _memoryPools); + result = stateUpdater.UpdateEffects(_effectContext, _isActive, poolMapper); if (result != ResultCode.Success) { @@ -450,7 +452,7 @@ namespace Ryujinx.Audio.Renderer.Server return result; } - result = stateUpdater.UpdateSinks(_sinkContext, _memoryPools); + result = stateUpdater.UpdateSinks(_sinkContext, poolMapper); if (result != ResultCode.Success) { diff --git a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs index 3297b5d9..099d8f56 100644 --- a/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/BehaviourContext.cs @@ -1,4 +1,5 @@ using System; +using System.Buffers; using System.Diagnostics; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; @@ -273,7 +274,7 @@ namespace Ryujinx.Audio.Renderer.Server } /// <summary> - /// Check if the audio renderer should trust the user destination count in <see cref="Splitter.SplitterState.Update(Splitter.SplitterContext, ref Parameter.SplitterInParameter, ReadOnlySpan{byte})"/>. + /// Check if the audio renderer should trust the user destination count in <see cref="Renderer.Server.Splitter.SplitterState.Update(Renderer.Server.Splitter.SplitterContext, Renderer.Parameter.SplitterInParameter, SequenceReader{byte})"/>. /// </summary> /// <returns>True if the audio renderer should trust the user destination count.</returns> public bool IsSplitterBugFixed() diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs index 57ca266f..74a9baff 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/AuxiliaryBufferEffect.cs @@ -33,21 +33,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs index a9716db2..77d9b5c2 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BaseEffect.cs @@ -81,7 +81,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// </summary> /// <param name="parameter">The user parameter.</param> /// <returns>Returns true if the <see cref="EffectType"/> sent by the user matches the internal <see cref="EffectType"/>.</returns> - public bool IsTypeValid<T>(ref T parameter) where T : unmanaged, IEffectInParameter + public bool IsTypeValid<T>(in T parameter) where T : unmanaged, IEffectInParameter { return parameter.Type == TargetEffectType; } @@ -98,7 +98,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// Update the internal common parameters from a user parameter. /// </summary> /// <param name="parameter">The user parameter.</param> - protected void UpdateParameterBase<T>(ref T parameter) where T : unmanaged, IEffectInParameter + protected void UpdateParameterBase<T>(in T parameter) where T : unmanaged, IEffectInParameter { MixId = parameter.MixId; ProcessingOrder = parameter.ProcessingOrder; @@ -139,7 +139,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// <summary> /// Initialize the given <paramref name="state"/> result state. /// </summary> - /// <param name="state">The state to initalize</param> + /// <param name="state">The state to initialize</param> public virtual void InitializeResultState(ref EffectResultState state) { } /// <summary> @@ -155,9 +155,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param> /// <param name="parameter">The user parameter.</param> /// <param name="mapper">The mapper to use.</param> - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); updateErrorInfo = new ErrorInfo(); } @@ -168,9 +168,9 @@ namespace Ryujinx.Audio.Renderer.Server.Effect /// <param name="updateErrorInfo">The possible <see cref="ErrorInfo"/> that was generated.</param> /// <param name="parameter">The user parameter.</param> /// <param name="mapper">The mapper to use.</param> - public virtual void Update(out ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public virtual void Update(out ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); updateErrorInfo = new ErrorInfo(); } diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs index b987f7c8..3b3e1021 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BiquadFilterEffect.cs @@ -35,21 +35,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BiquadFilter; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast<byte, BiquadFilterEffectParameter>(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs index d6cb9cfa..5d82b5ae 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/BufferMixEffect.cs @@ -19,21 +19,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect public override EffectType TargetEffectType => EffectType.BufferMix; - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast<byte, BufferMixParameter>(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs index 5be4b4ed..6917222f 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CaptureBufferEffect.cs @@ -32,21 +32,21 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return WorkBuffers[index].GetReference(true); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast<byte, AuxiliaryBufferParameter>(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs index 826c32cb..eff60e7d 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/CompressorEffect.cs @@ -39,17 +39,17 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { // Nintendo doesn't do anything here but we still require updateErrorInfo to be initialised. updateErrorInfo = new BehaviourParameter.ErrorInfo(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = MemoryMarshal.Cast<byte, CompressorParameter>(parameter.SpecificData)[0]; IsEnabled = parameter.IsEnabled; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs index 43cabb7d..9db1ce46 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/DelayEffect.cs @@ -37,19 +37,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref DelayParameter delayParameter = ref MemoryMarshal.Cast<byte, DelayParameter>(parameter.SpecificData)[0]; @@ -57,7 +57,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (delayParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.Status; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs index 3e2f7326..d9b3d566 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/LimiterEffect.cs @@ -39,25 +39,25 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref LimiterParameter limiterParameter = ref MemoryMarshal.Cast<byte, LimiterParameter>(parameter.SpecificData)[0]; updateErrorInfo = new BehaviourParameter.ErrorInfo(); - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); Parameter = limiterParameter; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs index f9d7f494..4b13cfec 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/Reverb3dEffect.cs @@ -36,19 +36,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref Reverb3dParameter reverbParameter = ref MemoryMarshal.Cast<byte, Reverb3dParameter>(parameter.SpecificData)[0]; @@ -56,7 +56,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (reverbParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.ParameterStatus; diff --git a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs index 6fdf8fc2..aa6e6744 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Effect/ReverbEffect.cs @@ -39,19 +39,19 @@ namespace Ryujinx.Audio.Renderer.Server.Effect return GetSingleBuffer(); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion1 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion1 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, ref EffectInParameterVersion2 parameter, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo updateErrorInfo, in EffectInParameterVersion2 parameter, PoolMapper mapper) { - Update(out updateErrorInfo, ref parameter, mapper); + Update(out updateErrorInfo, in parameter, mapper); } - public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + public void Update<T>(out BehaviourParameter.ErrorInfo updateErrorInfo, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref ReverbParameter reverbParameter = ref MemoryMarshal.Cast<byte, ReverbParameter>(parameter.SpecificData)[0]; @@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Effect if (reverbParameter.IsChannelCountMaxValid()) { - UpdateParameterBase(ref parameter); + UpdateParameterBase(in parameter); UsageState oldParameterStatus = Parameter.Status; diff --git a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs index 391b80f8..f67d0c12 100644 --- a/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs +++ b/src/Ryujinx.Audio/Renderer/Server/MemoryPool/PoolMapper.cs @@ -249,7 +249,7 @@ namespace Ryujinx.Audio.Renderer.Server.MemoryPool /// <param name="inParameter">Input user parameter.</param> /// <param name="outStatus">Output user parameter.</param> /// <returns>Returns the <see cref="UpdateResult"/> of the operations performed.</returns> - public UpdateResult Update(ref MemoryPoolState memoryPool, ref MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) + public UpdateResult Update(ref MemoryPoolState memoryPool, in MemoryPoolInParameter inParameter, ref MemoryPoolOutStatus outStatus) { MemoryPoolUserState inputState = inParameter.State; diff --git a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs index 88ae4483..b90574da 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Mix/MixState.cs @@ -195,7 +195,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// <param name="parameter">The input parameter of the mix.</param> /// <param name="splitterContext">The splitter context.</param> /// <returns>Return true, new connections were done on the adjacency matrix.</returns> - private bool UpdateConnection(EdgeMatrix edgeMatrix, ref MixParameter parameter, ref SplitterContext splitterContext) + private bool UpdateConnection(EdgeMatrix edgeMatrix, in MixParameter parameter, ref SplitterContext splitterContext) { bool hasNewConnections; @@ -259,7 +259,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix /// <param name="splitterContext">The splitter context.</param> /// <param name="behaviourContext">The behaviour context.</param> /// <returns>Return true if the mix was changed.</returns> - public bool Update(EdgeMatrix edgeMatrix, ref MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) + public bool Update(EdgeMatrix edgeMatrix, in MixParameter parameter, EffectContext effectContext, SplitterContext splitterContext, BehaviourContext behaviourContext) { bool isDirty; @@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Mix if (behaviourContext.IsSplitterSupported()) { - isDirty = UpdateConnection(edgeMatrix, ref parameter, ref splitterContext); + isDirty = UpdateConnection(edgeMatrix, in parameter, ref splitterContext); } else { diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs index d36c5e26..8c65e09b 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/BaseSink.cs @@ -59,7 +59,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// </summary> /// <param name="parameter">The user parameter.</param> /// <returns>Return true, if the <see cref="SinkType"/> sent by the user match the internal <see cref="SinkType"/>.</returns> - public bool IsTypeValid(ref SinkInParameter parameter) + public bool IsTypeValid(in SinkInParameter parameter) { return parameter.Type == TargetSinkType; } @@ -76,7 +76,7 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// Update the internal common parameters from user parameter. /// </summary> /// <param name="parameter">The user parameter.</param> - protected void UpdateStandardParameter(ref SinkInParameter parameter) + protected void UpdateStandardParameter(in SinkInParameter parameter) { if (IsUsed != parameter.IsUsed) { @@ -92,9 +92,9 @@ namespace Ryujinx.Audio.Renderer.Server.Sink /// <param name="parameter">The user parameter.</param> /// <param name="outStatus">The user output status.</param> /// <param name="mapper">The mapper to use.</param> - public virtual void Update(out ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public virtual void Update(out ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); errorInfo = new ErrorInfo(); } diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs index 09775798..f2751cf2 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/CircularBufferSink.cs @@ -44,18 +44,18 @@ namespace Ryujinx.Audio.Renderer.Server.Sink public override SinkType TargetSinkType => SinkType.CircularBuffer; - public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { errorInfo = new BehaviourParameter.ErrorInfo(); outStatus = new SinkOutStatus(); - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref CircularBufferParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, CircularBufferParameter>(parameter.SpecificData)[0]; if (parameter.IsUsed != IsUsed || ShouldSkip) { - UpdateStandardParameter(ref parameter); + UpdateStandardParameter(in parameter); if (parameter.IsUsed) { diff --git a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs index e03fe11d..afe2d4b1 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Sink/DeviceSink.cs @@ -49,15 +49,15 @@ namespace Ryujinx.Audio.Renderer.Server.Sink public override SinkType TargetSinkType => SinkType.Device; - public override void Update(out BehaviourParameter.ErrorInfo errorInfo, ref SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) + public override void Update(out BehaviourParameter.ErrorInfo errorInfo, in SinkInParameter parameter, ref SinkOutStatus outStatus, PoolMapper mapper) { - Debug.Assert(IsTypeValid(ref parameter)); + Debug.Assert(IsTypeValid(in parameter)); ref DeviceParameter inputDeviceParameter = ref MemoryMarshal.Cast<byte, DeviceParameter>(parameter.SpecificData)[0]; if (parameter.IsUsed != IsUsed) { - UpdateStandardParameter(ref parameter); + UpdateStandardParameter(in parameter); Parameter = inputDeviceParameter; } else diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs index e408692a..3efa783c 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterContext.cs @@ -2,10 +2,11 @@ using Ryujinx.Audio.Renderer.Common; using Ryujinx.Audio.Renderer.Parameter; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common; +using Ryujinx.Common.Extensions; using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; namespace Ryujinx.Audio.Renderer.Server.Splitter { @@ -25,7 +26,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter private Memory<SplitterDestination> _splitterDestinations; /// <summary> - /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>. + /// If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>. /// </summary> public bool IsBugFixed { get; private set; } @@ -110,7 +111,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> /// <param name="splitters">The <see cref="SplitterState"/> storage.</param> /// <param name="splitterDestinations">The <see cref="SplitterDestination"/> storage.</param> - /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, ref SplitterInParameter, ReadOnlySpan{byte})"/>.</param> + /// <param name="isBugFixed">If set to true, trust the user destination count in <see cref="SplitterState.Update(SplitterContext, in SplitterInParameter, ref SequenceReader{byte})"/>.</param> private void Setup(Memory<SplitterState> splitters, Memory<SplitterDestination> splitterDestinations, bool isBugFixed) { _splitters = splitters; @@ -148,11 +149,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> /// <param name="inputHeader">The splitter header.</param> /// <param name="input">The raw data after the splitter header.</param> - private void UpdateState(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input) + private void UpdateState(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input) { for (int i = 0; i < inputHeader.SplitterCount; i++) { - SplitterInParameter parameter = MemoryMarshal.Read<SplitterInParameter>(input); + ref readonly SplitterInParameter parameter = ref input.GetRefOrRefToCopy<SplitterInParameter>(out _); Debug.Assert(parameter.IsMagicValid()); @@ -162,10 +163,16 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter { ref SplitterState splitter = ref GetState(parameter.Id); - splitter.Update(this, ref parameter, input[Unsafe.SizeOf<SplitterInParameter>()..]); + splitter.Update(this, in parameter, ref input); } - input = input[(0x1C + parameter.DestinationCount * 4)..]; + // NOTE: there are 12 bytes of unused/unknown data after the destination IDs array. + input.Advance(0xC); + } + else + { + input.Rewind(Unsafe.SizeOf<SplitterInParameter>()); + break; } } } @@ -175,11 +182,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// </summary> /// <param name="inputHeader">The splitter header.</param> /// <param name="input">The raw data after the splitter header.</param> - private void UpdateData(scoped ref SplitterInParameterHeader inputHeader, ref ReadOnlySpan<byte> input) + private void UpdateData(in SplitterInParameterHeader inputHeader, ref SequenceReader<byte> input) { for (int i = 0; i < inputHeader.SplitterDestinationCount; i++) { - SplitterDestinationInParameter parameter = MemoryMarshal.Read<SplitterDestinationInParameter>(input); + ref readonly SplitterDestinationInParameter parameter = ref input.GetRefOrRefToCopy<SplitterDestinationInParameter>(out _); Debug.Assert(parameter.IsMagicValid()); @@ -191,8 +198,11 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter destination.Update(parameter); } - - input = input[Unsafe.SizeOf<SplitterDestinationInParameter>()..]; + } + else + { + input.Rewind(Unsafe.SizeOf<SplitterDestinationInParameter>()); + break; } } } @@ -201,36 +211,33 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// Update splitter from user parameters. /// </summary> /// <param name="input">The input raw user data.</param> - /// <param name="consumedSize">The total consumed size.</param> /// <returns>Return true if the update was successful.</returns> - public bool Update(ReadOnlySpan<byte> input, out int consumedSize) + public bool Update(ref SequenceReader<byte> input) { if (_splitterDestinations.IsEmpty || _splitters.IsEmpty) { - consumedSize = 0; - return true; } - int originalSize = input.Length; - - SplitterInParameterHeader header = SpanIOHelper.Read<SplitterInParameterHeader>(ref input); + ref readonly SplitterInParameterHeader header = ref input.GetRefOrRefToCopy<SplitterInParameterHeader>(out _); if (header.IsMagicValid()) { ClearAllNewConnectionFlag(); - UpdateState(ref header, ref input); - UpdateData(ref header, ref input); + UpdateState(in header, ref input); + UpdateData(in header, ref input); - consumedSize = BitUtils.AlignUp(originalSize - input.Length, 0x10); + input.SetConsumed(BitUtils.AlignUp(input.Consumed, 0x10)); return true; } + else + { + input.Rewind(Unsafe.SizeOf<SplitterInParameterHeader>()); - consumedSize = 0; - - return false; + return false; + } } /// <summary> diff --git a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs index e08ee9ea..109c81b2 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Splitter/SplitterState.cs @@ -1,4 +1,5 @@ using Ryujinx.Audio.Renderer.Parameter; +using Ryujinx.Common.Extensions; using System; using System.Buffers; using System.Diagnostics; @@ -122,7 +123,7 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter /// <param name="context">The splitter context.</param> /// <param name="parameter">The user parameter.</param> /// <param name="input">The raw input data after the <paramref name="parameter"/>.</param> - public void Update(SplitterContext context, ref SplitterInParameter parameter, ReadOnlySpan<byte> input) + public void Update(SplitterContext context, in SplitterInParameter parameter, ref SequenceReader<byte> input) { ClearLinks(); @@ -139,9 +140,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter if (destinationCount > 0) { - ReadOnlySpan<int> destinationIds = MemoryMarshal.Cast<byte, int>(input); + input.ReadLittleEndian(out int destinationId); - Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationIds[0]); + Memory<SplitterDestination> destination = context.GetDestinationMemory(destinationId); SetDestination(ref destination.Span[0]); @@ -149,7 +150,9 @@ namespace Ryujinx.Audio.Renderer.Server.Splitter for (int i = 1; i < destinationCount; i++) { - Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationIds[i]); + input.ReadLittleEndian(out destinationId); + + Memory<SplitterDestination> nextDestination = context.GetDestinationMemory(destinationId); destination.Span[0].Link(ref nextDestination.Span[0]); destination = nextDestination; diff --git a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs index 22eebc7c..f8d87f2d 100644 --- a/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs +++ b/src/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -9,41 +9,40 @@ using Ryujinx.Audio.Renderer.Server.Sink; using Ryujinx.Audio.Renderer.Server.Splitter; using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Utils; +using Ryujinx.Common.Extensions; using Ryujinx.Common.Logging; using System; using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; using static Ryujinx.Audio.Renderer.Common.BehaviourParameter; namespace Ryujinx.Audio.Renderer.Server { - public class StateUpdater + public ref struct StateUpdater { - private readonly ReadOnlyMemory<byte> _inputOrigin; + private SequenceReader<byte> _inputReader; + private readonly ReadOnlyMemory<byte> _outputOrigin; - private ReadOnlyMemory<byte> _input; private Memory<byte> _output; private readonly uint _processHandle; private BehaviourContext _behaviourContext; - private UpdateDataHeader _inputHeader; + private readonly ref readonly UpdateDataHeader _inputHeader; private readonly Memory<UpdateDataHeader> _outputHeader; - private ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; + private readonly ref UpdateDataHeader OutputHeader => ref _outputHeader.Span[0]; - public StateUpdater(ReadOnlyMemory<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext) + public StateUpdater(ReadOnlySequence<byte> input, Memory<byte> output, uint processHandle, BehaviourContext behaviourContext) { - _input = input; - _inputOrigin = _input; + _inputReader = new SequenceReader<byte>(input); _output = output; _outputOrigin = _output; _processHandle = processHandle; _behaviourContext = behaviourContext; - _inputHeader = SpanIOHelper.Read<UpdateDataHeader>(ref _input); + _inputHeader = ref _inputReader.GetRefOrRefToCopy<UpdateDataHeader>(out _); _outputHeader = SpanMemoryManager<UpdateDataHeader>.Cast(_output[..Unsafe.SizeOf<UpdateDataHeader>()]); OutputHeader.Initialize(_behaviourContext.UserRevision); @@ -52,7 +51,7 @@ namespace Ryujinx.Audio.Renderer.Server public ResultCode UpdateBehaviourContext() { - BehaviourParameter parameter = SpanIOHelper.Read<BehaviourParameter>(ref _input); + ref readonly BehaviourParameter parameter = ref _inputReader.GetRefOrRefToCopy<BehaviourParameter>(out _); if (!BehaviourContext.CheckValidRevision(parameter.UserRevision) || parameter.UserRevision != _behaviourContext.UserRevision) { @@ -81,11 +80,11 @@ namespace Ryujinx.Audio.Renderer.Server foreach (ref MemoryPoolState memoryPool in memoryPools) { - MemoryPoolInParameter parameter = SpanIOHelper.Read<MemoryPoolInParameter>(ref _input); + ref readonly MemoryPoolInParameter parameter = ref _inputReader.GetRefOrRefToCopy<MemoryPoolInParameter>(out _); ref MemoryPoolOutStatus outStatus = ref SpanIOHelper.GetWriteRef<MemoryPoolOutStatus>(ref _output)[0]; - PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, ref parameter, ref outStatus); + PoolMapper.UpdateResult updateResult = mapper.Update(ref memoryPool, in parameter, ref outStatus); if (updateResult != PoolMapper.UpdateResult.Success && updateResult != PoolMapper.UpdateResult.MapError && @@ -115,7 +114,7 @@ namespace Ryujinx.Audio.Renderer.Server for (int i = 0; i < context.GetCount(); i++) { - VoiceChannelResourceInParameter parameter = SpanIOHelper.Read<VoiceChannelResourceInParameter>(ref _input); + ref readonly VoiceChannelResourceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceChannelResourceInParameter>(out _); ref VoiceChannelResource resource = ref context.GetChannelResource(i); @@ -127,7 +126,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - public ResultCode UpdateVoices(VoiceContext context, Memory<MemoryPoolState> memoryPools) + public ResultCode UpdateVoices(VoiceContext context, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf<VoiceInParameter>() != _inputHeader.VoicesSize) { @@ -136,11 +135,7 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan<VoiceInParameter> parameters = MemoryMarshal.Cast<byte, VoiceInParameter>(_input[..(int)_inputHeader.VoicesSize].Span); - - _input = _input[(int)_inputHeader.VoicesSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; // First make everything not in use. for (int i = 0; i < context.GetCount(); i++) @@ -157,7 +152,7 @@ namespace Ryujinx.Audio.Renderer.Server // Start processing for (int i = 0; i < context.GetCount(); i++) { - VoiceInParameter parameter = parameters[i]; + ref readonly VoiceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<VoiceInParameter>(out _); voiceUpdateStates.Fill(Memory<VoiceUpdateState>.Empty); @@ -181,14 +176,14 @@ namespace Ryujinx.Audio.Renderer.Server currentVoiceState.Initialize(); } - currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, ref parameter, ref mapper, ref _behaviourContext); + currentVoiceState.UpdateParameters(out ErrorInfo updateParameterError, in parameter, mapper, ref _behaviourContext); if (updateParameterError.ErrorCode != ResultCode.Success) { _behaviourContext.AppendError(ref updateParameterError); } - currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, ref parameter, voiceUpdateStates, ref mapper, ref _behaviourContext); + currentVoiceState.UpdateWaveBuffers(out ErrorInfo[] waveBufferUpdateErrorInfos, in parameter, voiceUpdateStates, mapper, ref _behaviourContext); foreach (ref ErrorInfo errorInfo in waveBufferUpdateErrorInfos.AsSpan()) { @@ -198,7 +193,7 @@ namespace Ryujinx.Audio.Renderer.Server } } - currentVoiceState.WriteOutStatus(ref outStatus, ref parameter, voiceUpdateStates); + currentVoiceState.WriteOutStatus(ref outStatus, in parameter, voiceUpdateStates); } } @@ -211,10 +206,12 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.VoicesSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.VoicesSize); + return ResultCode.Success; } - private static void ResetEffect<T>(ref BaseEffect effect, ref T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter + private static void ResetEffect<T>(ref BaseEffect effect, in T parameter, PoolMapper mapper) where T : unmanaged, IEffectInParameter { effect.ForceUnmapBuffers(mapper); @@ -234,17 +231,17 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools) + public ResultCode UpdateEffects(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (_behaviourContext.IsEffectInfoVersion2Supported()) { - return UpdateEffectsVersion2(context, isAudioRendererActive, memoryPools); + return UpdateEffectsVersion2(context, isAudioRendererActive, mapper); } - return UpdateEffectsVersion1(context, isAudioRendererActive, memoryPools); + return UpdateEffectsVersion1(context, isAudioRendererActive, mapper); } - public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools) + public ResultCode UpdateEffectsVersion2(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion2>() != _inputHeader.EffectsSize) { @@ -253,26 +250,22 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan<EffectInParameterVersion2> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion2>(_input[..(int)_inputHeader.EffectsSize].Span); - - _input = _input[(int)_inputHeader.EffectsSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - EffectInParameterVersion2 parameter = parameters[i]; + ref readonly EffectInParameterVersion2 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion2>(out _); ref EffectOutStatusVersion2 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion2>(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); - if (!effect.IsTypeValid(ref parameter)) + if (!effect.IsTypeValid(in parameter)) { - ResetEffect(ref effect, ref parameter, mapper); + ResetEffect(ref effect, in parameter, mapper); } - effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -297,10 +290,12 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + return ResultCode.Success; } - public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, Memory<MemoryPoolState> memoryPools) + public ResultCode UpdateEffectsVersion1(EffectContext context, bool isAudioRendererActive, PoolMapper mapper) { if (context.GetCount() * Unsafe.SizeOf<EffectInParameterVersion1>() != _inputHeader.EffectsSize) { @@ -309,26 +304,22 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan<EffectInParameterVersion1> parameters = MemoryMarshal.Cast<byte, EffectInParameterVersion1>(_input[..(int)_inputHeader.EffectsSize].Span); - - _input = _input[(int)_inputHeader.EffectsSize..]; - - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - EffectInParameterVersion1 parameter = parameters[i]; + ref readonly EffectInParameterVersion1 parameter = ref _inputReader.GetRefOrRefToCopy<EffectInParameterVersion1>(out _); ref EffectOutStatusVersion1 outStatus = ref SpanIOHelper.GetWriteRef<EffectOutStatusVersion1>(ref _output)[0]; ref BaseEffect effect = ref context.GetEffect(i); - if (!effect.IsTypeValid(ref parameter)) + if (!effect.IsTypeValid(in parameter)) { - ResetEffect(ref effect, ref parameter, mapper); + ResetEffect(ref effect, in parameter, mapper); } - effect.Update(out ErrorInfo updateErrorInfo, ref parameter, mapper); + effect.Update(out ErrorInfo updateErrorInfo, in parameter, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -345,38 +336,40 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.EffectsSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.EffectsSize); + return ResultCode.Success; } public ResultCode UpdateSplitter(SplitterContext context) { - if (context.Update(_input.Span, out int consumedSize)) + if (context.Update(ref _inputReader)) { - _input = _input[consumedSize..]; - return ResultCode.Success; } return ResultCode.InvalidUpdateInfo; } - private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, ReadOnlySpan<MixParameter> parameters) + private static bool CheckMixParametersValidity(MixContext mixContext, uint mixBufferCount, uint inputMixCount, SequenceReader<byte> parameters) { uint maxMixStateCount = mixContext.GetCount(); uint totalRequiredMixBufferCount = 0; for (int i = 0; i < inputMixCount; i++) { - if (parameters[i].IsUsed) + ref readonly MixParameter parameter = ref parameters.GetRefOrRefToCopy<MixParameter>(out _); + + if (parameter.IsUsed) { - if (parameters[i].DestinationMixId != Constants.UnusedMixId && - parameters[i].DestinationMixId > maxMixStateCount && - parameters[i].MixId != Constants.FinalMixId) + if (parameter.DestinationMixId != Constants.UnusedMixId && + parameter.DestinationMixId > maxMixStateCount && + parameter.MixId != Constants.FinalMixId) { return true; } - totalRequiredMixBufferCount += parameters[i].BufferCount; + totalRequiredMixBufferCount += parameter.BufferCount; } } @@ -391,7 +384,7 @@ namespace Ryujinx.Audio.Renderer.Server if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) { - MixInParameterDirtyOnlyUpdate parameter = MemoryMarshal.Cast<byte, MixInParameterDirtyOnlyUpdate>(_input.Span)[0]; + ref readonly MixInParameterDirtyOnlyUpdate parameter = ref _inputReader.GetRefOrRefToCopy<MixInParameterDirtyOnlyUpdate>(out _); mixCount = parameter.MixCount; @@ -411,25 +404,20 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.InvalidUpdateInfo; } - if (_behaviourContext.IsMixInParameterDirtyOnlyUpdateSupported()) - { - _input = _input[Unsafe.SizeOf<MixInParameterDirtyOnlyUpdate>()..]; - } - - ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span[..(int)inputMixSize]); + long initialInputConsumed = _inputReader.Consumed; - _input = _input[(int)inputMixSize..]; + int parameterCount = (int)inputMixSize / Unsafe.SizeOf<MixParameter>(); - if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, parameters)) + if (CheckMixParametersValidity(mixContext, mixBufferCount, mixCount, _inputReader)) { return ResultCode.InvalidUpdateInfo; } bool isMixContextDirty = false; - for (int i = 0; i < parameters.Length; i++) + for (int i = 0; i < parameterCount; i++) { - MixParameter parameter = parameters[i]; + ref readonly MixParameter parameter = ref _inputReader.GetRefOrRefToCopy<MixParameter>(out _); int mixId = i; @@ -454,7 +442,7 @@ namespace Ryujinx.Audio.Renderer.Server if (mix.IsUsed) { - isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, ref parameter, effectContext, splitterContext, _behaviourContext); + isMixContextDirty |= mix.Update(mixContext.EdgeMatrix, in parameter, effectContext, splitterContext, _behaviourContext); } } @@ -473,10 +461,12 @@ namespace Ryujinx.Audio.Renderer.Server } } + _inputReader.SetConsumed(initialInputConsumed + inputMixSize); + return ResultCode.Success; } - private static void ResetSink(ref BaseSink sink, ref SinkInParameter parameter) + private static void ResetSink(ref BaseSink sink, in SinkInParameter parameter) { sink.CleanUp(); @@ -489,10 +479,8 @@ namespace Ryujinx.Audio.Renderer.Server }; } - public ResultCode UpdateSinks(SinkContext context, Memory<MemoryPoolState> memoryPools) + public ResultCode UpdateSinks(SinkContext context, PoolMapper mapper) { - PoolMapper mapper = new(_processHandle, memoryPools, _behaviourContext.IsMemoryPoolForceMappingEnabled()); - if (context.GetCount() * Unsafe.SizeOf<SinkInParameter>() != _inputHeader.SinksSize) { return ResultCode.InvalidUpdateInfo; @@ -500,22 +488,20 @@ namespace Ryujinx.Audio.Renderer.Server int initialOutputSize = _output.Length; - ReadOnlySpan<SinkInParameter> parameters = MemoryMarshal.Cast<byte, SinkInParameter>(_input[..(int)_inputHeader.SinksSize].Span); - - _input = _input[(int)_inputHeader.SinksSize..]; + long initialInputConsumed = _inputReader.Consumed; for (int i = 0; i < context.GetCount(); i++) { - SinkInParameter parameter = parameters[i]; + ref readonly SinkInParameter parameter = ref _inputReader.GetRefOrRefToCopy<SinkInParameter>(out _); ref SinkOutStatus outStatus = ref SpanIOHelper.GetWriteRef<SinkOutStatus>(ref _output)[0]; ref BaseSink sink = ref context.GetSink(i); - if (!sink.IsTypeValid(ref parameter)) + if (!sink.IsTypeValid(in parameter)) { - ResetSink(ref sink, ref parameter); + ResetSink(ref sink, in parameter); } - sink.Update(out ErrorInfo updateErrorInfo, ref parameter, ref outStatus, mapper); + sink.Update(out ErrorInfo updateErrorInfo, in parameter, ref outStatus, mapper); if (updateErrorInfo.ErrorCode != ResultCode.Success) { @@ -530,6 +516,8 @@ namespace Ryujinx.Audio.Renderer.Server Debug.Assert((initialOutputSize - currentOutputSize) == OutputHeader.SinksSize); + _inputReader.SetConsumed(initialInputConsumed + _inputHeader.SinksSize); + return ResultCode.Success; } @@ -540,7 +528,7 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.InvalidUpdateInfo; } - PerformanceInParameter parameter = SpanIOHelper.Read<PerformanceInParameter>(ref _input); + ref readonly PerformanceInParameter parameter = ref _inputReader.GetRefOrRefToCopy<PerformanceInParameter>(out _); ref PerformanceOutStatus outStatus = ref SpanIOHelper.GetWriteRef<PerformanceOutStatus>(ref _output)[0]; @@ -585,9 +573,9 @@ namespace Ryujinx.Audio.Renderer.Server return ResultCode.Success; } - public ResultCode CheckConsumedSize() + public readonly ResultCode CheckConsumedSize() { - int consumedInputSize = _inputOrigin.Length - _input.Length; + long consumedInputSize = _inputReader.Consumed; int consumedOutputSize = _outputOrigin.Length - _output.Length; if (consumedInputSize != _inputHeader.TotalSize) diff --git a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs index 225f7d31..040c70e6 100644 --- a/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs +++ b/src/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -254,7 +254,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// </summary> /// <param name="parameter">The user parameter.</param> /// <returns>Return true, if the server voice information needs to be updated.</returns> - private readonly bool ShouldUpdateParameters(ref VoiceInParameter parameter) + private readonly bool ShouldUpdateParameters(in VoiceInParameter parameter) { if (DataSourceStateAddressInfo.CpuAddress == parameter.DataSourceStateAddress) { @@ -273,7 +273,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// <param name="parameter">The user parameter.</param> /// <param name="poolMapper">The mapper to use.</param> /// <param name="behaviourContext">The behaviour context.</param> - public void UpdateParameters(out ErrorInfo outErrorInfo, ref VoiceInParameter parameter, ref PoolMapper poolMapper, ref BehaviourContext behaviourContext) + public void UpdateParameters(out ErrorInfo outErrorInfo, in VoiceInParameter parameter, PoolMapper poolMapper, ref BehaviourContext behaviourContext) { InUse = parameter.InUse; Id = parameter.Id; @@ -326,7 +326,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice VoiceDropFlag = false; } - if (ShouldUpdateParameters(ref parameter)) + if (ShouldUpdateParameters(in parameter)) { DataSourceStateUnmapped = !poolMapper.TryAttachBuffer(out outErrorInfo, ref DataSourceStateAddressInfo, parameter.DataSourceStateAddress, parameter.DataSourceStateSize); } @@ -380,7 +380,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// <param name="outStatus">The given user output.</param> /// <param name="parameter">The user parameter.</param> /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> - public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates) + public void WriteOutStatus(ref VoiceOutStatus outStatus, in VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates) { #if DEBUG // Sanity check in debug mode of the internal state @@ -426,7 +426,12 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// <param name="voiceUpdateStates">The voice states associated to the <see cref="VoiceState"/>.</param> /// <param name="mapper">The mapper to use.</param> /// <param name="behaviourContext">The behaviour context.</param> - public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + public void UpdateWaveBuffers( + out ErrorInfo[] errorInfos, + in VoiceInParameter parameter, + ReadOnlySpan<Memory<VoiceUpdateState>> voiceUpdateStates, + PoolMapper mapper, + ref BehaviourContext behaviourContext) { errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; @@ -444,7 +449,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice for (int i = 0; i < Constants.VoiceWaveBufferCount; i++) { - UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], ref mapper, ref behaviourContext); + UpdateWaveBuffer(errorInfos.AsSpan(i * 2, 2), ref WaveBuffers[i], ref parameter.WaveBuffers[i], parameter.SampleFormat, voiceUpdateState.IsWaveBufferValid[i], mapper, ref behaviourContext); } } @@ -458,7 +463,14 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// <param name="isValid">If set to true, the server side wavebuffer is considered valid.</param> /// <param name="mapper">The mapper to use.</param> /// <param name="behaviourContext">The behaviour context.</param> - private void UpdateWaveBuffer(Span<ErrorInfo> errorInfos, ref WaveBuffer waveBuffer, ref WaveBufferInternal inputWaveBuffer, SampleFormat sampleFormat, bool isValid, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + private void UpdateWaveBuffer( + Span<ErrorInfo> errorInfos, + ref WaveBuffer waveBuffer, + ref WaveBufferInternal inputWaveBuffer, + SampleFormat sampleFormat, + bool isValid, + PoolMapper mapper, + ref BehaviourContext behaviourContext) { if (!isValid && waveBuffer.IsSendToAudioProcessor && waveBuffer.BufferAddressInfo.CpuAddress != 0) { 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; + } + } +} diff --git a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs index 6dffcd29..0acb57be 100644 --- a/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs +++ b/src/Ryujinx.Cpu/Jit/MemoryManagerHostTracked.cs @@ -1,10 +1,12 @@ using ARMeilleure.Memory; +using Ryujinx.Common.Memory; using Ryujinx.Cpu.Jit.HostTracked; using Ryujinx.Cpu.Signal; using Ryujinx.Memory; using Ryujinx.Memory.Range; using Ryujinx.Memory.Tracking; using System; +using System.Buffers; using System.Collections.Generic; using System.Linq; using System.Runtime.CompilerServices; @@ -237,11 +239,11 @@ namespace Ryujinx.Cpu.Jit } else { - Memory<byte> memory = new byte[size]; + IMemoryOwner<byte> memoryOwner = ByteMemoryPool.Rent(size); - Read(va, memory.Span); + Read(va, memoryOwner.Memory.Span); - return new WritableRegion(this, va, memory); + return new WritableRegion(this, va, memoryOwner); } } diff --git a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs index 4c8d441b..d1be8298 100644 --- a/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs +++ b/src/Ryujinx.Horizon.Generators/Hipc/HipcGenerator.cs @@ -17,6 +17,8 @@ namespace Ryujinx.Horizon.Generators.Hipc private const string ResponseVariableName = "response"; private const string OutRawDataVariableName = "outRawData"; + private const string TypeSystemBuffersReadOnlySequence = "System.Buffers.ReadOnlySequence"; + private const string TypeSystemMemory = "System.Memory"; private const string TypeSystemReadOnlySpan = "System.ReadOnlySpan"; private const string TypeSystemSpan = "System.Span"; private const string TypeStructLayoutAttribute = "System.Runtime.InteropServices.StructLayoutAttribute"; @@ -329,7 +331,15 @@ namespace Ryujinx.Horizon.Generators.Hipc value = $"{InObjectsVariableName}[{inObjectIndex++}]"; break; case CommandArgType.Buffer: - if (IsReadOnlySpan(compilation, parameter)) + if (IsMemory(compilation, parameter)) + { + value = $"CommandSerialization.GetWritableRegion(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySequence(compilation, parameter)) + { + value = $"CommandSerialization.GetReadOnlySequence(processor.GetBufferRange({index}))"; + } + else if (IsReadOnlySpan(compilation, parameter)) { string spanGenericTypeName = GetCanonicalTypeNameOfGenericArgument(compilation, parameter.Type, 0); value = GenerateSpanCast(spanGenericTypeName, $"CommandSerialization.GetReadOnlySpan(processor.GetBufferRange({index}))"); @@ -346,7 +356,13 @@ namespace Ryujinx.Horizon.Generators.Hipc break; } - if (IsSpan(compilation, parameter)) + if (IsMemory(compilation, parameter)) + { + generator.AppendLine($"using var {argName} = {value};"); + + argName = $"{argName}.Memory"; + } + else if (IsSpan(compilation, parameter)) { generator.AppendLine($"using var {argName} = {value};"); @@ -637,7 +653,9 @@ namespace Ryujinx.Horizon.Generators.Hipc private static bool IsValidTypeForBuffer(Compilation compilation, ParameterSyntax parameter) { - return IsReadOnlySpan(compilation, parameter) || + return IsMemory(compilation, parameter) || + IsReadOnlySequence(compilation, parameter) || + IsReadOnlySpan(compilation, parameter) || IsSpan(compilation, parameter) || IsUnmanagedType(compilation, parameter.Type); } @@ -649,6 +667,16 @@ namespace Ryujinx.Horizon.Generators.Hipc return typeInfo.Type.IsUnmanagedType; } + private static bool IsMemory(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemMemory; + } + + private static bool IsReadOnlySequence(Compilation compilation, ParameterSyntax parameter) + { + return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemBuffersReadOnlySequence; + } + private static bool IsReadOnlySpan(Compilation compilation, ParameterSyntax parameter) { return GetCanonicalTypeName(compilation, parameter.Type) == TypeSystemReadOnlySpan; diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs index 776df641..54de0721 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/AudioRenderer.cs @@ -57,23 +57,11 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [CmifCommand(4)] public Result RequestUpdate( - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> output, - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Span<byte> performanceOutput, - [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySpan<byte> input) + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory<byte> output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.MapAlias)] Memory<byte> performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.MapAlias)] ReadOnlySequence<byte> input) { - using IMemoryOwner<byte> outputOwner = ByteMemoryPool.Rent(output.Length); - using IMemoryOwner<byte> performanceOutputOwner = ByteMemoryPool.Rent(performanceOutput.Length); - - Memory<byte> outputMemory = outputOwner.Memory; - Memory<byte> performanceOutputMemory = performanceOutputOwner.Memory; - - using MemoryHandle outputHandle = outputMemory.Pin(); - using MemoryHandle performanceOutputHandle = performanceOutputMemory.Pin(); - - Result result = new Result((int)_renderSystem.Update(outputMemory, performanceOutputMemory, input.ToArray())); - - outputMemory.Span.CopyTo(output); - performanceOutputMemory.Span.CopyTo(performanceOutput); + Result result = new Result((int)_renderSystem.Update(output, performanceOutput, input)); return result; } @@ -127,9 +115,9 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail [CmifCommand(10)] // 3.0.0+ public Result RequestUpdateAuto( - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> output, - [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Span<byte> performanceOutput, - [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySpan<byte> input) + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory<byte> output, + [Buffer(HipcBufferFlags.Out | HipcBufferFlags.AutoSelect)] Memory<byte> performanceOutput, + [Buffer(HipcBufferFlags.In | HipcBufferFlags.AutoSelect)] ReadOnlySequence<byte> input) { return RequestUpdate(output, performanceOutput, input); } diff --git a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs index e4ca2e8e..b766bd73 100644 --- a/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs +++ b/src/Ryujinx.Horizon/Sdk/Audio/Detail/IAudioRenderer.cs @@ -1,6 +1,7 @@ using Ryujinx.Horizon.Common; using Ryujinx.Horizon.Sdk.Sf; using System; +using System.Buffers; namespace Ryujinx.Horizon.Sdk.Audio.Detail { @@ -10,13 +11,13 @@ namespace Ryujinx.Horizon.Sdk.Audio.Detail Result GetSampleCount(out int sampleCount); Result GetMixBufferCount(out int mixBufferCount); Result GetState(out int state); - Result RequestUpdate(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input); + Result RequestUpdate(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input); Result Start(); Result Stop(); Result QuerySystemEvent(out int eventHandle); Result SetRenderingTimeLimit(int percent); Result GetRenderingTimeLimit(out int percent); - Result RequestUpdateAuto(Span<byte> output, Span<byte> performanceOutput, ReadOnlySpan<byte> input); + Result RequestUpdateAuto(Memory<byte> output, Memory<byte> performanceOutput, ReadOnlySequence<byte> input); Result ExecuteAudioRendererRendering(); Result SetVoiceDropParameter(float voiceDropParameter); Result GetVoiceDropParameter(out float voiceDropParameter); diff --git a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs index 038135ac..7f528464 100644 --- a/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs +++ b/src/Ryujinx.Horizon/Sdk/Sf/CommandSerialization.cs @@ -2,6 +2,7 @@ using Ryujinx.Horizon.Sdk.Sf.Cmif; using Ryujinx.Horizon.Sdk.Sf.Hipc; using Ryujinx.Memory; using System; +using System.Buffers; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -9,6 +10,11 @@ namespace Ryujinx.Horizon.Sdk.Sf { static class CommandSerialization { + public static ReadOnlySequence<byte> GetReadOnlySequence(PointerAndSize bufferRange) + { + return HorizonStatic.AddressSpace.GetReadOnlySequence(bufferRange.Address, checked((int)bufferRange.Size)); + } + public static ReadOnlySpan<byte> GetReadOnlySpan(PointerAndSize bufferRange) { return HorizonStatic.AddressSpace.GetSpan(bufferRange.Address, checked((int)bufferRange.Size)); diff --git a/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs new file mode 100644 index 00000000..c0127530 --- /dev/null +++ b/src/Ryujinx.Tests/Common/Extensions/SequenceReaderExtensionsTests.cs @@ -0,0 +1,359 @@ +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)); + } + } +} |
