diff options
| author | jhorv <38920027+jhorv@users.noreply.github.com> | 2024-04-14 16:06:14 -0400 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-04-14 17:06:14 -0300 |
| commit | 268c9aecf8e9181bb7114cf1dd826f00b2237714 (patch) | |
| tree | 7e14ab6cde7c7edaf99bd2151abfb73d5b00f103 /src/Ryujinx.Common | |
| parent | e916662b0f17b93d8987d481784cd45073335990 (diff) | |
Texture loading: reduce memory allocations (#6623)
* rebase
* add methods Ryyjinx.Common EmbeddedResources and SteamUtils
* GAL changes - change SetData() methods and ThreadedTexture commands to use IMemoryOwner<byte> instead of SpanOrArray<byte>
* Ryujinx.Graphics.Texture: change texture conversion methods to return IMemoryOwner<byte> and allocate from ByteMemoryPool
* Ryujinx.Graphics.OpenGL: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>
* Ryujinx.Graphics.Vulkan: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>
* Ryujinx.Graphics.Gpu: update ITexture and Texture-like types with SetData() methods to take IMemoryOwner<byte> instead of SpanOrArray<byte>
* Remove now-unused SpanOrArray<T>
* post-rebase cleanup
* PixelConverter: remove unsafe modifier on safe methods, and remove one unnecessary cast
* use ByteMemoryPool.Rent() in GetWritableRegion() impls
* fix formatting, rename `ReadRentedMemory()` to `ReadFileToRentedMemory()``
* Texture.ConvertToHostCompatibleFormat(): dispose of `result` in Astc decode branch
Diffstat (limited to 'src/Ryujinx.Common')
| -rw-r--r-- | src/Ryujinx.Common/Memory/SpanOrArray.cs | 89 | ||||
| -rw-r--r-- | src/Ryujinx.Common/Utilities/EmbeddedResources.cs | 17 | ||||
| -rw-r--r-- | src/Ryujinx.Common/Utilities/StreamUtils.cs | 67 |
3 files changed, 81 insertions, 92 deletions
diff --git a/src/Ryujinx.Common/Memory/SpanOrArray.cs b/src/Ryujinx.Common/Memory/SpanOrArray.cs deleted file mode 100644 index 269ac02f..00000000 --- a/src/Ryujinx.Common/Memory/SpanOrArray.cs +++ /dev/null @@ -1,89 +0,0 @@ -using System; - -namespace Ryujinx.Common.Memory -{ - /// <summary> - /// A struct that can represent both a Span and Array. - /// This is useful to keep the Array representation when possible to avoid copies. - /// </summary> - /// <typeparam name="T">Element Type</typeparam> - public readonly ref struct SpanOrArray<T> where T : unmanaged - { - public readonly T[] Array; - public readonly ReadOnlySpan<T> Span; - - /// <summary> - /// Create a new SpanOrArray from an array. - /// </summary> - /// <param name="array">Array to store</param> - public SpanOrArray(T[] array) - { - Array = array; - Span = ReadOnlySpan<T>.Empty; - } - - /// <summary> - /// Create a new SpanOrArray from a readonly span. - /// </summary> - /// <param name="array">Span to store</param> - public SpanOrArray(ReadOnlySpan<T> span) - { - Array = null; - Span = span; - } - - /// <summary> - /// Return the contained array, or convert the span if necessary. - /// </summary> - /// <returns>An array containing the data</returns> - public T[] ToArray() - { - return Array ?? Span.ToArray(); - } - - /// <summary> - /// Return a ReadOnlySpan from either the array or ReadOnlySpan. - /// </summary> - /// <returns>A ReadOnlySpan containing the data</returns> - public ReadOnlySpan<T> AsSpan() - { - return Array ?? Span; - } - - /// <summary> - /// Cast an array to a SpanOrArray. - /// </summary> - /// <param name="array">Source array</param> - public static implicit operator SpanOrArray<T>(T[] array) - { - return new SpanOrArray<T>(array); - } - - /// <summary> - /// Cast a ReadOnlySpan to a SpanOrArray. - /// </summary> - /// <param name="span">Source ReadOnlySpan</param> - public static implicit operator SpanOrArray<T>(ReadOnlySpan<T> span) - { - return new SpanOrArray<T>(span); - } - - /// <summary> - /// Cast a Span to a SpanOrArray. - /// </summary> - /// <param name="span">Source Span</param> - public static implicit operator SpanOrArray<T>(Span<T> span) - { - return new SpanOrArray<T>(span); - } - - /// <summary> - /// Cast a SpanOrArray to a ReadOnlySpan - /// </summary> - /// <param name="spanOrArray">Source SpanOrArray</param> - public static implicit operator ReadOnlySpan<T>(SpanOrArray<T> spanOrArray) - { - return spanOrArray.AsSpan(); - } - } -} diff --git a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs index a4facc2e..e22571c9 100644 --- a/src/Ryujinx.Common/Utilities/EmbeddedResources.cs +++ b/src/Ryujinx.Common/Utilities/EmbeddedResources.cs @@ -1,5 +1,6 @@ using Ryujinx.Common.Utilities; using System; +using System.Buffers; using System.IO; using System.Linq; using System.Reflection; @@ -41,6 +42,22 @@ namespace Ryujinx.Common return StreamUtils.StreamToBytes(stream); } + public static IMemoryOwner<byte> ReadFileToRentedMemory(string filename) + { + var (assembly, path) = ResolveManifestPath(filename); + + return ReadFileToRentedMemory(assembly, path); + } + + public static IMemoryOwner<byte> ReadFileToRentedMemory(Assembly assembly, string filename) + { + using var stream = GetStream(assembly, filename); + + return stream is null + ? null + : StreamUtils.StreamToRentedMemory(stream); + } + public async static Task<byte[]> ReadAsync(Assembly assembly, string filename) { using var stream = GetStream(assembly, filename); diff --git a/src/Ryujinx.Common/Utilities/StreamUtils.cs b/src/Ryujinx.Common/Utilities/StreamUtils.cs index 7a20c98e..74b6af5e 100644 --- a/src/Ryujinx.Common/Utilities/StreamUtils.cs +++ b/src/Ryujinx.Common/Utilities/StreamUtils.cs @@ -1,4 +1,6 @@ +using Microsoft.IO; using Ryujinx.Common.Memory; +using System.Buffers; using System.IO; using System.Threading; using System.Threading.Tasks; @@ -9,12 +11,50 @@ namespace Ryujinx.Common.Utilities { public static byte[] StreamToBytes(Stream input) { - using MemoryStream stream = MemoryStreamManager.Shared.GetStream(); + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + return output.ToArray(); + } - input.CopyTo(stream); + public static IMemoryOwner<byte> StreamToRentedMemory(Stream input) + { + if (input is MemoryStream inputMemoryStream) + { + return MemoryStreamToRentedMemory(inputMemoryStream); + } + else if (input.CanSeek) + { + long bytesExpected = input.Length; - return stream.ToArray(); + IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(bytesExpected); + + var destSpan = ownedMemory.Memory.Span; + + int totalBytesRead = 0; + + while (totalBytesRead < bytesExpected) + { + int bytesRead = input.Read(destSpan[totalBytesRead..]); + + if (bytesRead == 0) + { + ownedMemory.Dispose(); + + throw new IOException($"Tried reading {bytesExpected} but the stream closed after reading {totalBytesRead}."); + } + + totalBytesRead += bytesRead; + } + + return ownedMemory; + } + else + { + // If input is (non-seekable) then copy twice: first into a RecyclableMemoryStream, then to a rented IMemoryOwner<byte>. + using RecyclableMemoryStream output = StreamToRecyclableMemoryStream(input); + + return MemoryStreamToRentedMemory(output); + } } public static async Task<byte[]> StreamToBytesAsync(Stream input, CancellationToken cancellationToken = default) @@ -25,5 +65,26 @@ namespace Ryujinx.Common.Utilities return stream.ToArray(); } + + private static IMemoryOwner<byte> MemoryStreamToRentedMemory(MemoryStream input) + { + input.Position = 0; + + IMemoryOwner<byte> ownedMemory = ByteMemoryPool.Rent(input.Length); + + // Discard the return value because we assume reading a MemoryStream always succeeds completely. + _ = input.Read(ownedMemory.Memory.Span); + + return ownedMemory; + } + + private static RecyclableMemoryStream StreamToRecyclableMemoryStream(Stream input) + { + RecyclableMemoryStream stream = MemoryStreamManager.Shared.GetStream(); + + input.CopyTo(stream); + + return stream; + } } } |
