mirror of
				https://github.com/ryujinx-mirror/ryujinx.git
				synced 2025-11-04 08:18:58 -06:00 
			
		
		
		
	Audio rendering: reduce memory allocations (#6604)
* - WritableRegion: enable wrapping IMemoryOwner<byte> - IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous. - IVirtualMemoryManager: add GetReadOnlySequence() and impls - ByteMemoryPool: add new method RentCopy() - ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl * - BytesReadOnlySequenceSegment: move from Ryujinx.Common.Memory to Ryujinx.Memory - BytesReadOnlySequenceSegment: add IsContiguousWith() and Replace() methods - VirtualMemoryManagerBase: - remove generic type parameters, instead use ulong for virtual addresses and nuint for host/physical addresses - implement IWritableBlock - add virtual GetReadOnlySequence() with coalescing of contiguous segments - add virtual GetSpan() - add virtual GetWritableRegion() - add abstract IsMapped() - add virtual MapForeign(ulong, nuint, ulong) - add virtual Read<T>() - add virtual Read(ulong, Span<byte>) - add virtual ReadTracked<T>() - add virtual SignalMemoryTracking() - add virtual Write() - add virtual Write<T>() - add virtual WriteUntracked() - add virtual WriteWithRedundancyCheck() - VirtualMemoryManagerRefCountedBase: remove generic type parameters - AddressSpaceManager: remove redundant methods, add required overrides - HvMemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - MemoryManager: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - MemoryManagerHostMapped: remove redundant methods, add required overrides, add overrides for _invalidAccessHandler handling - NativeMemoryManager: add get properties for Pointer and Length - throughout: removed invalid <inheritdoc/> comments * - WritableRegion: enable wrapping IMemoryOwner<byte> - IVirtualMemoryManager impls of GetWritableRegion() use pooled memory when region is non-contiguous. - IVirtualMemoryManager: add GetReadOnlySequence() and impls - ByteMemoryPool: add new method RentCopy() - ByteMemoryPool: make class static, remove ctor and singleton field from earlier impl * add PagedMemoryRange enumerator types, use them in IVirtualMemoryManager implementations to consolidate page-handling logic and add a new capability - the coalescing of pages for consolidating memory copies and segmentation. * new: more tests for PagedMemoryRangeCoalescingEnumerator showing coalescing of contiguous segments * make some struct properties readonly * put braces around `foreach` bodies * encourage inlining of some PagedMemoryRange*Enumerator members * DynamicRingBuffer: - use ByteMemoryPool - make some methods return without locking when size/count argument = 0 - make generic Read<T>()/Write<T>() non-generic because its only usage is as T = byte - change Read(byte[]...) to Read(Span<byte>...) - change Write(byte[]...) to Write(Span<byte>...) * change IAudioRenderer.RequestUpdate() to take a ReadOnlySequence<byte>, enabling zero-copy audio rendering * HipcGenerator: support ReadOnlySequence<byte> as IPC method parameter * change IAudioRenderer/AudioRenderer RequestUpdate* methods to take input as ReadOnlySequence<byte> * MemoryManagerHostTracked: use rented memory when contiguous in `GetWritableRegion()` * rebase cleanup * dotnet format fixes * format and comment fixes * format long parameter list - take 2 * - add support to HipcGenerator for buffers of type `Memory<byte>` - change `AudioRenderer` `RequestUpdate()` and `RequestUpdateAuto()` to use Memory<byte> for output buffers, removing another memory block allocation/copy * SplitterContext `UpdateState()` and `UpdateData()` smooth out advance/rewind logic, only rewind if magic is invalid * DynamicRingBuffer.Write(): change Span<byte> to ReadOnlySpan<byte>
This commit is contained in:
		@@ -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)..]);
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
    {
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -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()
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
@@ -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();
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
@@ -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>
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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>()..];
 | 
			
		||||
            }
 | 
			
		||||
            long initialInputConsumed = _inputReader.Consumed;
 | 
			
		||||
 | 
			
		||||
            ReadOnlySpan<MixParameter> parameters = MemoryMarshal.Cast<byte, MixParameter>(_input.Span[..(int)inputMixSize]);
 | 
			
		||||
            int parameterCount = (int)inputMixSize / Unsafe.SizeOf<MixParameter>();
 | 
			
		||||
 | 
			
		||||
            _input = _input[(int)inputMixSize..];
 | 
			
		||||
 | 
			
		||||
            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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
            {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										181
									
								
								src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										181
									
								
								src/Ryujinx.Common/Extensions/SequenceReaderExtensions.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
        }
 | 
			
		||||
 
 | 
			
		||||
@@ -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);
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
 
 | 
			
		||||
@@ -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));
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user