diff --git a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs index 0446cd8c6..5cf539c6d 100644 --- a/Ryujinx.Audio/Renderer/Server/StateUpdater.cs +++ b/Ryujinx.Audio/Renderer/Server/StateUpdater.cs @@ -11,6 +11,7 @@ using Ryujinx.Audio.Renderer.Server.Voice; using Ryujinx.Audio.Renderer.Utils; using Ryujinx.Common.Logging; using System; +using System.Buffers; using System.Diagnostics; using System.Runtime.CompilerServices; using System.Runtime.InteropServices; @@ -149,12 +150,16 @@ namespace Ryujinx.Audio.Renderer.Server state.InUse = false; } + Memory[] voiceUpdateStatesArray = ArrayPool>.Shared.Rent(Constants.VoiceChannelCountMax); + + Span> voiceUpdateStates = voiceUpdateStatesArray.AsSpan(0, Constants.VoiceChannelCountMax); + // Start processing for (int i = 0; i < context.GetCount(); i++) { VoiceInParameter parameter = parameters[i]; - Memory[] voiceUpdateStates = new Memory[Constants.VoiceChannelCountMax]; + voiceUpdateStates.Fill(Memory.Empty); ref VoiceOutStatus outStatus = ref SpanIOHelper.GetWriteRef(ref _output)[0]; @@ -197,6 +202,8 @@ namespace Ryujinx.Audio.Renderer.Server } } + ArrayPool>.Shared.Return(voiceUpdateStatesArray); + int currentOutputSize = _output.Length; OutputHeader.VoicesSize = (uint)(Unsafe.SizeOf() * context.GetCount()); diff --git a/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs b/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs index 006d6dd3c..0bf53c544 100644 --- a/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs +++ b/Ryujinx.Audio/Renderer/Server/Voice/VoiceState.cs @@ -378,7 +378,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The given user output. /// The user parameter. /// The voice states associated to the . - public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, Memory[] voiceUpdateStates) + public void WriteOutStatus(ref VoiceOutStatus outStatus, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates) { #if DEBUG // Sanity check in debug mode of the internal state @@ -424,7 +424,7 @@ namespace Ryujinx.Audio.Renderer.Server.Voice /// The voice states associated to the . /// The mapper to use. /// The behaviour context. - public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, Memory[] voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) + public void UpdateWaveBuffers(out ErrorInfo[] errorInfos, ref VoiceInParameter parameter, ReadOnlySpan> voiceUpdateStates, ref PoolMapper mapper, ref BehaviourContext behaviourContext) { errorInfos = new ErrorInfo[Constants.VoiceWaveBufferCount * 2]; diff --git a/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs b/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs new file mode 100644 index 000000000..eda350bdd --- /dev/null +++ b/Ryujinx.Common/Memory/ByteMemoryPool.ByteMemoryPoolBuffer.cs @@ -0,0 +1,51 @@ +using System; +using System.Buffers; +using System.Threading; + +namespace Ryujinx.Common.Memory +{ + public sealed partial class ByteMemoryPool + { + /// + /// Represents a that wraps an array rented from + /// and exposes it as + /// with a length of the requested size. + /// + private sealed class ByteMemoryPoolBuffer : IMemoryOwner + { + private byte[] _array; + private readonly int _length; + + public ByteMemoryPoolBuffer(int length) + { + _array = ArrayPool.Shared.Rent(length); + _length = length; + } + + /// + /// Returns a belonging to this owner. + /// + public Memory Memory + { + get + { + byte[] array = _array; + + ObjectDisposedException.ThrowIf(array is null, this); + + return new Memory(array, 0, _length); + } + } + + public void Dispose() + { + var array = Interlocked.Exchange(ref _array, null); + + if (array != null) + { + ArrayPool.Shared.Return(array); + } + } + } + } +} diff --git a/Ryujinx.Common/Memory/ByteMemoryPool.cs b/Ryujinx.Common/Memory/ByteMemoryPool.cs new file mode 100644 index 000000000..2910f408f --- /dev/null +++ b/Ryujinx.Common/Memory/ByteMemoryPool.cs @@ -0,0 +1,108 @@ +using System; +using System.Buffers; + +namespace Ryujinx.Common.Memory +{ + /// + /// Provides a pool of re-usable byte array instances. + /// + public sealed partial class ByteMemoryPool + { + private static readonly ByteMemoryPool _shared = new ByteMemoryPool(); + + /// + /// Constructs a instance. Private to force access through + /// the instance. + /// + private ByteMemoryPool() + { + // No implementation + } + + /// + /// Retrieves a shared instance. + /// + public static ByteMemoryPool Shared => _shared; + + /// + /// Returns the maximum buffer size supported by this pool. + /// + public int MaxBufferSize => Array.MaxLength; + + /// + /// Rents a byte memory buffer from . + /// The buffer may contain data from a prior use. + /// + /// The buffer's required length in bytes + /// A wrapping the rented memory + /// + public IMemoryOwner Rent(long length) + => RentImpl(checked((int)length)); + + /// + /// Rents a byte memory buffer from . + /// The buffer may contain data from a prior use. + /// + /// The buffer's required length in bytes + /// A wrapping the rented memory + /// + public IMemoryOwner Rent(ulong length) + => RentImpl(checked((int)length)); + + /// + /// Rents a byte memory buffer from . + /// The buffer may contain data from a prior use. + /// + /// The buffer's required length in bytes + /// A wrapping the rented memory + /// + public IMemoryOwner Rent(int length) + => RentImpl(length); + + /// + /// Rents a byte memory buffer from . + /// The buffer's contents are cleared (set to all 0s) before returning. + /// + /// The buffer's required length in bytes + /// A wrapping the rented memory + /// + public IMemoryOwner RentCleared(long length) + => RentCleared(checked((int)length)); + + /// + /// Rents a byte memory buffer from . + /// The buffer's contents are cleared (set to all 0s) before returning. + /// + /// The buffer's required length in bytes + /// A wrapping the rented memory + /// + public IMemoryOwner RentCleared(ulong length) + => RentCleared(checked((int)length)); + + /// + /// Rents a byte memory buffer from . + /// The buffer's contents are cleared (set to all 0s) before returning. + /// + /// The buffer's required length in bytes + /// A wrapping the rented memory + /// + public IMemoryOwner RentCleared(int length) + { + var buffer = RentImpl(length); + + buffer.Memory.Span.Clear(); + + return buffer; + } + + private static ByteMemoryPoolBuffer RentImpl(int length) + { + if ((uint)length > Array.MaxLength) + { + throw new ArgumentOutOfRangeException(nameof(length), length, null); + } + + return new ByteMemoryPoolBuffer(length); + } + } +} diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs index 394bf888e..21630c42e 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -27,98 +27,103 @@ namespace Ryujinx.HLE.HOS.Ipc public IpcMessage() { - PtrBuff = new List(); - SendBuff = new List(); - ReceiveBuff = new List(); - ExchangeBuff = new List(); - RecvListBuff = new List(); + PtrBuff = new List(0); + SendBuff = new List(0); + ReceiveBuff = new List(0); + ExchangeBuff = new List(0); + RecvListBuff = new List(0); - ObjectIds = new List(); + ObjectIds = new List(0); } - public IpcMessage(ReadOnlySpan data, long cmdPtr) : this() + public IpcMessage(ReadOnlySpan data, long cmdPtr) { using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data)) { BinaryReader reader = new BinaryReader(ms); - Initialize(reader, cmdPtr); - } - } + int word0 = reader.ReadInt32(); + int word1 = reader.ReadInt32(); - private void Initialize(BinaryReader reader, long cmdPtr) - { - int word0 = reader.ReadInt32(); - int word1 = reader.ReadInt32(); + Type = (IpcMessageType)(word0 & 0xffff); - Type = (IpcMessageType)(word0 & 0xffff); + int ptrBuffCount = (word0 >> 16) & 0xf; + int sendBuffCount = (word0 >> 20) & 0xf; + int recvBuffCount = (word0 >> 24) & 0xf; + int xchgBuffCount = (word0 >> 28) & 0xf; - int ptrBuffCount = (word0 >> 16) & 0xf; - int sendBuffCount = (word0 >> 20) & 0xf; - int recvBuffCount = (word0 >> 24) & 0xf; - int xchgBuffCount = (word0 >> 28) & 0xf; + int rawDataSize = (word1 >> 0) & 0x3ff; + int recvListFlags = (word1 >> 10) & 0xf; + bool hndDescEnable = ((word1 >> 31) & 0x1) != 0; - int rawDataSize = (word1 >> 0) & 0x3ff; - int recvListFlags = (word1 >> 10) & 0xf; - bool hndDescEnable = ((word1 >> 31) & 0x1) != 0; - - if (hndDescEnable) - { - HandleDesc = new IpcHandleDesc(reader); - } - - for (int index = 0; index < ptrBuffCount; index++) - { - PtrBuff.Add(new IpcPtrBuffDesc(reader)); - } - - void ReadBuff(List buff, int count) - { - for (int index = 0; index < count; index++) + if (hndDescEnable) { - buff.Add(new IpcBuffDesc(reader)); + HandleDesc = new IpcHandleDesc(reader); } - } - ReadBuff(SendBuff, sendBuffCount); - ReadBuff(ReceiveBuff, recvBuffCount); - ReadBuff(ExchangeBuff, xchgBuffCount); + PtrBuff = new List(ptrBuffCount); - rawDataSize *= 4; + for (int index = 0; index < ptrBuffCount; index++) + { + PtrBuff.Add(new IpcPtrBuffDesc(reader)); + } - long recvListPos = reader.BaseStream.Position + rawDataSize; + static List ReadBuff(BinaryReader reader, int count) + { + List buff = new List(count); + + for (int index = 0; index < count; index++) + { + buff.Add(new IpcBuffDesc(reader)); + } + + return buff; + } + + SendBuff = ReadBuff(reader, sendBuffCount); + ReceiveBuff = ReadBuff(reader, recvBuffCount); + ExchangeBuff = ReadBuff(reader, xchgBuffCount); + + rawDataSize *= 4; + + long recvListPos = reader.BaseStream.Position + rawDataSize; // Only CMIF has the padding requirements. if (Type < IpcMessageType.TipcCloseSession) { long pad0 = GetPadSize16(reader.BaseStream.Position + cmdPtr); - if (rawDataSize != 0) - { - rawDataSize -= (int)pad0; + if (rawDataSize != 0) + { + rawDataSize -= (int)pad0; + } + + reader.BaseStream.Seek(pad0, SeekOrigin.Current); } - reader.BaseStream.Seek(pad0, SeekOrigin.Current); - } + int recvListCount = recvListFlags - 2; - int recvListCount = recvListFlags - 2; + if (recvListCount == 0) + { + recvListCount = 1; + } + else if (recvListCount < 0) + { + recvListCount = 0; + } - if (recvListCount == 0) - { - recvListCount = 1; - } - else if (recvListCount < 0) - { - recvListCount = 0; - } + RawData = reader.ReadBytes(rawDataSize); - RawData = reader.ReadBytes(rawDataSize); + reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin); - reader.BaseStream.Seek(recvListPos, SeekOrigin.Begin); + RecvListBuff = new List(recvListCount); - for (int index = 0; index < recvListCount; index++) - { - RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64())); + for (int index = 0; index < recvListCount; index++) + { + RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64())); + } + + ObjectIds = new List(0); } } diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs index 1af171b92..c0cd9ce99 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -71,7 +71,13 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { lock (_context.CriticalSection.Lock) { - _waitingObjects.RemoveAll(x => x.Object == schedulerObj); + for (int index = _waitingObjects.Count - 1; index >= 0; index--) + { + if (_waitingObjects[index].Object == schedulerObj) + { + _waitingObjects.RemoveAt(index); + } + } } } @@ -105,16 +111,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Common } else { - while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks <= next.TimePoint) + while (Interlocked.Read(ref _enforceWakeupFromSpinWait) != 1 && PerformanceCounter.ElapsedTicks < next.TimePoint) { + // Our time is close - don't let SpinWait go off and potentially Thread.Sleep(). if (spinWait.NextSpinWillYield) { Thread.Yield(); spinWait.Reset(); } - - spinWait.SpinOnce(); + else + { + spinWait.SpinOnce(); + } } spinWait.Reset(); diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index c6467208e..3163c3487 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -9,6 +9,7 @@ using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; using System; +using System.Buffers; using System.Threading; namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall @@ -553,7 +554,9 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KProcess currentProcess = KernelStatic.GetCurrentProcess(); - KSynchronizationObject[] syncObjs = handles.Length == 0 ? Array.Empty() : new KSynchronizationObject[handles.Length]; + KSynchronizationObject[] syncObjsArray = ArrayPool.Shared.Rent(handles.Length); + + Span syncObjs = syncObjsArray.AsSpan(0, handles.Length); for (int index = 0; index < handles.Length; index++) { @@ -606,6 +609,8 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall } } + ArrayPool.Shared.Return(syncObjsArray); + return result; } diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs index 973d5f6a4..d42f90032 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -1,6 +1,7 @@ using Ryujinx.HLE.HOS.Kernel.Common; using Ryujinx.Horizon.Common; using System; +using System.Buffers; using System.Collections.Generic; namespace Ryujinx.HLE.HOS.Kernel.Threading @@ -59,7 +60,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } else { - LinkedListNode[] syncNodes = syncObjs.Length == 0 ? Array.Empty>() : new LinkedListNode[syncObjs.Length]; + LinkedListNode[] syncNodesArray = ArrayPool>.Shared.Rent(syncObjs.Length); + + Span> syncNodes = syncNodesArray.AsSpan(0, syncObjs.Length); for (int index = 0; index < syncObjs.Length; index++) { @@ -101,6 +104,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading handleIndex = index; } } + + ArrayPool>.Shared.Return(syncNodesArray); } _context.CriticalSection.Leave(); diff --git a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs index 813210461..a137c4133 100644 --- a/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs +++ b/Ryujinx.HLE/HOS/Services/Audio/AudioRenderer/AudioRendererServer.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; @@ -68,25 +69,29 @@ namespace Ryujinx.HLE.HOS.Services.Audio.AudioRenderer ReadOnlyMemory input = context.Memory.GetSpan(inputPosition, (int)inputSize).ToArray(); - Memory output = new byte[outputSize]; - Memory performanceOutput = new byte[performanceOutputSize]; - - using MemoryHandle outputHandle = output.Pin(); - using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); - - ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); - - if (result == ResultCode.Success) + using (IMemoryOwner outputOwner = ByteMemoryPool.Shared.RentCleared(outputSize)) + using (IMemoryOwner performanceOutputOwner = ByteMemoryPool.Shared.RentCleared(performanceOutputSize)) { - context.Memory.Write(outputPosition, output.Span); - context.Memory.Write(performanceOutputPosition, performanceOutput.Span); - } - else - { - Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}"); - } + Memory output = outputOwner.Memory; + Memory performanceOutput = performanceOutputOwner.Memory; - return result; + using MemoryHandle outputHandle = output.Pin(); + using MemoryHandle performanceOutputHandle = performanceOutput.Pin(); + + ResultCode result = _impl.RequestUpdate(output, performanceOutput, input); + + if (result == ResultCode.Success) + { + context.Memory.Write(outputPosition, output.Span); + context.Memory.Write(performanceOutputPosition, performanceOutput.Span); + } + else + { + Logger.Error?.Print(LogClass.ServiceAudio, $"Error while processing renderer update: 0x{(int)result:X}"); + } + + return result; + } } [CommandCmif(5)] diff --git a/Ryujinx.HLE/HOS/Services/IpcService.cs b/Ryujinx.HLE/HOS/Services/IpcService.cs index f42fd0e21..048a68a9e 100644 --- a/Ryujinx.HLE/HOS/Services/IpcService.cs +++ b/Ryujinx.HLE/HOS/Services/IpcService.cs @@ -76,6 +76,8 @@ namespace Ryujinx.HLE.HOS.Services context.RequestData.BaseStream.Seek(0x10 + dataPayloadSize, SeekOrigin.Begin); + context.Request.ObjectIds.EnsureCapacity(inputObjCount); + for (int index = 0; index < inputObjCount; index++) { context.Request.ObjectIds.Add(context.RequestData.ReadInt32()); diff --git a/Ryujinx.HLE/HOS/Services/ServerBase.cs b/Ryujinx.HLE/HOS/Services/ServerBase.cs index 762b4fa44..b994679aa 100644 --- a/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -217,6 +217,8 @@ namespace Ryujinx.HLE.HOS.Services if (noReceive) { + response.PtrBuff.EnsureCapacity(request.RecvListBuff.Count); + for (int i = 0; i < request.RecvListBuff.Count; i++) { ulong size = (ulong)BinaryPrimitives.ReadInt16LittleEndian(request.RawData.AsSpan(sizesOffset + i * 2, 2)); diff --git a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs index 0724c83ee..42fc27612 100644 --- a/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs +++ b/Ryujinx.HLE/HOS/Services/SurfaceFlinger/IHOSBinderDriver.cs @@ -1,7 +1,9 @@ -using Ryujinx.HLE.HOS.Ipc; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; using System; +using System.Buffers; namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger { @@ -83,16 +85,19 @@ namespace Ryujinx.HLE.HOS.Services.SurfaceFlinger ReadOnlySpan inputParcel = context.Memory.GetSpan(dataPos, (int)dataSize); - Span outputParcel = new Span(new byte[replySize]); - - ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); - - if (result == ResultCode.Success) + using (IMemoryOwner outputParcelOwner = ByteMemoryPool.Shared.RentCleared(replySize)) { - context.Memory.Write(replyPos, outputParcel); - } + Span outputParcel = outputParcelOwner.Memory.Span; + + ResultCode result = OnTransact(binderId, code, flags, inputParcel, outputParcel); - return result; + if (result == ResultCode.Success) + { + context.Memory.Write(replyPos, outputParcel); + } + + return result; + } } protected abstract ResultCode AdjustRefcount(int binderId, int addVal, int type); diff --git a/Ryujinx.Input.SDL2/SDL2Gamepad.cs b/Ryujinx.Input.SDL2/SDL2Gamepad.cs index eec4e07e5..925526737 100644 --- a/Ryujinx.Input.SDL2/SDL2Gamepad.cs +++ b/Ryujinx.Input.SDL2/SDL2Gamepad.cs @@ -12,17 +12,7 @@ namespace Ryujinx.Input.SDL2 { private bool HasConfiguration => _configuration != null; - private class ButtonMappingEntry - { - public readonly GamepadButtonInputId To; - public readonly GamepadButtonInputId From; - - public ButtonMappingEntry(GamepadButtonInputId to, GamepadButtonInputId from) - { - To = to; - From = from; - } - } + private record struct ButtonMappingEntry(GamepadButtonInputId To, GamepadButtonInputId From); private StandardControllerInputConfig _configuration; @@ -85,7 +75,7 @@ namespace Ryujinx.Input.SDL2 public SDL2Gamepad(IntPtr gamepadHandle, string driverId) { _gamepadHandle = gamepadHandle; - _buttonsUserMapping = new List(); + _buttonsUserMapping = new List(20); Name = SDL_GameControllerName(_gamepadHandle); Id = driverId;