diff --git a/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs index cebfbde12..0dd5355f4 100644 --- a/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs +++ b/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs @@ -1,6 +1,7 @@ using ARMeilleure.CodeGen.Linking; using ARMeilleure.CodeGen.RegisterAllocators; using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; using System; using System.Collections.Generic; using System.IO; @@ -59,7 +60,7 @@ namespace ARMeilleure.CodeGen.Arm64 public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) { - _stream = new MemoryStream(); + _stream = MemoryStreamManager.Shared.GetStream(); AllocResult = allocResult; diff --git a/ARMeilleure/CodeGen/X86/Assembler.cs b/ARMeilleure/CodeGen/X86/Assembler.cs index c15deadc5..2ea4208b3 100644 --- a/ARMeilleure/CodeGen/X86/Assembler.cs +++ b/ARMeilleure/CodeGen/X86/Assembler.cs @@ -1,5 +1,6 @@ using ARMeilleure.CodeGen.Linking; using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; using System; using System.Collections.Generic; using System.Diagnostics; @@ -1285,7 +1286,7 @@ namespace ARMeilleure.CodeGen.X86 // Write the code, ignoring the dummy bytes after jumps, into a new stream. _stream.Seek(0, SeekOrigin.Begin); - using var codeStream = new MemoryStream(); + using var codeStream = MemoryStreamManager.Shared.GetStream(); var assembler = new Assembler(codeStream, HasRelocs); bool hasRelocs = HasRelocs; diff --git a/ARMeilleure/CodeGen/X86/CodeGenContext.cs b/ARMeilleure/CodeGen/X86/CodeGenContext.cs index eee71bd78..899487241 100644 --- a/ARMeilleure/CodeGen/X86/CodeGenContext.cs +++ b/ARMeilleure/CodeGen/X86/CodeGenContext.cs @@ -1,5 +1,6 @@ using ARMeilleure.CodeGen.RegisterAllocators; using ARMeilleure.IntermediateRepresentation; +using Ryujinx.Common.Memory; using System.IO; using System.Numerics; @@ -22,7 +23,7 @@ namespace ARMeilleure.CodeGen.X86 public CodeGenContext(AllocationResult allocResult, int maxCallArgs, int blocksCount, bool relocatable) { - _stream = new MemoryStream(); + _stream = MemoryStreamManager.Shared.GetStream(); _blockLabels = new Operand[blocksCount]; AllocResult = allocResult; diff --git a/ARMeilleure/Translation/PTC/Ptc.cs b/ARMeilleure/Translation/PTC/Ptc.cs index 276ec788b..0b23fd043 100644 --- a/ARMeilleure/Translation/PTC/Ptc.cs +++ b/ARMeilleure/Translation/PTC/Ptc.cs @@ -6,6 +6,7 @@ using ARMeilleure.Memory; using Ryujinx.Common; using Ryujinx.Common.Configuration; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using System; using System.Buffers.Binary; using System.Collections.Generic; @@ -150,10 +151,10 @@ namespace ARMeilleure.Translation.PTC private void InitializeCarriers() { - _infosStream = new MemoryStream(); + _infosStream = MemoryStreamManager.Shared.GetStream(); _codesList = new List(); - _relocsStream = new MemoryStream(); - _unwindInfosStream = new MemoryStream(); + _relocsStream = MemoryStreamManager.Shared.GetStream(); + _unwindInfosStream = MemoryStreamManager.Shared.GetStream(); } private void DisposeCarriers() diff --git a/ARMeilleure/Translation/PTC/PtcProfiler.cs b/ARMeilleure/Translation/PTC/PtcProfiler.cs index 030ccff5f..391e29c76 100644 --- a/ARMeilleure/Translation/PTC/PtcProfiler.cs +++ b/ARMeilleure/Translation/PTC/PtcProfiler.cs @@ -1,6 +1,7 @@ using ARMeilleure.State; using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using System; using System.Buffers.Binary; using System.Collections.Concurrent; @@ -182,7 +183,7 @@ namespace ARMeilleure.Translation.PTC return false; } - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) { Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); @@ -274,7 +275,7 @@ namespace ARMeilleure.Translation.PTC outerHeader.SetHeaderHash(); - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) { Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L); diff --git a/Directory.Packages.props b/Directory.Packages.props index eda7d395a..14d440753 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -22,6 +22,7 @@ + diff --git a/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs index ea404d2a2..21da6fc01 100644 --- a/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs +++ b/Ryujinx.Common/Extensions/BinaryReaderExtensions.cs @@ -12,19 +12,5 @@ namespace Ryujinx.Common { return MemoryMarshal.Cast(reader.ReadBytes(Unsafe.SizeOf()))[0]; } - - public unsafe static void WriteStruct(this BinaryWriter writer, T value) - where T : unmanaged - { - ReadOnlySpan data = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); - - writer.Write(data); - } - - public static void Write(this BinaryWriter writer, UInt128 value) - { - writer.Write((ulong)value); - writer.Write((ulong)(value >> 64)); - } } } diff --git a/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs b/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs new file mode 100644 index 000000000..fddc8c1b7 --- /dev/null +++ b/Ryujinx.Common/Extensions/BinaryWriterExtensions.cs @@ -0,0 +1,28 @@ +using System; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + public static class BinaryWriterExtensions + { + public unsafe static void WriteStruct(this BinaryWriter writer, T value) + where T : unmanaged + { + ReadOnlySpan data = MemoryMarshal.Cast(MemoryMarshal.CreateReadOnlySpan(ref value, 1)); + + writer.Write(data); + } + + public static void Write(this BinaryWriter writer, UInt128 value) + { + writer.Write((ulong)value); + writer.Write((ulong)(value >> 64)); + } + + public static void Write(this BinaryWriter writer, MemoryStream stream) + { + stream.CopyTo(writer.BaseStream); + } + } +} diff --git a/Ryujinx.Common/Extensions/StreamExtensions.cs b/Ryujinx.Common/Extensions/StreamExtensions.cs new file mode 100644 index 000000000..f6fc870ab --- /dev/null +++ b/Ryujinx.Common/Extensions/StreamExtensions.cs @@ -0,0 +1,138 @@ +using System; +using System.Buffers.Binary; +using System.IO; +using System.Runtime.InteropServices; + +namespace Ryujinx.Common +{ + public static class StreamExtensions + { + /// + /// Writes a " /> to this stream. + /// + /// This default implementation converts each buffer value to a stack-allocated + /// byte array, then writes it to the Stream using . + /// + /// The stream to be written to + /// The buffer of values to be written + public static void Write(this Stream stream, ReadOnlySpan buffer) + { + if (buffer.Length == 0) + { + return; + } + + if (BitConverter.IsLittleEndian) + { + ReadOnlySpan byteBuffer = MemoryMarshal.Cast(buffer); + stream.Write(byteBuffer); + } + else + { + Span byteBuffer = stackalloc byte[sizeof(int)]; + + foreach (int value in buffer) + { + BinaryPrimitives.WriteInt32LittleEndian(byteBuffer, value); + stream.Write(byteBuffer); + } + } + } + + /// + /// Writes a four-byte signed integer to this stream. The current position + /// of the stream is advanced by four. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, int value) + { + Span buffer = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + /// Writes an eight-byte signed integer to this stream. The current position + /// of the stream is advanced by eight. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, long value) + { + Span buffer = stackalloc byte[sizeof(long)]; + BinaryPrimitives.WriteInt64LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + // Writes a four-byte unsigned integer to this stream. The current position + // of the stream is advanced by four. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, uint value) + { + Span buffer = stackalloc byte[sizeof(uint)]; + BinaryPrimitives.WriteUInt32LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + /// Writes an eight-byte unsigned integer to this stream. The current + /// position of the stream is advanced by eight. + /// + /// The stream to be written to + /// The value to be written + public static void Write(this Stream stream, ulong value) + { + Span buffer = stackalloc byte[sizeof(ulong)]; + BinaryPrimitives.WriteUInt64LittleEndian(buffer, value); + stream.Write(buffer); + } + + /// + /// Writes the contents of source to stream by calling source.CopyTo(stream). + /// Provides consistency with other Stream.Write methods. + /// + /// The stream to be written to + /// The stream to be read from + public static void Write(this Stream stream, Stream source) + { + source.CopyTo(stream); + } + + /// + /// Writes a sequence of bytes to the Stream. + /// + /// The stream to be written to. + /// The byte to be written + /// The number of times the value should be written + public static void WriteByte(this Stream stream, byte value, int count) + { + if (count <= 0) + { + return; + } + + const int BlockSize = 16; + + int blockCount = count / BlockSize; + if (blockCount > 0) + { + Span span = stackalloc byte[BlockSize]; + span.Fill(value); + for (int x = 0; x < blockCount; x++) + { + stream.Write(span); + } + } + + int nonBlockBytes = count % BlockSize; + for (int x = 0; x < nonBlockBytes; x++) + { + stream.WriteByte(value); + } + } + } +} diff --git a/Ryujinx.Common/Memory/MemoryStreamManager.cs b/Ryujinx.Common/Memory/MemoryStreamManager.cs new file mode 100644 index 000000000..68b829993 --- /dev/null +++ b/Ryujinx.Common/Memory/MemoryStreamManager.cs @@ -0,0 +1,99 @@ +using Microsoft.IO; +using System; + +namespace Ryujinx.Common.Memory +{ + public static class MemoryStreamManager + { + private static readonly RecyclableMemoryStreamManager _shared = new RecyclableMemoryStreamManager(); + + /// + /// We don't expose the RecyclableMemoryStreamManager directly because version 2.x + /// returns them as MemoryStream. This Shared class is here to a) offer only the GetStream() versions we use + /// and b) return them as RecyclableMemoryStream so we don't have to cast. + /// + public static class Shared + { + /// + /// Retrieve a new MemoryStream object with no tag and a default initial capacity. + /// + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream() + => new RecyclableMemoryStream(_shared); + + /// + /// Retrieve a new MemoryStream object with the contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(byte[] buffer) + => GetStream(Guid.NewGuid(), null, buffer, 0, buffer.Length); + + /// + /// Retrieve a new MemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// The byte buffer to copy data from + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(ReadOnlySpan buffer) + => GetStream(Guid.NewGuid(), null, buffer); + + /// + /// Retrieve a new RecyclableMemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned. + /// A unique identifier which can be used to trace usages of the stream + /// A tag which can be used to track the source of the stream + /// The byte buffer to copy data from + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(Guid id, string tag, ReadOnlySpan buffer) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(_shared, id, tag, buffer.Length); + stream.Write(buffer); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + + /// + /// Retrieve a new RecyclableMemoryStream object with the given tag and with contents copied from the provided + /// buffer. The provided buffer is not wrapped or used after construction. + /// + /// The new stream's position is set to the beginning of the stream when returned + /// A unique identifier which can be used to trace usages of the stream + /// A tag which can be used to track the source of the stream + /// The byte buffer to copy data from + /// The offset from the start of the buffer to copy from + /// The number of bytes to copy from the buffer + /// A RecyclableMemoryStream + public static RecyclableMemoryStream GetStream(Guid id, string tag, byte[] buffer, int offset, int count) + { + RecyclableMemoryStream stream = null; + try + { + stream = new RecyclableMemoryStream(_shared, id, tag, count); + stream.Write(buffer, offset, count); + stream.Position = 0; + return stream; + } + catch + { + stream?.Dispose(); + throw; + } + } + } + } +} diff --git a/Ryujinx.Common/Ryujinx.Common.csproj b/Ryujinx.Common/Ryujinx.Common.csproj index c307f524e..c02b11e0c 100644 --- a/Ryujinx.Common/Ryujinx.Common.csproj +++ b/Ryujinx.Common/Ryujinx.Common.csproj @@ -7,6 +7,7 @@ + diff --git a/Ryujinx.Common/Utilities/EmbeddedResources.cs b/Ryujinx.Common/Utilities/EmbeddedResources.cs index e7c8d7d70..22b55f161 100644 --- a/Ryujinx.Common/Utilities/EmbeddedResources.cs +++ b/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -1,3 +1,5 @@ +using Ryujinx.Common.Memory; +using Ryujinx.Common.Utilities; using System; using System.IO; using System.Linq; @@ -38,12 +40,7 @@ namespace Ryujinx.Common return null; } - using (var mem = new MemoryStream()) - { - stream.CopyTo(mem); - - return mem.ToArray(); - } + return StreamUtils.StreamToBytes(stream); } } @@ -56,12 +53,7 @@ namespace Ryujinx.Common return null; } - using (var mem = new MemoryStream()) - { - await stream.CopyToAsync(mem); - - return mem.ToArray(); - } + return await StreamUtils.StreamToBytesAsync(stream); } } diff --git a/Ryujinx.Common/Utilities/StreamUtils.cs b/Ryujinx.Common/Utilities/StreamUtils.cs index 9bd03ec90..da97188d8 100644 --- a/Ryujinx.Common/Utilities/StreamUtils.cs +++ b/Ryujinx.Common/Utilities/StreamUtils.cs @@ -1,4 +1,8 @@ -using System.IO; +using Microsoft.IO; +using Ryujinx.Common.Memory; +using System.IO; +using System.Threading; +using System.Threading.Tasks; namespace Ryujinx.Common.Utilities { @@ -6,12 +10,22 @@ namespace Ryujinx.Common.Utilities { public static byte[] StreamToBytes(Stream input) { - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) { input.CopyTo(stream); return stream.ToArray(); } } + + public static async Task StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) + { + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) + { + await input.CopyToAsync(stream, cancellationToken); + + return stream.ToArray(); + } + } } } \ No newline at end of file diff --git a/Ryujinx.Cpu/MemoryHelper.cs b/Ryujinx.Cpu/MemoryHelper.cs index 64ff360e5..194a0c35d 100644 --- a/Ryujinx.Cpu/MemoryHelper.cs +++ b/Ryujinx.Cpu/MemoryHelper.cs @@ -1,4 +1,6 @@ -using Ryujinx.Memory; +using Microsoft.IO; +using Ryujinx.Common.Memory; +using Ryujinx.Memory; using System; using System.IO; using System.Runtime.CompilerServices; @@ -40,7 +42,7 @@ namespace Ryujinx.Cpu public static string ReadAsciiString(IVirtualMemoryManager memory, ulong position, long maxSize = -1) { - using (MemoryStream ms = new MemoryStream()) + using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream()) { for (long offs = 0; offs < maxSize || maxSize == -1; offs++) { @@ -54,7 +56,7 @@ namespace Ryujinx.Cpu ms.WriteByte(value); } - return Encoding.ASCII.GetString(ms.ToArray()); + return Encoding.ASCII.GetString(ms.GetReadOnlySequence()); } } } diff --git a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs index 5b430e1af..77e526672 100644 --- a/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs +++ b/Ryujinx.Graphics.Gpu/Shader/DiskCache/ShaderBinarySerializer.cs @@ -1,3 +1,5 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.Shader; using Ryujinx.Graphics.Shader.Translation; @@ -11,16 +13,15 @@ namespace Ryujinx.Graphics.Gpu.Shader.DiskCache { public static byte[] Pack(ShaderSource[] sources) { - using MemoryStream output = new MemoryStream(); - using BinaryWriter writer = new BinaryWriter(output); + using MemoryStream output = MemoryStreamManager.Shared.GetStream(); - writer.Write(sources.Length); + output.Write(sources.Length); - for (int i = 0; i < sources.Length; i++) + foreach (ShaderSource source in sources) { - writer.Write((int)sources[i].Stage); - writer.Write(sources[i].BinaryCode.Length); - writer.Write(sources[i].BinaryCode); + output.Write((int)source.Stage); + output.Write(source.BinaryCode.Length); + output.Write(source.BinaryCode); } return output.ToArray(); diff --git a/Ryujinx.HLE/FileSystem/ContentManager.cs b/Ryujinx.HLE/FileSystem/ContentManager.cs index 4e3940081..9facdd0b7 100644 --- a/Ryujinx.HLE/FileSystem/ContentManager.cs +++ b/Ryujinx.HLE/FileSystem/ContentManager.cs @@ -9,6 +9,7 @@ using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; using LibHac.Tools.Ncm; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Common.Utilities; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.HOS.Services.Ssl; @@ -637,12 +638,12 @@ namespace Ryujinx.HLE.FileSystem private Stream GetZipStream(ZipArchiveEntry entry) { - MemoryStream dest = new MemoryStream(); + MemoryStream dest = MemoryStreamManager.Shared.GetStream(); - Stream src = entry.Open(); - - src.CopyTo(dest); - src.Dispose(); + using (Stream src = entry.Open()) + { + src.CopyTo(dest); + } return dest; } diff --git a/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs b/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs index 2d5509ff8..952afcd5f 100644 --- a/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/Browser/BrowserApplet.cs @@ -1,5 +1,6 @@ using Ryujinx.Common; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Services.Am.AppletAE; using System; using System.Collections.Generic; @@ -70,7 +71,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser private byte[] BuildResponseOld(WebCommonReturnValue result) { - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.WriteStruct(result); @@ -80,7 +81,7 @@ namespace Ryujinx.HLE.HOS.Applets.Browser } private byte[] BuildResponseNew(List outputArguments) { - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.WriteStruct(new WebArgHeader diff --git a/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs b/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs index 5cdfb3143..5d5a26c23 100644 --- a/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/Controller/ControllerApplet.cs @@ -1,4 +1,5 @@ using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Services.Am.AppletAE; using Ryujinx.HLE.HOS.Services.Hid; using Ryujinx.HLE.HOS.Services.Hid.Types; @@ -123,7 +124,7 @@ namespace Ryujinx.HLE.HOS.Applets private byte[] BuildResponse(ControllerSupportResultInfo result) { - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write(MemoryMarshal.AsBytes(MemoryMarshal.CreateReadOnlySpan(ref result, Unsafe.SizeOf()))); @@ -134,7 +135,7 @@ namespace Ryujinx.HLE.HOS.Applets private byte[] BuildResponse() { - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write((ulong)ResultCode.Success); diff --git a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs index cec9f213e..a8119a470 100644 --- a/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs +++ b/Ryujinx.HLE/HOS/Applets/PlayerSelect/PlayerSelectApplet.cs @@ -1,4 +1,5 @@ -using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.HLE.HOS.Services.Am.AppletAE; using System; using System.IO; @@ -43,7 +44,7 @@ namespace Ryujinx.HLE.HOS.Applets { UserProfile currentUser = _system.AccountManager.LastOpenedUser; - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.Write((ulong)PlayerSelectResult.Success); diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs index e6ed46138..c7ef7e9cf 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs @@ -1,3 +1,6 @@ +using Microsoft.IO; +using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; using System.IO; @@ -18,20 +21,27 @@ namespace Ryujinx.HLE.HOS.Ipc HasPId = (word & 1) != 0; - ToCopy = new int[(word >> 1) & 0xf]; - ToMove = new int[(word >> 5) & 0xf]; - PId = HasPId ? reader.ReadUInt64() : 0; - for (int index = 0; index < ToCopy.Length; index++) + int toCopySize = (word >> 1) & 0xf; + int[] toCopy = toCopySize == 0 ? Array.Empty() : new int[toCopySize]; + + for (int index = 0; index < toCopy.Length; index++) { - ToCopy[index] = reader.ReadInt32(); + toCopy[index] = reader.ReadInt32(); } - for (int index = 0; index < ToMove.Length; index++) + ToCopy = toCopy; + + int toMoveSize = (word >> 5) & 0xf; + int[] toMove = toMoveSize == 0 ? Array.Empty() : new int[toMoveSize]; + + for (int index = 0; index < toMove.Length; index++) { - ToMove[index] = reader.ReadInt32(); + toMove[index] = reader.ReadInt32(); } + + ToMove = toMove; } public IpcHandleDesc(int[] copy, int[] move) @@ -57,36 +67,27 @@ namespace Ryujinx.HLE.HOS.Ipc return new IpcHandleDesc(Array.Empty(), handles); } - public byte[] GetBytes() + public RecyclableMemoryStream GetStream() { - using (MemoryStream ms = new MemoryStream()) + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + int word = HasPId ? 1 : 0; + + word |= (ToCopy.Length & 0xf) << 1; + word |= (ToMove.Length & 0xf) << 5; + + ms.Write(word); + + if (HasPId) { - BinaryWriter writer = new BinaryWriter(ms); - - int word = HasPId ? 1 : 0; - - word |= (ToCopy.Length & 0xf) << 1; - word |= (ToMove.Length & 0xf) << 5; - - writer.Write(word); - - if (HasPId) - { - writer.Write(PId); - } - - foreach (int handle in ToCopy) - { - writer.Write(handle); - } - - foreach (int handle in ToMove) - { - writer.Write(handle); - } - - return ms.ToArray(); + ms.Write(PId); } + + ms.Write(ToCopy); + ms.Write(ToMove); + + ms.Position = 0; + return ms; } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs index 55044da40..4e8f2fbfd 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs @@ -1,4 +1,8 @@ +using Microsoft.IO; +using Ryujinx.Common; +using Ryujinx.Common.Memory; using System; +using System.Buffers; using System.Collections.Generic; using System.Diagnostics; using System.IO; @@ -32,9 +36,9 @@ namespace Ryujinx.HLE.HOS.Ipc ObjectIds = new List(); } - public IpcMessage(byte[] data, long cmdPtr) : this() + public IpcMessage(ReadOnlySpan data, long cmdPtr) : this() { - using (MemoryStream ms = new MemoryStream(data)) + using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(data)) { BinaryReader reader = new BinaryReader(ms); @@ -114,124 +118,119 @@ namespace Ryujinx.HLE.HOS.Ipc for (int index = 0; index < recvListCount; index++) { - RecvListBuff.Add(new IpcRecvListBuffDesc(reader)); + RecvListBuff.Add(new IpcRecvListBuffDesc(reader.ReadUInt64())); } } - public byte[] GetBytes(long cmdPtr, ulong recvListAddr) + public RecyclableMemoryStream GetStream(long cmdPtr, ulong recvListAddr) { - using (MemoryStream ms = new MemoryStream()) + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + int word0; + int word1; + + word0 = (int)Type; + word0 |= (PtrBuff.Count & 0xf) << 16; + word0 |= (SendBuff.Count & 0xf) << 20; + word0 |= (ReceiveBuff.Count & 0xf) << 24; + word0 |= (ExchangeBuff.Count & 0xf) << 28; + + using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream(); + + int dataLength = RawData?.Length ?? 0; + + dataLength = (dataLength + 3) & ~3; + + int rawLength = dataLength; + + int pad0 = (int)GetPadSize16(cmdPtr + 8 + (handleDataStream?.Length ?? 0) + PtrBuff.Count * 8); + + // Apparently, padding after Raw Data is 16 bytes, however when there is + // padding before Raw Data too, we need to subtract the size of this padding. + // This is the weirdest padding I've seen so far... + int pad1 = 0x10 - pad0; + + dataLength = (dataLength + pad0 + pad1) / 4; + + word1 = (dataLength & 0x3ff) | (2 << 10); + + if (HandleDesc != null) { - BinaryWriter writer = new BinaryWriter(ms); - - int word0; - int word1; - - word0 = (int)Type; - word0 |= (PtrBuff.Count & 0xf) << 16; - word0 |= (SendBuff.Count & 0xf) << 20; - word0 |= (ReceiveBuff.Count & 0xf) << 24; - word0 |= (ExchangeBuff.Count & 0xf) << 28; - - byte[] handleData = Array.Empty(); - - if (HandleDesc != null) - { - handleData = HandleDesc.GetBytes(); - } - - int dataLength = RawData?.Length ?? 0; - - dataLength = (dataLength + 3) & ~3; - - int rawLength = dataLength; - - int pad0 = (int)GetPadSize16(cmdPtr + 8 + handleData.Length + PtrBuff.Count * 8); - - // Apparently, padding after Raw Data is 16 bytes, however when there is - // padding before Raw Data too, we need to subtract the size of this padding. - // This is the weirdest padding I've seen so far... - int pad1 = 0x10 - pad0; - - dataLength = (dataLength + pad0 + pad1) / 4; - - word1 = (dataLength & 0x3ff) | (2 << 10); - - if (HandleDesc != null) - { - word1 |= 1 << 31; - } - - writer.Write(word0); - writer.Write(word1); - writer.Write(handleData); - - for (int index = 0; index < PtrBuff.Count; index++) - { - writer.Write(PtrBuff[index].GetWord0()); - writer.Write(PtrBuff[index].GetWord1()); - } - - ms.Seek(pad0, SeekOrigin.Current); - - if (RawData != null) - { - writer.Write(RawData); - ms.Seek(rawLength - RawData.Length, SeekOrigin.Current); - } - - writer.Write(new byte[pad1]); - writer.Write(recvListAddr); - - return ms.ToArray(); + word1 |= 1 << 31; } + + ms.Write(word0); + ms.Write(word1); + + if (handleDataStream != null) + { + ms.Write(handleDataStream); + } + + foreach (IpcPtrBuffDesc ptrBuffDesc in PtrBuff) + { + ms.Write(ptrBuffDesc.GetWord0()); + ms.Write(ptrBuffDesc.GetWord1()); + } + + ms.WriteByte(0, pad0); + + if (RawData != null) + { + ms.Write(RawData); + ms.WriteByte(0, rawLength - RawData.Length); + } + + ms.WriteByte(0, pad1); + + ms.Write(recvListAddr); + + ms.Position = 0; + + return ms; } - public byte[] GetBytesTipc() + public RecyclableMemoryStream GetStreamTipc() { Debug.Assert(PtrBuff.Count == 0); - using (MemoryStream ms = new MemoryStream()) + RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream(); + + int word0; + int word1; + + word0 = (int)Type; + word0 |= (SendBuff.Count & 0xf) << 20; + word0 |= (ReceiveBuff.Count & 0xf) << 24; + word0 |= (ExchangeBuff.Count & 0xf) << 28; + + using RecyclableMemoryStream handleDataStream = HandleDesc?.GetStream(); + + int dataLength = RawData?.Length ?? 0; + + dataLength = ((dataLength + 3) & ~3) / 4; + + word1 = (dataLength & 0x3ff); + + if (HandleDesc != null) { - BinaryWriter writer = new BinaryWriter(ms); - - int word0; - int word1; - - word0 = (int)Type; - word0 |= (SendBuff.Count & 0xf) << 20; - word0 |= (ReceiveBuff.Count & 0xf) << 24; - word0 |= (ExchangeBuff.Count & 0xf) << 28; - - byte[] handleData = Array.Empty(); - - if (HandleDesc != null) - { - handleData = HandleDesc.GetBytes(); - } - - int dataLength = RawData?.Length ?? 0; - - dataLength = ((dataLength + 3) & ~3) / 4; - - word1 = (dataLength & 0x3ff); - - if (HandleDesc != null) - { - word1 |= 1 << 31; - } - - writer.Write(word0); - writer.Write(word1); - writer.Write(handleData); - - if (RawData != null) - { - writer.Write(RawData); - } - - return ms.ToArray(); + word1 |= 1 << 31; } + + ms.Write(word0); + ms.Write(word1); + + if (handleDataStream != null) + { + ms.Write(handleDataStream); + } + + if (RawData != null) + { + ms.Write(RawData); + } + + return ms; } private long GetPadSize16(long position) diff --git a/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs index 10406ac7d..bcc9d8f89 100644 --- a/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs +++ b/Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs @@ -13,13 +13,11 @@ namespace Ryujinx.HLE.HOS.Ipc Size = size; } - public IpcRecvListBuffDesc(BinaryReader reader) + public IpcRecvListBuffDesc(ulong packedValue) { - ulong value = reader.ReadUInt64(); + Position = packedValue & 0xffffffffffff; - Position = value & 0xffffffffffff; - - Size = (ushort)(value >> 48); + Size = (ushort)(packedValue >> 48); } } } \ No newline at end of file diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs index 030a314f7..1af171b92 100644 --- a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs +++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs @@ -16,7 +16,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public WaitingObject(IKFutureSchedulerObject schedulerObj, long timePoint) { - Object = schedulerObj; + Object = schedulerObj; TimePoint = timePoint; } } @@ -27,6 +27,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Common private bool _keepRunning; private long _enforceWakeupFromSpinWait; + private const long NanosecondsPerSecond = 1000000000L; + private const long NanosecondsPerMillisecond = 1000000L; + public KTimeManager(KernelContext context) { _context = context; @@ -55,7 +58,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common { _waitingObjects.Add(new WaitingObject(schedulerObj, timePoint)); - if (timeout < 1000000) + if (timeout < NanosecondsPerMillisecond) { Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 1); } @@ -142,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common private WaitingObject GetNextWaitingObject() { WaitingObject selected = null; - + long lowestTimePoint = long.MaxValue; for (int index = _waitingObjects.Count - 1; index >= 0; index--) @@ -161,7 +164,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static long ConvertNanosecondsToMilliseconds(long time) { - time /= 1000000; + time /= NanosecondsPerMillisecond; if ((ulong)time > int.MaxValue) { @@ -173,18 +176,18 @@ namespace Ryujinx.HLE.HOS.Kernel.Common public static long ConvertMillisecondsToNanoseconds(long time) { - return time * 1000000; + return time * NanosecondsPerMillisecond; } public static long ConvertNanosecondsToHostTicks(long ns) { - long nsDiv = ns / 1000000000; - long nsMod = ns % 1000000000; - long tickDiv = PerformanceCounter.TicksPerSecond / 1000000000; - long tickMod = PerformanceCounter.TicksPerSecond % 1000000000; + long nsDiv = ns / NanosecondsPerSecond; + long nsMod = ns % NanosecondsPerSecond; + long tickDiv = PerformanceCounter.TicksPerSecond / NanosecondsPerSecond; + long tickMod = PerformanceCounter.TicksPerSecond % NanosecondsPerSecond; - long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / 1000000000; - return (nsDiv * tickDiv) * 1000000000 + nsDiv * tickMod + nsMod * tickDiv + baseTicks; + long baseTicks = (nsMod * tickMod + PerformanceCounter.TicksPerSecond - 1) / NanosecondsPerSecond; + return (nsDiv * tickDiv) * NanosecondsPerSecond + nsDiv * tickMod + nsMod * tickDiv + baseTicks; } public static long ConvertGuestTicksToNanoseconds(long ticks) diff --git a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs index eef78e186..c6467208e 100644 --- a/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs +++ b/Ryujinx.HLE/HOS/Kernel/SupervisorCall/Syscall.cs @@ -553,7 +553,7 @@ namespace Ryujinx.HLE.HOS.Kernel.SupervisorCall KProcess currentProcess = KernelStatic.GetCurrentProcess(); - KSynchronizationObject[] syncObjs = new KSynchronizationObject[handles.Length]; + KSynchronizationObject[] syncObjs = handles.Length == 0 ? Array.Empty() : new KSynchronizationObject[handles.Length]; for (int index = 0; index < handles.Length; index++) { diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs index 2c9d75742..14fba7045 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KPriorityQueue.cs @@ -5,11 +5,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { class KPriorityQueue { - private LinkedList[][] _scheduledThreadsPerPrioPerCore; - private LinkedList[][] _suggestedThreadsPerPrioPerCore; + private readonly LinkedList[][] _scheduledThreadsPerPrioPerCore; + private readonly LinkedList[][] _suggestedThreadsPerPrioPerCore; - private long[] _scheduledPrioritiesPerCore; - private long[] _suggestedPrioritiesPerCore; + private readonly long[] _scheduledPrioritiesPerCore; + private readonly long[] _suggestedPrioritiesPerCore; public KPriorityQueue() { @@ -32,43 +32,134 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading _suggestedPrioritiesPerCore = new long[KScheduler.CpuCoresCount]; } - public IEnumerable SuggestedThreads(int core) + public readonly ref struct KThreadEnumerable { - return Iterate(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); - } + readonly LinkedList[][] _listPerPrioPerCore; + readonly long[] _prios; + readonly int _core; - public IEnumerable ScheduledThreads(int core) - { - return Iterate(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core); - } - - private IEnumerable Iterate(LinkedList[][] listPerPrioPerCore, long[] prios, int core) - { - long prioMask = prios[core]; - - int prio = BitOperations.TrailingZeroCount(prioMask); - - prioMask &= ~(1L << prio); - - while (prio < KScheduler.PrioritiesCount) + public KThreadEnumerable(LinkedList[][] listPerPrioPerCore, long[] prios, int core) { - LinkedList list = listPerPrioPerCore[prio][core]; + _listPerPrioPerCore = listPerPrioPerCore; + _prios = prios; + _core = core; + } - LinkedListNode node = list.First; + public Enumerator GetEnumerator() + { + return new Enumerator(_listPerPrioPerCore, _prios, _core); + } - while (node != null) + public ref struct Enumerator + { + private readonly LinkedList[][] _listPerPrioPerCore; + private readonly int _core; + private long _prioMask; + private int _prio; + private LinkedList _list; + private LinkedListNode _node; + + public Enumerator(LinkedList[][] listPerPrioPerCore, long[] prios, int core) { - yield return node.Value; - - node = node.Next; + _listPerPrioPerCore = listPerPrioPerCore; + _core = core; + _prioMask = prios[core]; + _prio = BitOperations.TrailingZeroCount(_prioMask); + _prioMask &= ~(1L << _prio); } - prio = BitOperations.TrailingZeroCount(prioMask); + public KThread Current => _node?.Value; - prioMask &= ~(1L << prio); + public bool MoveNext() + { + _node = _node?.Next; + + if (_node == null) + { + if (!MoveNextListAndFirstNode()) + { + return false; + } + } + + return _node != null; + } + + private bool MoveNextListAndFirstNode() + { + if (_prio < KScheduler.PrioritiesCount) + { + _list = _listPerPrioPerCore[_prio][_core]; + + _node = _list.First; + + _prio = BitOperations.TrailingZeroCount(_prioMask); + + _prioMask &= ~(1L << _prio); + + return true; + } + else + { + _list = null; + _node = null; + return false; + } + } } } + public KThreadEnumerable ScheduledThreads(int core) + { + return new KThreadEnumerable(_scheduledThreadsPerPrioPerCore, _scheduledPrioritiesPerCore, core); + } + + public KThreadEnumerable SuggestedThreads(int core) + { + return new KThreadEnumerable(_suggestedThreadsPerPrioPerCore, _suggestedPrioritiesPerCore, core); + } + + public KThread ScheduledThreadsFirstOrDefault(int core) + { + return ScheduledThreadsElementAtOrDefault(core, 0); + } + + public KThread ScheduledThreadsElementAtOrDefault(int core, int index) + { + int currentIndex = 0; + foreach (var scheduledThread in ScheduledThreads(core)) + { + if (currentIndex == index) + { + return scheduledThread; + } + else + { + currentIndex++; + } + } + + return null; + } + + public KThread ScheduledThreadsWithDynamicPriorityFirstOrDefault(int core, int dynamicPriority) + { + foreach (var scheduledThread in ScheduledThreads(core)) + { + if (scheduledThread.DynamicPriority == dynamicPriority) + { + return scheduledThread; + } + } + + return null; + } + + public bool HasScheduledThreads(int core) + { + return ScheduledThreadsFirstOrDefault(core) != null; + } + public void TransferToCore(int prio, int dstCore, KThread thread) { int srcCore = thread.ActiveCore; diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs index 0c51b7b9a..b9de7d9c7 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KScheduler.cs @@ -1,8 +1,6 @@ using Ryujinx.Common; using Ryujinx.HLE.HOS.Kernel.Process; using System; -using System.Collections.Generic; -using System.Linq; using System.Numerics; using System.Threading; @@ -17,6 +15,8 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private static readonly int[] PreemptionPriorities = new int[] { 59, 59, 59, 63 }; + private static readonly int[] _srcCoresHighestPrioThreads = new int[CpuCoresCount]; + private readonly KernelContext _context; private readonly int _coreId; @@ -86,7 +86,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading for (int core = 0; core < CpuCoresCount; core++) { - KThread thread = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); + KThread thread = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core); if (thread != null && thread.Owner != null && @@ -115,12 +115,12 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading { // If the core is not idle (there's already a thread running on it), // then we don't need to attempt load balancing. - if (context.PriorityQueue.ScheduledThreads(core).Any()) + if (context.PriorityQueue.HasScheduledThreads(core)) { continue; } - int[] srcCoresHighestPrioThreads = new int[CpuCoresCount]; + Array.Fill(_srcCoresHighestPrioThreads, 0); int srcCoresHighestPrioThreadsCount = 0; @@ -136,7 +136,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading break; } - srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore; + _srcCoresHighestPrioThreads[srcCoresHighestPrioThreadsCount++] = suggested.ActiveCore; } // Not yet selected candidate found. @@ -158,9 +158,9 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // (the first one that doesn't make the source core idle if moved). for (int index = 0; index < srcCoresHighestPrioThreadsCount; index++) { - int srcCore = srcCoresHighestPrioThreads[index]; + int srcCore = _srcCoresHighestPrioThreads[index]; - KThread src = context.PriorityQueue.ScheduledThreads(srcCore).ElementAtOrDefault(1); + KThread src = context.PriorityQueue.ScheduledThreadsElementAtOrDefault(srcCore, 1); if (src != null) { @@ -422,9 +422,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading private static void RotateScheduledQueue(KernelContext context, int core, int prio) { - IEnumerable scheduledThreads = context.PriorityQueue.ScheduledThreads(core); - - KThread selectedThread = scheduledThreads.FirstOrDefault(x => x.DynamicPriority == prio); + KThread selectedThread = context.PriorityQueue.ScheduledThreadsWithDynamicPriorityFirstOrDefault(core, prio); KThread nextThread = null; // Yield priority queue. @@ -433,14 +431,14 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading nextThread = context.PriorityQueue.Reschedule(prio, core, selectedThread); } - IEnumerable SuitableCandidates() + static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread selectedThread, KThread nextThread, Predicate< KThread> predicate) { foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) { int suggestedCore = suggested.ActiveCore; if (suggestedCore >= 0) { - KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault(); + KThread selectedSuggestedCore = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore); if (selectedSuggestedCore == suggested || (selectedSuggestedCore != null && selectedSuggestedCore.DynamicPriority < 2)) { @@ -453,14 +451,19 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading nextThread == null || nextThread.LastScheduledTime >= suggested.LastScheduledTime) { - yield return suggested; + if (predicate(suggested)) + { + return suggested; + } } } + + return null; } // Select candidate threads that could run on this core. // Only take into account threads that are not yet selected. - KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority == prio); + KThread dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority == prio); if (dst != null) { @@ -469,11 +472,11 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // If the priority of the currently selected thread is lower or same as the preemption priority, // then try to migrate a thread with lower priority. - KThread bestCandidate = context.PriorityQueue.ScheduledThreads(core).FirstOrDefault(); + KThread bestCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(core); if (bestCandidate != null && bestCandidate.DynamicPriority >= prio) { - dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority < bestCandidate.DynamicPriority); + dst = FirstSuitableCandidateOrDefault(context, core, selectedThread, nextThread, x => x.DynamicPriority < bestCandidate.DynamicPriority); if (dst != null) { @@ -534,7 +537,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading // Move current thread to the end of the queue. KThread nextThread = context.PriorityQueue.Reschedule(prio, core, currentThread); - IEnumerable SuitableCandidates() + static KThread FirstSuitableCandidateOrDefault(KernelContext context, int core, KThread nextThread, int lessThanOrEqualPriority) { foreach (KThread suggested in context.PriorityQueue.SuggestedThreads(core)) { @@ -554,12 +557,17 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading if (suggested.LastScheduledTime <= nextThread.LastScheduledTime || suggested.DynamicPriority < nextThread.DynamicPriority) { - yield return suggested; + if (suggested.DynamicPriority <= lessThanOrEqualPriority) + { + return suggested; + } } } + + return null; } - KThread dst = SuitableCandidates().FirstOrDefault(x => x.DynamicPriority <= prio); + KThread dst = FirstSuitableCandidateOrDefault(context, core, nextThread, prio); if (dst != null) { @@ -596,7 +604,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading context.PriorityQueue.TransferToCore(currentThread.DynamicPriority, -1, currentThread); - if (!context.PriorityQueue.ScheduledThreads(core).Any()) + if (!context.PriorityQueue.HasScheduledThreads(core)) { KThread selectedThread = null; @@ -609,7 +617,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading continue; } - KThread firstCandidate = context.PriorityQueue.ScheduledThreads(suggestedCore).FirstOrDefault(); + KThread firstCandidate = context.PriorityQueue.ScheduledThreadsFirstOrDefault(suggestedCore); if (firstCandidate == suggested) { diff --git a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs index 01b65f55e..973d5f6a4 100644 --- a/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs +++ b/Ryujinx.HLE/HOS/Kernel/Threading/KSynchronization.cs @@ -59,7 +59,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Threading } else { - LinkedListNode[] syncNodes = new LinkedListNode[syncObjs.Length]; + LinkedListNode[] syncNodes = syncObjs.Length == 0 ? Array.Empty>() : new LinkedListNode[syncObjs.Length]; for (int index = 0; index < syncObjs.Length; index++) { diff --git a/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs index 227cfdae1..49e342f27 100644 --- a/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs +++ b/Ryujinx.HLE/HOS/Services/Am/AppletAE/Storage/StorageHelper.cs @@ -1,4 +1,5 @@ -using Ryujinx.HLE.HOS.Services.Account.Acc; +using Ryujinx.Common.Memory; +using Ryujinx.HLE.HOS.Services.Account.Acc; using System.IO; namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage @@ -10,7 +11,7 @@ namespace Ryujinx.HLE.HOS.Services.Am.AppletAE.Storage public static byte[] MakeLaunchParams(UserProfile userProfile) { // Size needs to be at least 0x88 bytes otherwise application errors. - using (MemoryStream ms = new MemoryStream()) + using (MemoryStream ms = MemoryStreamManager.Shared.GetStream()) { BinaryWriter writer = new BinaryWriter(ms); diff --git a/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs b/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs index 66a69a8be..fef82cbc4 100644 --- a/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs +++ b/Ryujinx.HLE/HOS/Services/Sdb/Pl/SharedFontManager.cs @@ -5,6 +5,7 @@ using LibHac.FsSystem; using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Memory; using Ryujinx.HLE.Exceptions; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Kernel.Memory; @@ -160,7 +161,7 @@ namespace Ryujinx.HLE.HOS.Services.Sdb.Pl static uint KXor(uint data) => data ^ FontKey; using (BinaryReader reader = new BinaryReader(bfttfStream)) - using (MemoryStream ttfStream = new MemoryStream()) + using (MemoryStream ttfStream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter output = new BinaryWriter(ttfStream)) { if (KXor(reader.ReadUInt32()) != BFTTFMagic) diff --git a/Ryujinx.HLE/HOS/Services/ServerBase.cs b/Ryujinx.HLE/HOS/Services/ServerBase.cs index d4382a643..619f54483 100644 --- a/Ryujinx.HLE/HOS/Services/ServerBase.cs +++ b/Ryujinx.HLE/HOS/Services/ServerBase.cs @@ -1,3 +1,5 @@ +using Ryujinx.Common; +using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS.Ipc; using Ryujinx.HLE.HOS.Kernel; using Ryujinx.HLE.HOS.Kernel.Ipc; @@ -5,6 +7,7 @@ using Ryujinx.HLE.HOS.Kernel.Process; using Ryujinx.HLE.HOS.Kernel.Threading; using Ryujinx.Horizon.Common; using System; +using System.Buffers; using System.Buffers.Binary; using System.Collections.Generic; using System.IO; @@ -37,14 +40,27 @@ namespace Ryujinx.HLE.HOS.Services private readonly Dictionary _sessions = new Dictionary(); private readonly Dictionary> _ports = new Dictionary>(); + private readonly MemoryStream _requestDataStream; + private readonly BinaryReader _requestDataReader; + + private readonly MemoryStream _responseDataStream; + private readonly BinaryWriter _responseDataWriter; + public ManualResetEvent InitDone { get; } public string Name { get; } public Func SmObjectFactory { get; } public ServerBase(KernelContext context, string name, Func smObjectFactory = null) { - InitDone = new ManualResetEvent(false); _context = context; + + _requestDataStream = MemoryStreamManager.Shared.GetStream(); + _requestDataReader = new BinaryReader(_requestDataStream); + + _responseDataStream = MemoryStreamManager.Shared.GetStream(); + _responseDataWriter = new BinaryWriter(_responseDataStream); + + InitDone = new ManualResetEvent(false); Name = name; SmObjectFactory = smObjectFactory; @@ -110,15 +126,15 @@ namespace Ryujinx.HLE.HOS.Services while (true) { - int[] portHandles = _portHandles.ToArray(); - int[] sessionHandles = _sessionHandles.ToArray(); - int[] handles = new int[portHandles.Length + sessionHandles.Length]; + int handleCount = _portHandles.Count + _sessionHandles.Count; - portHandles.CopyTo(handles, 0); - sessionHandles.CopyTo(handles, portHandles.Length); + int[] handles = ArrayPool.Shared.Rent(handleCount); + + _portHandles.CopyTo(handles, 0); + _sessionHandles.CopyTo(handles, _portHandles.Count); // We still need a timeout here to allow the service to pick up and listen new sessions... - var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles, replyTargetHandle, 1000000L); + var rc = _context.Syscall.ReplyAndReceive(out int signaledIndex, handles.AsSpan(0, handleCount), replyTargetHandle, 1000000L); thread.HandlePostSyscall(); @@ -129,7 +145,7 @@ namespace Ryujinx.HLE.HOS.Services replyTargetHandle = 0; - if (rc == Result.Success && signaledIndex >= portHandles.Length) + if (rc == Result.Success && signaledIndex >= _portHandles.Count) { // We got a IPC request, process it, pass to the appropriate service if needed. int signaledHandle = handles[signaledIndex]; @@ -156,6 +172,8 @@ namespace Ryujinx.HLE.HOS.Services _selfProcess.CpuMemory.Write(messagePtr + 0x4, 2 << 10); _selfProcess.CpuMemory.Write(messagePtr + 0x8, heapAddr | ((ulong)PointerBufferSize << 48)); } + + ArrayPool.Shared.Return(handles); } Dispose(); @@ -166,13 +184,9 @@ namespace Ryujinx.HLE.HOS.Services KProcess process = KernelStatic.GetCurrentProcess(); KThread thread = KernelStatic.GetCurrentThread(); ulong messagePtr = thread.TlsAddress; - ulong messageSize = 0x100; - byte[] reqData = new byte[messageSize]; + IpcMessage request = ReadRequest(process, messagePtr); - process.CpuMemory.Read(messagePtr, reqData); - - IpcMessage request = new IpcMessage(reqData, (long)messagePtr); IpcMessage response = new IpcMessage(); ulong tempAddr = recvListAddr; @@ -202,158 +216,157 @@ namespace Ryujinx.HLE.HOS.Services bool shouldReply = true; bool isTipcCommunication = false; - using (MemoryStream raw = new MemoryStream(request.RawData)) + _requestDataStream.SetLength(0); + _requestDataStream.Write(request.RawData); + _requestDataStream.Position = 0; + + if (request.Type == IpcMessageType.HipcRequest || + request.Type == IpcMessageType.HipcRequestWithContext) { - BinaryReader reqReader = new BinaryReader(raw); + response.Type = IpcMessageType.HipcResponse; - if (request.Type == IpcMessageType.HipcRequest || - request.Type == IpcMessageType.HipcRequestWithContext) - { - response.Type = IpcMessageType.HipcResponse; + _responseDataStream.SetLength(0); - using (MemoryStream resMs = new MemoryStream()) - { - BinaryWriter resWriter = new BinaryWriter(resMs); + ServiceCtx context = new ServiceCtx( + _context.Device, + process, + process.CpuMemory, + thread, + request, + response, + _requestDataReader, + _responseDataWriter); - ServiceCtx context = new ServiceCtx( - _context.Device, - process, - process.CpuMemory, - thread, - request, - response, - reqReader, - resWriter); + _sessions[serverSessionHandle].CallHipcMethod(context); - _sessions[serverSessionHandle].CallHipcMethod(context); - - response.RawData = resMs.ToArray(); - } - } - else if (request.Type == IpcMessageType.HipcControl || - request.Type == IpcMessageType.HipcControlWithContext) - { - uint magic = (uint)reqReader.ReadUInt64(); - uint cmdId = (uint)reqReader.ReadUInt64(); - - switch (cmdId) - { - case 0: - request = FillResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain()); - break; - - case 3: - request = FillResponse(response, 0, PointerBufferSize); - break; - - // TODO: Whats the difference between IpcDuplicateSession/Ex? - case 2: - case 4: - int unknown = reqReader.ReadInt32(); - - _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0); - - AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]); - - response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle); - - request = FillResponse(response, 0); - - break; - - default: throw new NotImplementedException(cmdId.ToString()); - } - } - else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession) - { - _context.Syscall.CloseHandle(serverSessionHandle); - _sessionHandles.Remove(serverSessionHandle); - IpcService service = _sessions[serverSessionHandle]; - if (service is IDisposable disposableObj) - { - disposableObj.Dispose(); - } - _sessions.Remove(serverSessionHandle); - shouldReply = false; - } - // If the type is past 0xF, we are using TIPC - else if (request.Type > IpcMessageType.TipcCloseSession) - { - isTipcCommunication = true; - - // Response type is always the same as request on TIPC. - response.Type = request.Type; - - using (MemoryStream resMs = new MemoryStream()) - { - BinaryWriter resWriter = new BinaryWriter(resMs); - - ServiceCtx context = new ServiceCtx( - _context.Device, - process, - process.CpuMemory, - thread, - request, - response, - reqReader, - resWriter); - - _sessions[serverSessionHandle].CallTipcMethod(context); - - response.RawData = resMs.ToArray(); - } - - process.CpuMemory.Write(messagePtr, response.GetBytesTipc()); - } - else - { - throw new NotImplementedException(request.Type.ToString()); - } - - if (!isTipcCommunication) - { - process.CpuMemory.Write(messagePtr, response.GetBytes((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48))); - } - - return shouldReply; + response.RawData = _responseDataStream.ToArray(); } + else if (request.Type == IpcMessageType.HipcControl || + request.Type == IpcMessageType.HipcControlWithContext) + { + uint magic = (uint)_requestDataReader.ReadUInt64(); + uint cmdId = (uint)_requestDataReader.ReadUInt64(); + + switch (cmdId) + { + case 0: + FillHipcResponse(response, 0, _sessions[serverSessionHandle].ConvertToDomain()); + break; + + case 3: + FillHipcResponse(response, 0, PointerBufferSize); + break; + + // TODO: Whats the difference between IpcDuplicateSession/Ex? + case 2: + case 4: + int unknown = _requestDataReader.ReadInt32(); + + _context.Syscall.CreateSession(out int dupServerSessionHandle, out int dupClientSessionHandle, false, 0); + + AddSessionObj(dupServerSessionHandle, _sessions[serverSessionHandle]); + + response.HandleDesc = IpcHandleDesc.MakeMove(dupClientSessionHandle); + + FillHipcResponse(response, 0); + + break; + + default: throw new NotImplementedException(cmdId.ToString()); + } + } + else if (request.Type == IpcMessageType.HipcCloseSession || request.Type == IpcMessageType.TipcCloseSession) + { + _context.Syscall.CloseHandle(serverSessionHandle); + _sessionHandles.Remove(serverSessionHandle); + IpcService service = _sessions[serverSessionHandle]; + (service as IDisposable)?.Dispose(); + _sessions.Remove(serverSessionHandle); + shouldReply = false; + } + // If the type is past 0xF, we are using TIPC + else if (request.Type > IpcMessageType.TipcCloseSession) + { + isTipcCommunication = true; + + // Response type is always the same as request on TIPC. + response.Type = request.Type; + + _responseDataStream.SetLength(0); + + ServiceCtx context = new ServiceCtx( + _context.Device, + process, + process.CpuMemory, + thread, + request, + response, + _requestDataReader, + _responseDataWriter); + + _sessions[serverSessionHandle].CallTipcMethod(context); + + response.RawData = _responseDataStream.ToArray(); + + using var responseStream = response.GetStreamTipc(); + process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence()); + } + else + { + throw new NotImplementedException(request.Type.ToString()); + } + + if (!isTipcCommunication) + { + using var responseStream = response.GetStream((long)messagePtr, recvListAddr | ((ulong)PointerBufferSize << 48)); + process.CpuMemory.Write(messagePtr, responseStream.GetReadOnlySequence()); + } + + return shouldReply; } - private static IpcMessage FillResponse(IpcMessage response, long result, params int[] values) + private static IpcMessage ReadRequest(KProcess process, ulong messagePtr) { - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); + const int messageSize = 0x100; - foreach (int value in values) - { - writer.Write(value); - } + byte[] reqData = ArrayPool.Shared.Rent(messageSize); - return FillResponse(response, result, ms.ToArray()); - } + Span reqDataSpan = reqData.AsSpan(0, messageSize); + reqDataSpan.Clear(); + + process.CpuMemory.Read(messagePtr, reqDataSpan); + + IpcMessage request = new IpcMessage(reqDataSpan, (long)messagePtr); + + ArrayPool.Shared.Return(reqData); + + return request; } - private static IpcMessage FillResponse(IpcMessage response, long result, byte[] data = null) + private void FillHipcResponse(IpcMessage response, long result) + { + FillHipcResponse(response, result, ReadOnlySpan.Empty); + } + + private void FillHipcResponse(IpcMessage response, long result, int value) + { + Span span = stackalloc byte[sizeof(int)]; + BinaryPrimitives.WriteInt32LittleEndian(span, value); + FillHipcResponse(response, result, span); + } + + private void FillHipcResponse(IpcMessage response, long result, ReadOnlySpan data) { response.Type = IpcMessageType.HipcResponse; - using (MemoryStream ms = new MemoryStream()) - { - BinaryWriter writer = new BinaryWriter(ms); + _responseDataStream.SetLength(0); - writer.Write(IpcMagic.Sfco); - writer.Write(result); + _responseDataStream.Write(IpcMagic.Sfco); + _responseDataStream.Write(result); - if (data != null) - { - writer.Write(data); - } + _responseDataStream.Write(data); - response.RawData = ms.ToArray(); - } - - return response; + response.RawData = _responseDataStream.ToArray(); } protected virtual void Dispose(bool disposing) @@ -372,6 +385,11 @@ namespace Ryujinx.HLE.HOS.Services _sessions.Clear(); + _requestDataReader.Dispose(); + _requestDataStream.Dispose(); + _responseDataWriter.Dispose(); + _responseDataStream.Dispose(); + InitDone.Dispose(); } } diff --git a/Ryujinx.HLE/Utilities/StringUtils.cs b/Ryujinx.HLE/Utilities/StringUtils.cs index a64d451c1..1810b1ad7 100644 --- a/Ryujinx.HLE/Utilities/StringUtils.cs +++ b/Ryujinx.HLE/Utilities/StringUtils.cs @@ -1,4 +1,6 @@ using LibHac.Common; +using Microsoft.IO; +using Ryujinx.Common.Memory; using Ryujinx.HLE.HOS; using System; using System.Globalization; @@ -77,7 +79,7 @@ namespace Ryujinx.HLE.Utilities ulong position = context.Request.PtrBuff[index].Position; ulong size = context.Request.PtrBuff[index].Size; - using (MemoryStream ms = new MemoryStream()) + using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream()) { while (size-- > 0) { @@ -91,7 +93,7 @@ namespace Ryujinx.HLE.Utilities ms.WriteByte(value); } - return Encoding.UTF8.GetString(ms.ToArray()); + return Encoding.UTF8.GetString(ms.GetReadOnlySequence()); } } @@ -110,7 +112,7 @@ namespace Ryujinx.HLE.Utilities ulong position = context.Request.SendBuff[index].Position; ulong size = context.Request.SendBuff[index].Size; - using (MemoryStream ms = new MemoryStream()) + using (RecyclableMemoryStream ms = MemoryStreamManager.Shared.GetStream()) { while (size-- > 0) { @@ -124,7 +126,7 @@ namespace Ryujinx.HLE.Utilities ms.WriteByte(value); } - return Encoding.UTF8.GetString(ms.ToArray()); + return Encoding.UTF8.GetString(ms.GetReadOnlySequence()); } } diff --git a/Ryujinx.Input/Motion/CemuHook/Client.cs b/Ryujinx.Input/Motion/CemuHook/Client.cs index f5f7b8643..4498b8ca6 100644 --- a/Ryujinx.Input/Motion/CemuHook/Client.cs +++ b/Ryujinx.Input/Motion/CemuHook/Client.cs @@ -3,6 +3,7 @@ using Ryujinx.Common.Configuration.Hid; using Ryujinx.Common.Configuration.Hid.Controller; using Ryujinx.Common.Configuration.Hid.Controller.Motion; using Ryujinx.Common.Logging; +using Ryujinx.Common.Memory; using Ryujinx.Input.HLE; using Ryujinx.Input.Motion.CemuHook.Protocol; using System; @@ -381,7 +382,7 @@ namespace Ryujinx.Input.Motion.CemuHook Header header = GenerateHeader(clientId); - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.WriteStruct(header); @@ -421,7 +422,7 @@ namespace Ryujinx.Input.Motion.CemuHook Header header = GenerateHeader(clientId); - using (MemoryStream stream = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) using (BinaryWriter writer = new BinaryWriter(stream)) { writer.WriteStruct(header); diff --git a/Ryujinx.Memory/IVirtualMemoryManager.cs b/Ryujinx.Memory/IVirtualMemoryManager.cs index e1851d48b..edbfc8855 100644 --- a/Ryujinx.Memory/IVirtualMemoryManager.cs +++ b/Ryujinx.Memory/IVirtualMemoryManager.cs @@ -1,5 +1,6 @@ using Ryujinx.Memory.Range; using System; +using System.Buffers; using System.Collections.Generic; namespace Ryujinx.Memory @@ -77,6 +78,21 @@ namespace Ryujinx.Memory /// Throw for unhandled invalid or unmapped memory accesses void Write(ulong va, ReadOnlySpan data); + /// + /// Writes data to CPU mapped memory, with write tracking. + /// + /// Virtual address to write the data into + /// Data to be written + /// Throw for unhandled invalid or unmapped memory accesses + public void Write(ulong va, ReadOnlySequence data) + { + foreach (ReadOnlyMemory segment in data) + { + Write(va, segment.Span); + va += (ulong)segment.Length; + } + } + /// /// Writes data to the application process, returning false if the data was not changed. /// This triggers read memory tracking, as a redundancy check would be useless if the data is not up to date. diff --git a/Ryujinx/Ui/Windows/AvatarWindow.cs b/Ryujinx/Ui/Windows/AvatarWindow.cs index fc928bde2..0cda890f5 100644 --- a/Ryujinx/Ui/Windows/AvatarWindow.cs +++ b/Ryujinx/Ui/Windows/AvatarWindow.cs @@ -6,6 +6,7 @@ using LibHac.FsSystem; using LibHac.Ncm; using LibHac.Tools.FsSystem; using LibHac.Tools.FsSystem.NcaUtils; +using Ryujinx.Common.Memory; using Ryujinx.HLE.FileSystem; using Ryujinx.Ui.Common.Configuration; using SixLabors.ImageSharp; @@ -136,8 +137,8 @@ namespace Ryujinx.Ui.Windows romfs.OpenFile(ref file.Ref, ("/" + item.FullPath).ToU8Span(), OpenMode.Read).ThrowIfFailure(); - using (MemoryStream stream = new MemoryStream()) - using (MemoryStream streamPng = new MemoryStream()) + using (MemoryStream stream = MemoryStreamManager.Shared.GetStream()) + using (MemoryStream streamPng = MemoryStreamManager.Shared.GetStream()) { file.Get.AsStream().CopyTo(stream); @@ -169,7 +170,7 @@ namespace Ryujinx.Ui.Windows private byte[] ProcessImage(byte[] data) { - using (MemoryStream streamJpg = new MemoryStream()) + using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream()) { Image avatarImage = Image.Load(data, new PngDecoder()); diff --git a/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs b/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs index 495c1d1fa..a08b5dd17 100644 --- a/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs +++ b/Ryujinx/Ui/Windows/UserProfilesManagerWindow.cs @@ -1,4 +1,5 @@ using Gtk; +using Ryujinx.Common.Memory; using Ryujinx.HLE.FileSystem; using Ryujinx.HLE.HOS.Services.Account.Acc; using Ryujinx.Ui.Common.Configuration; @@ -181,7 +182,7 @@ namespace Ryujinx.Ui.Windows { image.Mutate(x => x.Resize(256, 256)); - using (MemoryStream streamJpg = new MemoryStream()) + using (MemoryStream streamJpg = MemoryStreamManager.Shared.GetStream()) { image.SaveAsJpeg(streamJpg);