From 5131b71437b36f9b876046a2fddd5465652e0f10 Mon Sep 17 00:00:00 2001 From: jhorv <38920027+jhorv@users.noreply.github.com> Date: Fri, 17 Mar 2023 08:14:50 -0400 Subject: [PATCH] Reducing memory allocations (#4537) * add RecyclableMemoryStream dependency and MemoryStreamManager * organize BinaryReader/BinaryWriter extensions * add StreamExtensions to reduce need for BinaryWriter * simple replacments of MemoryStream with RecyclableMemoryStream * add write ReadOnlySequence support to IVirtualMemoryManager * avoid 0-length array creation * rework IpcMessage and related types to greatly reduce memory allocation by using RecylableMemoryStream, keeping streams around longer, avoiding their creation when possible, and avoiding creation of BinaryReader and BinaryWriter when possible * reduce LINQ-induced memory allocations with custom methods to query KPriorityQueue * use RecyclableMemoryStream in StreamUtils, and use StreamUtils in EmbeddedResources * add constants for nanosecond/millisecond conversions * code formatting * XML doc adjustments * fix: StreamExtension.WriteByte not writing non-zero values for lengths <= 16 * XML Doc improvements. Implement StreamExtensions.WriteByte() block writes for large-enough count values. * add copyless path for StreamExtension.Write(ReadOnlySpan) * add default implementation of IVirtualMemoryManager.Write(ulong, ReadOnlySequence); remove previous explicit implementations * code style fixes * remove LINQ completely from KScheduler/KPriorityQueue by implementing a custom struct-based enumerator --- ARMeilleure/CodeGen/Arm64/CodeGenContext.cs | 3 +- ARMeilleure/CodeGen/X86/Assembler.cs | 3 +- ARMeilleure/CodeGen/X86/CodeGenContext.cs | 3 +- ARMeilleure/Translation/PTC/Ptc.cs | 7 +- ARMeilleure/Translation/PTC/PtcProfiler.cs | 5 +- Directory.Packages.props | 1 + .../Extensions/BinaryReaderExtensions.cs | 14 - .../Extensions/BinaryWriterExtensions.cs | 28 ++ Ryujinx.Common/Extensions/StreamExtensions.cs | 138 ++++++++ Ryujinx.Common/Memory/MemoryStreamManager.cs | 99 ++++++ Ryujinx.Common/Ryujinx.Common.csproj | 1 + Ryujinx.Common/Utilities/EmbeddedResources.cs | 16 +- Ryujinx.Common/Utilities/StreamUtils.cs | 18 +- Ryujinx.Cpu/MemoryHelper.cs | 8 +- .../DiskCache/ShaderBinarySerializer.cs | 15 +- Ryujinx.HLE/FileSystem/ContentManager.cs | 11 +- .../HOS/Applets/Browser/BrowserApplet.cs | 5 +- .../Applets/Controller/ControllerApplet.cs | 5 +- .../PlayerSelect/PlayerSelectApplet.cs | 5 +- Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs | 69 ++-- Ryujinx.HLE/HOS/Ipc/IpcMessage.cs | 213 ++++++------ Ryujinx.HLE/HOS/Ipc/IpcRecvListBuffDesc.cs | 8 +- Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 25 +- .../HOS/Kernel/SupervisorCall/Syscall.cs | 2 +- .../HOS/Kernel/Threading/KPriorityQueue.cs | 149 +++++++-- .../HOS/Kernel/Threading/KScheduler.cs | 52 +-- .../HOS/Kernel/Threading/KSynchronization.cs | 2 +- .../Am/AppletAE/Storage/StorageHelper.cs | 5 +- .../HOS/Services/Sdb/Pl/SharedFontManager.cs | 3 +- Ryujinx.HLE/HOS/Services/ServerBase.cs | 310 +++++++++--------- Ryujinx.HLE/Utilities/StringUtils.cs | 10 +- Ryujinx.Input/Motion/CemuHook/Client.cs | 5 +- Ryujinx.Memory/IVirtualMemoryManager.cs | 16 + Ryujinx/Ui/Windows/AvatarWindow.cs | 7 +- .../Ui/Windows/UserProfilesManagerWindow.cs | 3 +- 35 files changed, 838 insertions(+), 426 deletions(-) create mode 100644 Ryujinx.Common/Extensions/BinaryWriterExtensions.cs create mode 100644 Ryujinx.Common/Extensions/StreamExtensions.cs create mode 100644 Ryujinx.Common/Memory/MemoryStreamManager.cs diff --git a/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs index cebfbde121..0dd5355f42 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 c15deadc53..2ea4208b33 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 eee71bd78a..899487241c 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 276ec788b9..0b23fd043b 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 030ccff5fa..391e29c76f 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 eda7d395ad..14d440753b 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 ea404d2a22..21da6fc012 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 0000000000..fddc8c1b74 --- /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 0000000000..f6fc870abf --- /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 0000000000..68b8299938 --- /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 c307f524ed..c02b11e0ce 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 e7c8d7d701..22b55f1617 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 9bd03ec907..da97188d8b 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 64ff360e52..194a0c35de 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 5b430e1af6..77e526672c 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 4e39400815..9facdd0b77 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 2d5509ff8d..952afcd5f6 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 5cdfb31438..5d5a26c23b 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 cec9f213ea..a8119a470b 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 e6ed46138e..c7ef7e9cfa 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 55044da404..4e8f2fbfd1 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 10406ac7d8..bcc9d8f890 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 030a314f7e..1af171b926 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 eef78e186f..c6467208eb 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 2c9d757427..14fba70456 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 0c51b7b9a3..b9de7d9c74 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 01b65f55ea..973d5f6a41 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 227cfdae12..49e342f271 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 66a69a8bed..fef82cbc46 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 d4382a6433..619f544835 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 a64d451c1e..1810b1ad76 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 f5f7b86431..4498b8ca67 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 e1851d48b8..edbfc88557 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 fc928bde29..0cda890f51 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 495c1d1fa2..a08b5dd17b 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);