From 1ecc8fbc3b395f8238d4e74f06a8c014336d25b7 Mon Sep 17 00:00:00 2001
From: jhorv <38920027+jhorv@users.noreply.github.com>
Date: Sun, 2 Jun 2024 21:24:14 -0400
Subject: [PATCH] New pooled memory types (#6821)
* feat: add new types MemoryOwner and SpanOwner
* use SpanOwner instead of new array allocation
* change for loop condition to `fences.Length` instead of `count` to elide Span boundary checks on `fences`
---
src/Ryujinx.Common/Memory/MemoryOwner.cs | 140 ++++++++++++++++++
src/Ryujinx.Common/Memory/SpanOwner.cs | 114 ++++++++++++++
.../MultiFenceHolder.cs | 6 +-
3 files changed, 258 insertions(+), 2 deletions(-)
create mode 100644 src/Ryujinx.Common/Memory/MemoryOwner.cs
create mode 100644 src/Ryujinx.Common/Memory/SpanOwner.cs
diff --git a/src/Ryujinx.Common/Memory/MemoryOwner.cs b/src/Ryujinx.Common/Memory/MemoryOwner.cs
new file mode 100644
index 000000000..5e567ab8d
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/MemoryOwner.cs
@@ -0,0 +1,140 @@
+#nullable enable
+using System;
+using System.Buffers;
+using System.Diagnostics.CodeAnalysis;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// An implementation with an embedded length and fast
+ /// accessor, with memory allocated from .
+ ///
+ /// The type of item to store.
+ public sealed class MemoryOwner : IMemoryOwner
+ {
+ private readonly int _length;
+ private T[]? _array;
+
+ ///
+ /// Initializes a new instance of the class with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private MemoryOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the specified length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static MemoryOwner RentCleared(int length)
+ {
+ MemoryOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static MemoryOwner RentCopy(ReadOnlySpan buffer)
+ {
+ MemoryOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance.
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ public Memory Memory
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ return new(array, 0, _length);
+ }
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ T[]? array = _array;
+
+ if (array is null)
+ {
+ ThrowObjectDisposedException();
+ }
+
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ public void Dispose()
+ {
+ T[]? array = Interlocked.Exchange(ref _array, null);
+
+ if (array is not null)
+ {
+ ArrayPool.Shared.Return(array);
+ }
+ }
+
+ ///
+ /// Throws an when is .
+ ///
+ [DoesNotReturn]
+ private static void ThrowObjectDisposedException()
+ {
+ throw new ObjectDisposedException(nameof(MemoryOwner), "The buffer has already been disposed.");
+ }
+ }
+}
diff --git a/src/Ryujinx.Common/Memory/SpanOwner.cs b/src/Ryujinx.Common/Memory/SpanOwner.cs
new file mode 100644
index 000000000..a4b4adf32
--- /dev/null
+++ b/src/Ryujinx.Common/Memory/SpanOwner.cs
@@ -0,0 +1,114 @@
+using System;
+using System.Buffers;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+
+namespace Ryujinx.Common.Memory
+{
+ ///
+ /// A stack-only type that rents a buffer of a specified length from .
+ /// It does not implement to avoid being boxed, but should still be disposed. This
+ /// is easy since C# 8, which allows use of C# `using` constructs on any type that has a public Dispose() method.
+ /// To keep this type simple, fast, and read-only, it does not check or guard against multiple disposals.
+ /// For all these reasons, all usage should be with a `using` block or statement.
+ ///
+ /// The type of item to store.
+ public readonly ref struct SpanOwner
+ {
+ private readonly int _length;
+ private readonly T[] _array;
+
+ ///
+ /// Initializes a new instance of the struct with the specified parameters.
+ ///
+ /// The length of the new memory buffer to use
+ private SpanOwner(int length)
+ {
+ _length = length;
+ _array = ArrayPool.Shared.Rent(length);
+ }
+
+ ///
+ /// Gets an empty instance.
+ ///
+ public static SpanOwner Empty
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => new(0);
+ }
+
+ ///
+ /// Creates a new instance with the specified length.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner Rent(int length) => new(length);
+
+ ///
+ /// Creates a new instance with the length and the content cleared.
+ ///
+ /// The length of the new memory buffer to use
+ /// A instance of the requested length and the content cleared
+ /// Thrown when is not valid
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public static SpanOwner RentCleared(int length)
+ {
+ SpanOwner result = new(length);
+
+ result._array.AsSpan(0, length).Clear();
+
+ return result;
+ }
+
+ ///
+ /// Creates a new instance with the content copied from the specified buffer.
+ ///
+ /// The buffer to copy
+ /// A instance with the same length and content as
+ public static SpanOwner RentCopy(ReadOnlySpan buffer)
+ {
+ SpanOwner result = new(buffer.Length);
+
+ buffer.CopyTo(result._array);
+
+ return result;
+ }
+
+ ///
+ /// Gets the number of items in the current instance
+ ///
+ public int Length
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get => _length;
+ }
+
+ ///
+ /// Gets a wrapping the memory belonging to the current instance.
+ ///
+ ///
+ /// Uses a trick made possible by the .NET 6+ runtime array layout.
+ ///
+ public Span Span
+ {
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ get
+ {
+ ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
+
+ return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
+ }
+ }
+
+ ///
+ /// Implements the duck-typed method.
+ ///
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public void Dispose()
+ {
+ ArrayPool.Shared.Return(_array);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
index 0bce3b72d..806b872bc 100644
--- a/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
+++ b/src/Ryujinx.Graphics.Vulkan/MultiFenceHolder.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Memory;
using Silk.NET.Vulkan;
using System;
@@ -165,14 +166,15 @@ namespace Ryujinx.Graphics.Vulkan
/// True if all fences were signaled before the timeout expired, false otherwise
private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
{
- Span fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
+ using SpanOwner fenceHoldersOwner = SpanOwner.Rent(CommandBufferPool.MaxCommandBuffers);
+ Span fenceHolders = fenceHoldersOwner.Span;
int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
Span fences = stackalloc Fence[count];
int fenceCount = 0;
- for (int i = 0; i < count; i++)
+ for (int i = 0; i < fences.Length; i++)
{
if (fenceHolders[i].TryGet(out Fence fence))
{