mirror of
				https://github.com/ryujinx-mirror/ryujinx.git
				synced 2025-11-04 08:59:04 -06:00 
			
		
		
		
	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`
This commit is contained in:
		
							
								
								
									
										140
									
								
								src/Ryujinx.Common/Memory/MemoryOwner.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										140
									
								
								src/Ryujinx.Common/Memory/MemoryOwner.cs
									
									
									
									
									
										Normal file
									
								
							@@ -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
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// An <see cref="IMemoryOwner{T}"/> implementation with an embedded length and fast <see cref="Span{T}"/>
 | 
			
		||||
    /// accessor, with memory allocated from <seealso cref="ArrayPool{T}.Shared"/>.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T">The type of item to store.</typeparam>
 | 
			
		||||
    public sealed class MemoryOwner<T> : IMemoryOwner<T>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly int _length;
 | 
			
		||||
        private T[]? _array;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="MemoryOwner{T}"/> class with the specified parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="length">The length of the new memory buffer to use</param>
 | 
			
		||||
        private MemoryOwner(int length)
 | 
			
		||||
        {
 | 
			
		||||
            _length = length;
 | 
			
		||||
            _array = ArrayPool<T>.Shared.Rent(length);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="length">The length of the new memory buffer to use</param>
 | 
			
		||||
        /// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length</returns>
 | 
			
		||||
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static MemoryOwner<T> Rent(int length) => new(length);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new <see cref="MemoryOwner{T}"/> instance with the specified length and the content cleared.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="length">The length of the new memory buffer to use</param>
 | 
			
		||||
        /// <returns>A <see cref="MemoryOwner{T}"/> instance of the requested length and the content cleared</returns>
 | 
			
		||||
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static MemoryOwner<T> RentCleared(int length)
 | 
			
		||||
        {
 | 
			
		||||
            MemoryOwner<T> result = new(length);
 | 
			
		||||
 | 
			
		||||
            result._array.AsSpan(0, length).Clear();
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new <see cref="MemoryOwner{T}"/> instance with the content copied from the specified buffer.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="buffer">The buffer to copy</param>
 | 
			
		||||
        /// <returns>A <see cref="MemoryOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
 | 
			
		||||
        public static MemoryOwner<T> RentCopy(ReadOnlySpan<T> buffer)
 | 
			
		||||
        {
 | 
			
		||||
            MemoryOwner<T> result = new(buffer.Length);
 | 
			
		||||
 | 
			
		||||
            buffer.CopyTo(result._array);
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the number of items in the current instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Length
 | 
			
		||||
        {
 | 
			
		||||
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
            get => _length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public Memory<T> Memory
 | 
			
		||||
        {
 | 
			
		||||
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                T[]? array = _array;
 | 
			
		||||
 | 
			
		||||
                if (array is null)
 | 
			
		||||
                {
 | 
			
		||||
                    ThrowObjectDisposedException();
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                return new(array, 0, _length);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Uses a trick made possible by the .NET 6+ runtime array layout.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        public Span<T> 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);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <inheritdoc/>
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            T[]? array = Interlocked.Exchange(ref _array, null);
 | 
			
		||||
 | 
			
		||||
            if (array is not null)
 | 
			
		||||
            {
 | 
			
		||||
                ArrayPool<T>.Shared.Return(array);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Throws an <see cref="ObjectDisposedException"/> when <see cref="_array"/> is <see langword="null"/>.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [DoesNotReturn]
 | 
			
		||||
        private static void ThrowObjectDisposedException()
 | 
			
		||||
        {
 | 
			
		||||
            throw new ObjectDisposedException(nameof(MemoryOwner<T>), "The buffer has already been disposed.");
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										114
									
								
								src/Ryujinx.Common/Memory/SpanOwner.cs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								src/Ryujinx.Common/Memory/SpanOwner.cs
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
using System;
 | 
			
		||||
using System.Buffers;
 | 
			
		||||
using System.Runtime.CompilerServices;
 | 
			
		||||
using System.Runtime.InteropServices;
 | 
			
		||||
 | 
			
		||||
namespace Ryujinx.Common.Memory
 | 
			
		||||
{
 | 
			
		||||
    /// <summary>
 | 
			
		||||
    /// A stack-only type that rents a buffer of a specified length from <seealso cref="ArrayPool{T}.Shared"/>.
 | 
			
		||||
    /// It does not implement <see cref="IDisposable"/> 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.
 | 
			
		||||
    /// </summary>
 | 
			
		||||
    /// <typeparam name="T">The type of item to store.</typeparam>
 | 
			
		||||
    public readonly ref struct SpanOwner<T>
 | 
			
		||||
    {
 | 
			
		||||
        private readonly int _length;
 | 
			
		||||
        private readonly T[] _array;
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Initializes a new instance of the <see cref="SpanOwner{T}"/> struct with the specified parameters.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="length">The length of the new memory buffer to use</param>
 | 
			
		||||
        private SpanOwner(int length)
 | 
			
		||||
        {
 | 
			
		||||
            _length = length;
 | 
			
		||||
            _array = ArrayPool<T>.Shared.Rent(length);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets an empty <see cref="SpanOwner{T}"/> instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public static SpanOwner<T> Empty
 | 
			
		||||
        {
 | 
			
		||||
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
            get => new(0);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new <see cref="SpanOwner{T}"/> instance with the specified length.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="length">The length of the new memory buffer to use</param>
 | 
			
		||||
        /// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length</returns>
 | 
			
		||||
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static SpanOwner<T> Rent(int length) => new(length);
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new <see cref="SpanOwner{T}"/> instance with the length and the content cleared.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="length">The length of the new memory buffer to use</param>
 | 
			
		||||
        /// <returns>A <see cref="SpanOwner{T}"/> instance of the requested length and the content cleared</returns>
 | 
			
		||||
        /// <exception cref="ArgumentOutOfRangeException">Thrown when <paramref name="length"/> is not valid</exception>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public static SpanOwner<T> RentCleared(int length)
 | 
			
		||||
        {
 | 
			
		||||
            SpanOwner<T> result = new(length);
 | 
			
		||||
 | 
			
		||||
            result._array.AsSpan(0, length).Clear();
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Creates a new <see cref="SpanOwner{T}"/> instance with the content copied from the specified buffer.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <param name="buffer">The buffer to copy</param>
 | 
			
		||||
        /// <returns>A <see cref="SpanOwner{T}"/> instance with the same length and content as <paramref name="buffer"/></returns>
 | 
			
		||||
        public static SpanOwner<T> RentCopy(ReadOnlySpan<T> buffer)
 | 
			
		||||
        {
 | 
			
		||||
            SpanOwner<T> result = new(buffer.Length);
 | 
			
		||||
 | 
			
		||||
            buffer.CopyTo(result._array);
 | 
			
		||||
 | 
			
		||||
            return result;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets the number of items in the current instance
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        public int Length
 | 
			
		||||
        {
 | 
			
		||||
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
            get => _length;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Gets a <see cref="Span{T}"/> wrapping the memory belonging to the current instance.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        /// <remarks>
 | 
			
		||||
        /// Uses a trick made possible by the .NET 6+ runtime array layout.
 | 
			
		||||
        /// </remarks>
 | 
			
		||||
        public Span<T> Span
 | 
			
		||||
        {
 | 
			
		||||
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
            get
 | 
			
		||||
            {
 | 
			
		||||
                ref T firstElementRef = ref MemoryMarshal.GetArrayDataReference(_array);
 | 
			
		||||
 | 
			
		||||
                return MemoryMarshal.CreateSpan(ref firstElementRef, _length);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        /// <summary>
 | 
			
		||||
        /// Implements the duck-typed <see cref="IDisposable.Dispose"/> method.
 | 
			
		||||
        /// </summary>
 | 
			
		||||
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
 | 
			
		||||
        public void Dispose()
 | 
			
		||||
        {
 | 
			
		||||
            ArrayPool<T>.Shared.Return(_array);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
@@ -1,3 +1,4 @@
 | 
			
		||||
using Ryujinx.Common.Memory;
 | 
			
		||||
using Silk.NET.Vulkan;
 | 
			
		||||
using System;
 | 
			
		||||
 | 
			
		||||
@@ -165,14 +166,15 @@ namespace Ryujinx.Graphics.Vulkan
 | 
			
		||||
        /// <returns>True if all fences were signaled before the timeout expired, false otherwise</returns>
 | 
			
		||||
        private bool WaitForFencesImpl(Vk api, Device device, int offset, int size, bool hasTimeout, ulong timeout)
 | 
			
		||||
        {
 | 
			
		||||
            Span<FenceHolder> fenceHolders = new FenceHolder[CommandBufferPool.MaxCommandBuffers];
 | 
			
		||||
            using SpanOwner<FenceHolder> fenceHoldersOwner = SpanOwner<FenceHolder>.Rent(CommandBufferPool.MaxCommandBuffers);
 | 
			
		||||
            Span<FenceHolder> fenceHolders = fenceHoldersOwner.Span;
 | 
			
		||||
 | 
			
		||||
            int count = size != 0 ? GetOverlappingFences(fenceHolders, offset, size) : GetFences(fenceHolders);
 | 
			
		||||
            Span<Fence> 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))
 | 
			
		||||
                {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user