Compare commits

..

3 Commits

Author SHA1 Message Date
gdkchan
4744bde0e5 Reduce the amount of descriptor pool allocations on Vulkan (#5673)
* Reduce the amount of descriptor pool allocations on Vulkan

* Formatting

* Slice can be simplified

* Make GetDescriptorPoolSizes static

* Adjust CanFit calculation so that TryAllocateDescriptorSets never fails

* Remove unused field
2023-09-26 02:00:02 +02:00
gdkchan
4a835bb2b9 Make Vulkan memory allocator actually thread safe (#5575)
* Make Vulkan memory allocator actually thread safe

* Make free thread safe too

* PR feedback
2023-09-26 01:50:06 +02:00
gdkchan
ddc9ae2a83 Add VTimer as alternative interrupt method on Apple Hypervisor (#5663)
* Add VTimer as alternative interrupt method on Apple Hypervisor

* Fix naming violations on TimeApi

* Fix timer interval (was 16us rather than 16ms)

* Fix delta ticks calculation

* Missing ThrowOnError call

* Add SupportedOSPlatform attribute on AppleHv classes
2023-09-26 01:18:32 +02:00
24 changed files with 361 additions and 112 deletions

View File

@@ -1,9 +1,11 @@
using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvAddressSpace : IDisposable class HvAddressSpace : IDisposable
{ {
private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39)); private const ulong KernelRegionBase = unchecked((ulong)-(1L << 39));

View File

@@ -2,10 +2,12 @@ using Ryujinx.Cpu.AppleHv.Arm;
using System; using System;
using System.Diagnostics; using System.Diagnostics;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvAddressSpaceRange : IDisposable class HvAddressSpaceRange : IDisposable
{ {
private const ulong AllocationGranule = 1UL << 14; private const ulong AllocationGranule = 1UL << 14;

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
@@ -12,10 +13,18 @@ namespace Ryujinx.Cpu.AppleHv
#pragma warning restore CS0649 #pragma warning restore CS0649
} }
enum HvExitReason : uint
{
Canceled,
Exception,
VTimerActivated,
Unknown,
}
struct HvVcpuExit struct HvVcpuExit
{ {
#pragma warning disable CS0649 // Field is never assigned to #pragma warning disable CS0649 // Field is never assigned to
public uint Reason; public HvExitReason Reason;
public HvVcpuExitException Exception; public HvVcpuExitException Exception;
#pragma warning restore CS0649 #pragma warning restore CS0649
} }
@@ -255,6 +264,7 @@ namespace Ryujinx.Cpu.AppleHv
} }
} }
[SupportedOSPlatform("macos")]
static partial class HvApi static partial class HvApi
{ {
public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor"; public const string LibraryName = "/System/Library/Frameworks/Hypervisor.framework/Hypervisor";

View File

@@ -1,7 +1,9 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvCpuContext : ICpuContext class HvCpuContext : ICpuContext
{ {
private readonly ITickSource _tickSource; private readonly ITickSource _tickSource;

View File

@@ -1,7 +1,9 @@
using ARMeilleure.Memory; using ARMeilleure.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
public class HvEngine : ICpuEngine public class HvEngine : ICpuEngine
{ {
private readonly ITickSource _tickSource; private readonly ITickSource _tickSource;

View File

@@ -2,9 +2,12 @@ using ARMeilleure.State;
using Ryujinx.Cpu.AppleHv.Arm; using Ryujinx.Cpu.AppleHv.Arm;
using Ryujinx.Memory.Tracking; using Ryujinx.Memory.Tracking;
using System; using System;
using System.Runtime.Versioning;
using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvExecutionContext : IExecutionContext class HvExecutionContext : IExecutionContext
{ {
/// <inheritdoc/> /// <inheritdoc/>
@@ -67,6 +70,8 @@ namespace Ryujinx.Cpu.AppleHv
private readonly ExceptionCallbacks _exceptionCallbacks; private readonly ExceptionCallbacks _exceptionCallbacks;
private int _interruptRequested;
public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks) public HvExecutionContext(ICounter counter, ExceptionCallbacks exceptionCallbacks)
{ {
_counter = counter; _counter = counter;
@@ -111,7 +116,15 @@ namespace Ryujinx.Cpu.AppleHv
/// <inheritdoc/> /// <inheritdoc/>
public void RequestInterrupt() public void RequestInterrupt()
{ {
_impl.RequestInterrupt(); if (Interlocked.Exchange(ref _interruptRequested, 1) == 0 && _impl is HvExecutionContextVcpu impl)
{
impl.RequestInterrupt();
}
}
private bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
} }
/// <inheritdoc/> /// <inheritdoc/>
@@ -131,9 +144,9 @@ namespace Ryujinx.Cpu.AppleHv
{ {
HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError(); HvApi.hv_vcpu_run(vcpu.Handle).ThrowOnError();
uint reason = vcpu.ExitInfo->Reason; HvExitReason reason = vcpu.ExitInfo->Reason;
if (reason == 1) if (reason == HvExitReason.Exception)
{ {
uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome; uint hvEsr = (uint)vcpu.ExitInfo->Exception.Syndrome;
ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26); ExceptionClass hvEc = (ExceptionClass)(hvEsr >> 26);
@@ -146,14 +159,22 @@ namespace Ryujinx.Cpu.AppleHv
address = SynchronousException(memoryManager, ref vcpu); address = SynchronousException(memoryManager, ref vcpu);
HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError(); HvApi.hv_vcpu_set_reg(vcpu.Handle, HvReg.PC, address).ThrowOnError();
} }
else if (reason == 0) else if (reason == HvExitReason.Canceled || reason == HvExitReason.VTimerActivated)
{ {
if (_impl.GetAndClearInterruptRequested()) if (GetAndClearInterruptRequested())
{ {
ReturnToPool(vcpu); ReturnToPool(vcpu);
InterruptHandler(); InterruptHandler();
vcpu = RentFromPool(memoryManager.AddressSpace, vcpu); vcpu = RentFromPool(memoryManager.AddressSpace, vcpu);
} }
if (reason == HvExitReason.VTimerActivated)
{
vcpu.EnableAndUpdateVTimer();
// Unmask VTimer interrupts.
HvApi.hv_vcpu_set_vtimer_mask(vcpu.Handle, false).ThrowOnError();
}
} }
else else
{ {

View File

@@ -46,14 +46,5 @@ namespace Ryujinx.Cpu.AppleHv
{ {
_v[index] = value; _v[index] = value;
} }
public void RequestInterrupt()
{
}
public bool GetAndClearInterruptRequested()
{
return false;
}
} }
} }

View File

@@ -2,10 +2,11 @@ using ARMeilleure.State;
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Threading; using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvExecutionContextVcpu : IHvExecutionContext class HvExecutionContextVcpu : IHvExecutionContext
{ {
private static readonly MemoryBlock _setSimdFpRegFuncMem; private static readonly MemoryBlock _setSimdFpRegFuncMem;
@@ -135,7 +136,6 @@ namespace Ryujinx.Cpu.AppleHv
} }
private readonly ulong _vcpu; private readonly ulong _vcpu;
private int _interruptRequested;
public HvExecutionContextVcpu(ulong vcpu) public HvExecutionContextVcpu(ulong vcpu)
{ {
@@ -181,16 +181,8 @@ namespace Ryujinx.Cpu.AppleHv
public void RequestInterrupt() public void RequestInterrupt()
{ {
if (Interlocked.Exchange(ref _interruptRequested, 1) == 0) ulong vcpu = _vcpu;
{ HvApi.hv_vcpus_exit(ref vcpu, 1);
ulong vcpu = _vcpu;
HvApi.hv_vcpus_exit(ref vcpu, 1);
}
}
public bool GetAndClearInterruptRequested()
{
return Interlocked.Exchange(ref _interruptRequested, 0) != 0;
} }
} }
} }

View File

@@ -1,8 +1,10 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
readonly struct HvMemoryBlockAllocation : IDisposable readonly struct HvMemoryBlockAllocation : IDisposable
{ {
private readonly HvMemoryBlockAllocator _owner; private readonly HvMemoryBlockAllocator _owner;

View File

@@ -1,7 +1,9 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block> class HvMemoryBlockAllocator : PrivateMemoryAllocatorImpl<HvMemoryBlockAllocator.Block>
{ {
public class Block : PrivateMemoryAllocator.Block public class Block : PrivateMemoryAllocator.Block

View File

@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using System.Runtime.CompilerServices; using System.Runtime.CompilerServices;
using System.Runtime.InteropServices; using System.Runtime.InteropServices;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
@@ -14,6 +15,7 @@ namespace Ryujinx.Cpu.AppleHv
/// <summary> /// <summary>
/// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table. /// Represents a CPU memory manager which maps guest virtual memory directly onto the Hypervisor page table.
/// </summary> /// </summary>
[SupportedOSPlatform("macos")]
public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock public class HvMemoryManager : MemoryManagerBase, IMemoryManager, IVirtualMemoryManagerTracked, IWritableBlock
{ {
public const int PageBits = 12; public const int PageBits = 12;

View File

@@ -1,7 +1,15 @@
using System.Diagnostics;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
unsafe class HvVcpu unsafe class HvVcpu
{ {
private const ulong InterruptIntervalNs = 16 * 1000000; // 16 ms
private static ulong _interruptTimeDeltaTicks = 0;
public readonly ulong Handle; public readonly ulong Handle;
public readonly HvVcpuExit* ExitInfo; public readonly HvVcpuExit* ExitInfo;
public readonly IHvExecutionContext ShadowContext; public readonly IHvExecutionContext ShadowContext;
@@ -21,5 +29,28 @@ namespace Ryujinx.Cpu.AppleHv
NativeContext = nativeContext; NativeContext = nativeContext;
IsEphemeral = isEphemeral; IsEphemeral = isEphemeral;
} }
public void EnableAndUpdateVTimer()
{
// We need to ensure interrupts will be serviced,
// and for that we set up the VTime to trigger an interrupt at fixed intervals.
ulong deltaTicks = _interruptTimeDeltaTicks;
if (deltaTicks == 0)
{
// Calculate our time delta in ticks based on the current clock frequency.
int result = TimeApi.mach_timebase_info(out var timeBaseInfo);
Debug.Assert(result == 0);
deltaTicks = ((InterruptIntervalNs * timeBaseInfo.Denom) + (timeBaseInfo.Numer - 1)) / timeBaseInfo.Numer;
_interruptTimeDeltaTicks = deltaTicks;
}
HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CTL_EL0, 1).ThrowOnError();
HvApi.hv_vcpu_set_sys_reg(Handle, HvSysReg.CNTV_CVAL_EL0, TimeApi.mach_absolute_time() + deltaTicks).ThrowOnError();
}
} }
} }

View File

@@ -1,8 +1,10 @@
using System; using System;
using System.Runtime.Versioning;
using System.Threading; using System.Threading;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
class HvVcpuPool class HvVcpuPool
{ {
// Since there's a limit on the number of VCPUs we can create, // Since there's a limit on the number of VCPUs we can create,
@@ -81,6 +83,8 @@ namespace Ryujinx.Cpu.AppleHv
HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral); HvVcpu vcpu = new(vcpuHandle, exitInfo, shadowContext, nativeContext, isEphemeral);
vcpu.EnableAndUpdateVTimer();
return vcpu; return vcpu;
} }

View File

@@ -1,8 +1,10 @@
using Ryujinx.Memory; using Ryujinx.Memory;
using System; using System;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
[SupportedOSPlatform("macos")]
static class HvVm static class HvVm
{ {
// This alignment allows us to use larger blocks on the page table. // This alignment allows us to use larger blocks on the page table.

View File

@@ -2,7 +2,7 @@ using ARMeilleure.State;
namespace Ryujinx.Cpu.AppleHv namespace Ryujinx.Cpu.AppleHv
{ {
public interface IHvExecutionContext interface IHvExecutionContext
{ {
ulong Pc { get; set; } ulong Pc { get; set; }
ulong ElrEl1 { get; set; } ulong ElrEl1 { get; set; }
@@ -39,8 +39,5 @@ namespace Ryujinx.Cpu.AppleHv
SetV(i, context.GetV(i)); SetV(i, context.GetV(i));
} }
} }
void RequestInterrupt();
bool GetAndClearInterruptRequested();
} }
} }

View File

@@ -0,0 +1,21 @@
using System.Runtime.InteropServices;
using System.Runtime.Versioning;
namespace Ryujinx.Cpu.AppleHv
{
struct MachTimebaseInfo
{
public uint Numer;
public uint Denom;
}
[SupportedOSPlatform("macos")]
static partial class TimeApi
{
[LibraryImport("libc", SetLastError = true)]
public static partial ulong mach_absolute_time();
[LibraryImport("libc", SetLastError = true)]
public static partial int mach_timebase_info(out MachTimebaseInfo info);
}
}

View File

@@ -27,6 +27,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
public bool InUse; public bool InUse;
public bool InConsumption; public bool InConsumption;
public int SubmissionCount;
public CommandBuffer CommandBuffer; public CommandBuffer CommandBuffer;
public FenceHolder Fence; public FenceHolder Fence;
public SemaphoreHolder Semaphore; public SemaphoreHolder Semaphore;
@@ -193,6 +194,11 @@ namespace Ryujinx.Graphics.Vulkan
return _commandBuffers[cbIndex].Fence; return _commandBuffers[cbIndex].Fence;
} }
public int GetSubmissionCount(int cbIndex)
{
return _commandBuffers[cbIndex].SubmissionCount;
}
private int FreeConsumed(bool wait) private int FreeConsumed(bool wait)
{ {
int freeEntry = 0; int freeEntry = 0;
@@ -282,6 +288,7 @@ namespace Ryujinx.Graphics.Vulkan
Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle); Debug.Assert(entry.CommandBuffer.Handle == cbs.CommandBuffer.Handle);
entry.InUse = false; entry.InUse = false;
entry.InConsumption = true; entry.InConsumption = true;
entry.SubmissionCount++;
_inUseCount--; _inUseCount--;
var commandBuffer = entry.CommandBuffer; var commandBuffer = entry.CommandBuffer;

View File

@@ -6,7 +6,7 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class DescriptorSetManager : IDisposable class DescriptorSetManager : IDisposable
{ {
private const uint DescriptorPoolMultiplier = 16; public const uint MaxSets = 16;
public class DescriptorPoolHolder : IDisposable public class DescriptorPoolHolder : IDisposable
{ {
@@ -14,36 +14,28 @@ namespace Ryujinx.Graphics.Vulkan
public Device Device { get; } public Device Device { get; }
private readonly DescriptorPool _pool; private readonly DescriptorPool _pool;
private readonly uint _capacity; private int _freeDescriptors;
private int _totalSets; private int _totalSets;
private int _setsInUse; private int _setsInUse;
private bool _done; private bool _done;
public unsafe DescriptorPoolHolder(Vk api, Device device) public unsafe DescriptorPoolHolder(Vk api, Device device, ReadOnlySpan<DescriptorPoolSize> poolSizes, bool updateAfterBind)
{ {
Api = api; Api = api;
Device = device; Device = device;
var poolSizes = new[] foreach (var poolSize in poolSizes)
{ {
new DescriptorPoolSize(DescriptorType.UniformBuffer, (1 + Constants.MaxUniformBufferBindings) * DescriptorPoolMultiplier), _freeDescriptors += (int)poolSize.DescriptorCount;
new DescriptorPoolSize(DescriptorType.StorageBuffer, Constants.MaxStorageBufferBindings * DescriptorPoolMultiplier), }
new DescriptorPoolSize(DescriptorType.CombinedImageSampler, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageImage, Constants.MaxImageBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.UniformTexelBuffer, Constants.MaxTextureBindings * DescriptorPoolMultiplier),
new DescriptorPoolSize(DescriptorType.StorageTexelBuffer, Constants.MaxImageBindings * DescriptorPoolMultiplier),
};
uint maxSets = (uint)poolSizes.Length * DescriptorPoolMultiplier;
_capacity = maxSets;
fixed (DescriptorPoolSize* pPoolsSize = poolSizes) fixed (DescriptorPoolSize* pPoolsSize = poolSizes)
{ {
var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo var descriptorPoolCreateInfo = new DescriptorPoolCreateInfo
{ {
SType = StructureType.DescriptorPoolCreateInfo, SType = StructureType.DescriptorPoolCreateInfo,
MaxSets = maxSets, Flags = updateAfterBind ? DescriptorPoolCreateFlags.UpdateAfterBindBit : DescriptorPoolCreateFlags.None,
MaxSets = MaxSets,
PoolSizeCount = (uint)poolSizes.Length, PoolSizeCount = (uint)poolSizes.Length,
PPoolSizes = pPoolsSize, PPoolSizes = pPoolsSize,
}; };
@@ -52,18 +44,22 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
public DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts) public unsafe DescriptorSetCollection AllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors)
{ {
TryAllocateDescriptorSets(layouts, isTry: false, out var dsc); TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: false, out var dsc);
return dsc; return dsc;
} }
public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, out DescriptorSetCollection dsc) public bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, int consumedDescriptors, out DescriptorSetCollection dsc)
{ {
return TryAllocateDescriptorSets(layouts, isTry: true, out dsc); return TryAllocateDescriptorSets(layouts, consumedDescriptors, isTry: true, out dsc);
} }
private unsafe bool TryAllocateDescriptorSets(ReadOnlySpan<DescriptorSetLayout> layouts, bool isTry, out DescriptorSetCollection dsc) private unsafe bool TryAllocateDescriptorSets(
ReadOnlySpan<DescriptorSetLayout> layouts,
int consumedDescriptors,
bool isTry,
out DescriptorSetCollection dsc)
{ {
Debug.Assert(!_done); Debug.Assert(!_done);
@@ -84,7 +80,7 @@ namespace Ryujinx.Graphics.Vulkan
var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets); var result = Api.AllocateDescriptorSets(Device, &descriptorSetAllocateInfo, pDescriptorSets);
if (isTry && result == Result.ErrorOutOfPoolMemory) if (isTry && result == Result.ErrorOutOfPoolMemory)
{ {
_totalSets = (int)_capacity; _totalSets = (int)MaxSets;
_done = true; _done = true;
DestroyIfDone(); DestroyIfDone();
dsc = default; dsc = default;
@@ -95,6 +91,7 @@ namespace Ryujinx.Graphics.Vulkan
} }
} }
_freeDescriptors -= consumedDescriptors;
_totalSets += layouts.Length; _totalSets += layouts.Length;
_setsInUse += layouts.Length; _setsInUse += layouts.Length;
@@ -109,9 +106,15 @@ namespace Ryujinx.Graphics.Vulkan
DestroyIfDone(); DestroyIfDone();
} }
public bool CanFit(int count) public bool CanFit(int setsCount, int descriptorsCount)
{ {
if (_totalSets + count <= _capacity) // Try to determine if an allocation with the given parameters will succeed.
// An allocation may fail if the sets count or descriptors count exceeds the available counts
// of the pool.
// Not getting that right is not fatal, it will just create a new pool and try again,
// but it is less efficient.
if (_totalSets + setsCount <= MaxSets && _freeDescriptors >= descriptorsCount)
{ {
return true; return true;
} }
@@ -148,46 +151,74 @@ namespace Ryujinx.Graphics.Vulkan
} }
private readonly Device _device; private readonly Device _device;
private DescriptorPoolHolder _currentPool; private readonly DescriptorPoolHolder[] _currentPools;
public DescriptorSetManager(Device device) public DescriptorSetManager(Device device, int poolCount)
{ {
_device = device; _device = device;
_currentPools = new DescriptorPoolHolder[poolCount];
} }
public Auto<DescriptorSetCollection> AllocateDescriptorSet(Vk api, DescriptorSetLayout layout) public Auto<DescriptorSetCollection> AllocateDescriptorSet(
Vk api,
DescriptorSetLayout layout,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{ {
Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1]; Span<DescriptorSetLayout> layouts = stackalloc DescriptorSetLayout[1];
layouts[0] = layout; layouts[0] = layout;
return AllocateDescriptorSets(api, layouts); return AllocateDescriptorSets(api, layouts, poolSizes, poolIndex, consumedDescriptors, updateAfterBind);
} }
public Auto<DescriptorSetCollection> AllocateDescriptorSets(Vk api, ReadOnlySpan<DescriptorSetLayout> layouts) public Auto<DescriptorSetCollection> AllocateDescriptorSets(
Vk api,
ReadOnlySpan<DescriptorSetLayout> layouts,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int consumedDescriptors,
bool updateAfterBind)
{ {
// If we fail the first time, just create a new pool and try again. // If we fail the first time, just create a new pool and try again.
if (!GetPool(api, layouts.Length).TryAllocateDescriptorSets(layouts, out var dsc))
var pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
if (!pool.TryAllocateDescriptorSets(layouts, consumedDescriptors, out var dsc))
{ {
dsc = GetPool(api, layouts.Length).AllocateDescriptorSets(layouts); pool = GetPool(api, poolSizes, poolIndex, layouts.Length, consumedDescriptors, updateAfterBind);
dsc = pool.AllocateDescriptorSets(layouts, consumedDescriptors);
} }
return new Auto<DescriptorSetCollection>(dsc); return new Auto<DescriptorSetCollection>(dsc);
} }
private DescriptorPoolHolder GetPool(Vk api, int requiredCount) private DescriptorPoolHolder GetPool(
Vk api,
ReadOnlySpan<DescriptorPoolSize> poolSizes,
int poolIndex,
int setsCount,
int descriptorsCount,
bool updateAfterBind)
{ {
if (_currentPool == null || !_currentPool.CanFit(requiredCount)) ref DescriptorPoolHolder currentPool = ref _currentPools[poolIndex];
if (currentPool == null || !currentPool.CanFit(setsCount, descriptorsCount))
{ {
_currentPool = new DescriptorPoolHolder(api, _device); currentPool = new DescriptorPoolHolder(api, _device, poolSizes, updateAfterBind);
} }
return _currentPool; return currentPool;
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)
{ {
if (disposing) if (disposing)
{ {
_currentPool?.Dispose(); for (int index = 0; index < _currentPools.Length; index++)
{
_currentPools[index]?.Dispose();
_currentPools[index] = null;
}
} }
} }

View File

@@ -59,6 +59,8 @@ namespace Ryujinx.Graphics.Vulkan
private BitMapStruct<Array2<long>> _uniformMirrored; private BitMapStruct<Array2<long>> _uniformMirrored;
private BitMapStruct<Array2<long>> _storageMirrored; private BitMapStruct<Array2<long>> _storageMirrored;
private bool _updateDescriptorCacheCbIndex;
[Flags] [Flags]
private enum DirtyFlags private enum DirtyFlags
{ {
@@ -218,6 +220,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SetProgram(ShaderCollection program) public void SetProgram(ShaderCollection program)
{ {
_program = program; _program = program;
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
} }
@@ -490,7 +493,13 @@ namespace Ryujinx.Graphics.Vulkan
var dummyBuffer = _dummyBuffer?.GetBuffer(); var dummyBuffer = _dummyBuffer?.GetBuffer();
var dsc = program.GetNewDescriptorSetCollection(_gd, cbs.CommandBufferIndex, setIndex, out var isNew).Get(cbs); if (_updateDescriptorCacheCbIndex)
{
_updateDescriptorCacheCbIndex = false;
program.UpdateDescriptorCacheCommandBufferIndex(cbs.CommandBufferIndex);
}
var dsc = program.GetNewDescriptorSetCollection(setIndex, out var isNew).Get(cbs);
if (!program.HasMinimalLayout) if (!program.HasMinimalLayout)
{ {
@@ -697,6 +706,7 @@ namespace Ryujinx.Graphics.Vulkan
public void SignalCommandBufferChange() public void SignalCommandBufferChange()
{ {
_updateDescriptorCacheCbIndex = true;
_dirty = DirtyFlags.All; _dirty = DirtyFlags.All;
_uniformSet.Clear(); _uniformSet.Clear();

View File

@@ -1,6 +1,7 @@
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -13,6 +14,7 @@ namespace Ryujinx.Graphics.Vulkan
private readonly Device _device; private readonly Device _device;
private readonly List<MemoryAllocatorBlockList> _blockLists; private readonly List<MemoryAllocatorBlockList> _blockLists;
private readonly int _blockAlignment; private readonly int _blockAlignment;
private readonly ReaderWriterLockSlim _lock;
public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device) public MemoryAllocator(Vk api, VulkanPhysicalDevice physicalDevice, Device device)
{ {
@@ -21,6 +23,7 @@ namespace Ryujinx.Graphics.Vulkan
_device = device; _device = device;
_blockLists = new List<MemoryAllocatorBlockList>(); _blockLists = new List<MemoryAllocatorBlockList>();
_blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount); _blockAlignment = (int)Math.Min(int.MaxValue, MaxDeviceMemoryUsageEstimate / _physicalDevice.PhysicalDeviceProperties.Limits.MaxMemoryAllocationCount);
_lock = new(LockRecursionPolicy.NoRecursion);
} }
public MemoryAllocation AllocateDeviceMemory( public MemoryAllocation AllocateDeviceMemory(
@@ -40,21 +43,37 @@ namespace Ryujinx.Graphics.Vulkan
private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer) private MemoryAllocation Allocate(int memoryTypeIndex, ulong size, ulong alignment, bool map, bool isBuffer)
{ {
for (int i = 0; i < _blockLists.Count; i++) _lock.EnterReadLock();
try
{ {
var bl = _blockLists[i]; for (int i = 0; i < _blockLists.Count; i++)
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
{ {
lock (bl) var bl = _blockLists[i];
if (bl.MemoryTypeIndex == memoryTypeIndex && bl.ForBuffer == isBuffer)
{ {
return bl.Allocate(size, alignment, map); return bl.Allocate(size, alignment, map);
} }
} }
} }
finally
{
_lock.ExitReadLock();
}
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer); _lock.EnterWriteLock();
_blockLists.Add(newBl);
return newBl.Allocate(size, alignment, map); try
{
var newBl = new MemoryAllocatorBlockList(_api, _device, memoryTypeIndex, _blockAlignment, isBuffer);
_blockLists.Add(newBl);
return newBl.Allocate(size, alignment, map);
}
finally
{
_lock.ExitWriteLock();
}
} }
internal int FindSuitableMemoryTypeIndex( internal int FindSuitableMemoryTypeIndex(

View File

@@ -3,6 +3,7 @@ using Silk.NET.Vulkan;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Threading;
namespace Ryujinx.Graphics.Vulkan namespace Ryujinx.Graphics.Vulkan
{ {
@@ -166,6 +167,8 @@ namespace Ryujinx.Graphics.Vulkan
private readonly int _blockAlignment; private readonly int _blockAlignment;
private readonly ReaderWriterLockSlim _lock;
public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer) public MemoryAllocatorBlockList(Vk api, Device device, int memoryTypeIndex, int blockAlignment, bool forBuffer)
{ {
_blocks = new List<Block>(); _blocks = new List<Block>();
@@ -174,6 +177,7 @@ namespace Ryujinx.Graphics.Vulkan
MemoryTypeIndex = memoryTypeIndex; MemoryTypeIndex = memoryTypeIndex;
ForBuffer = forBuffer; ForBuffer = forBuffer;
_blockAlignment = blockAlignment; _blockAlignment = blockAlignment;
_lock = new(LockRecursionPolicy.NoRecursion);
} }
public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map) public unsafe MemoryAllocation Allocate(ulong size, ulong alignment, bool map)
@@ -184,19 +188,28 @@ namespace Ryujinx.Graphics.Vulkan
throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}."); throw new ArgumentOutOfRangeException(nameof(alignment), $"Invalid alignment 0x{alignment:X}.");
} }
for (int i = 0; i < _blocks.Count; i++) _lock.EnterReadLock();
{
var block = _blocks[i];
if (block.Mapped == map && block.Size >= size) try
{
for (int i = 0; i < _blocks.Count; i++)
{ {
ulong offset = block.Allocate(size, alignment); var block = _blocks[i];
if (offset != InvalidOffset)
if (block.Mapped == map && block.Size >= size)
{ {
return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size); ulong offset = block.Allocate(size, alignment);
if (offset != InvalidOffset)
{
return new MemoryAllocation(this, block, block.Memory, GetHostPointer(block, offset), offset, size);
}
} }
} }
} }
finally
{
_lock.ExitReadLock();
}
ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment); ulong blockAlignedSize = BitUtils.AlignUp(size, (ulong)_blockAlignment);
@@ -244,14 +257,23 @@ namespace Ryujinx.Graphics.Vulkan
if (block.IsTotallyFree()) if (block.IsTotallyFree())
{ {
for (int i = 0; i < _blocks.Count; i++) _lock.EnterWriteLock();
try
{ {
if (_blocks[i] == block) for (int i = 0; i < _blocks.Count; i++)
{ {
_blocks.RemoveAt(i); if (_blocks[i] == block)
break; {
_blocks.RemoveAt(i);
break;
}
} }
} }
finally
{
_lock.ExitWriteLock();
}
block.Destroy(_api, _device); block.Destroy(_api, _device);
} }
@@ -259,13 +281,22 @@ namespace Ryujinx.Graphics.Vulkan
private void InsertBlock(Block block) private void InsertBlock(Block block)
{ {
int index = _blocks.BinarySearch(block); _lock.EnterWriteLock();
if (index < 0)
{
index = ~index;
}
_blocks.Insert(index, block); try
{
int index = _blocks.BinarySearch(block);
if (index < 0)
{
index = ~index;
}
_blocks.Insert(index, block);
}
finally
{
_lock.ExitWriteLock();
}
} }
public void Dispose() public void Dispose()

View File

@@ -1,5 +1,6 @@
using Ryujinx.Graphics.GAL; using Ryujinx.Graphics.GAL;
using Silk.NET.Vulkan; using Silk.NET.Vulkan;
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
@@ -7,15 +8,28 @@ namespace Ryujinx.Graphics.Vulkan
{ {
class PipelineLayoutCacheEntry class PipelineLayoutCacheEntry
{ {
// Those were adjusted based on current descriptor usage and the descriptor counts usually used on pipeline layouts.
// It might be a good idea to tweak them again if those change, or maybe find a way to calculate an optimal value dynamically.
private const uint DefaultUniformBufferPoolCapacity = 19 * DescriptorSetManager.MaxSets;
private const uint DefaultStorageBufferPoolCapacity = 16 * DescriptorSetManager.MaxSets;
private const uint DefaultTexturePoolCapacity = 128 * DescriptorSetManager.MaxSets;
private const uint DefaultImagePoolCapacity = 8 * DescriptorSetManager.MaxSets;
private const int MaxPoolSizesPerSet = 2;
private readonly VulkanRenderer _gd; private readonly VulkanRenderer _gd;
private readonly Device _device; private readonly Device _device;
public DescriptorSetLayout[] DescriptorSetLayouts { get; } public DescriptorSetLayout[] DescriptorSetLayouts { get; }
public PipelineLayout PipelineLayout { get; } public PipelineLayout PipelineLayout { get; }
private readonly int[] _consumedDescriptorsPerSet;
private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache; private readonly List<Auto<DescriptorSetCollection>>[][] _dsCache;
private List<Auto<DescriptorSetCollection>>[] _currentDsCache;
private readonly int[] _dsCacheCursor; private readonly int[] _dsCacheCursor;
private int _dsLastCbIndex; private int _dsLastCbIndex;
private int _dsLastSubmissionCount;
private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount) private PipelineLayoutCacheEntry(VulkanRenderer gd, Device device, int setsCount)
{ {
@@ -44,29 +58,55 @@ namespace Ryujinx.Graphics.Vulkan
bool usePushDescriptors) : this(gd, device, setDescriptors.Count) bool usePushDescriptors) : this(gd, device, setDescriptors.Count)
{ {
(DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors); (DescriptorSetLayouts, PipelineLayout) = PipelineLayoutFactory.Create(gd, device, setDescriptors, usePushDescriptors);
_consumedDescriptorsPerSet = new int[setDescriptors.Count];
for (int setIndex = 0; setIndex < setDescriptors.Count; setIndex++)
{
int count = 0;
foreach (var descriptor in setDescriptors[setIndex].Descriptors)
{
count += descriptor.Count;
}
_consumedDescriptorsPerSet[setIndex] = count;
}
} }
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection( public void UpdateCommandBufferIndex(int commandBufferIndex)
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
{ {
if (_dsLastCbIndex != commandBufferIndex) int submissionCount = _gd.CommandBufferPool.GetSubmissionCount(commandBufferIndex);
if (_dsLastCbIndex != commandBufferIndex || _dsLastSubmissionCount != submissionCount)
{ {
_dsLastCbIndex = commandBufferIndex; _dsLastCbIndex = commandBufferIndex;
_dsLastSubmissionCount = submissionCount;
for (int i = 0; i < _dsCacheCursor.Length; i++) Array.Clear(_dsCacheCursor);
{
_dsCacheCursor[i] = 0;
}
} }
var list = _dsCache[commandBufferIndex][setIndex]; _currentDsCache = _dsCache[commandBufferIndex];
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
var list = _currentDsCache[setIndex];
int index = _dsCacheCursor[setIndex]++; int index = _dsCacheCursor[setIndex]++;
if (index == list.Count) if (index == list.Count)
{ {
var dsc = gd.DescriptorSetManager.AllocateDescriptorSet(gd.Api, DescriptorSetLayouts[setIndex]); Span<DescriptorPoolSize> poolSizes = stackalloc DescriptorPoolSize[MaxPoolSizesPerSet];
poolSizes = GetDescriptorPoolSizes(poolSizes, setIndex);
int consumedDescriptors = _consumedDescriptorsPerSet[setIndex];
var dsc = _gd.DescriptorSetManager.AllocateDescriptorSet(
_gd.Api,
DescriptorSetLayouts[setIndex],
poolSizes,
setIndex,
consumedDescriptors,
false);
list.Add(dsc); list.Add(dsc);
isNew = true; isNew = true;
return dsc; return dsc;
@@ -76,6 +116,33 @@ namespace Ryujinx.Graphics.Vulkan
return list[index]; return list[index];
} }
private static Span<DescriptorPoolSize> GetDescriptorPoolSizes(Span<DescriptorPoolSize> output, int setIndex)
{
int count = 1;
switch (setIndex)
{
case PipelineBase.UniformSetIndex:
output[0] = new(DescriptorType.UniformBuffer, DefaultUniformBufferPoolCapacity);
break;
case PipelineBase.StorageSetIndex:
output[0] = new(DescriptorType.StorageBuffer, DefaultStorageBufferPoolCapacity);
break;
case PipelineBase.TextureSetIndex:
output[0] = new(DescriptorType.CombinedImageSampler, DefaultTexturePoolCapacity);
output[1] = new(DescriptorType.UniformTexelBuffer, DefaultTexturePoolCapacity);
count = 2;
break;
case PipelineBase.ImageSetIndex:
output[0] = new(DescriptorType.StorageImage, DefaultImagePoolCapacity);
output[1] = new(DescriptorType.StorageTexelBuffer, DefaultImagePoolCapacity);
count = 2;
break;
}
return output[..count];
}
protected virtual unsafe void Dispose(bool disposing) protected virtual unsafe void Dispose(bool disposing)
{ {
if (disposing) if (disposing)

View File

@@ -464,13 +464,14 @@ namespace Ryujinx.Graphics.Vulkan
return true; return true;
} }
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection( public void UpdateDescriptorCacheCommandBufferIndex(int commandBufferIndex)
VulkanRenderer gd,
int commandBufferIndex,
int setIndex,
out bool isNew)
{ {
return _plce.GetNewDescriptorSetCollection(gd, commandBufferIndex, setIndex, out isNew); _plce.UpdateCommandBufferIndex(commandBufferIndex);
}
public Auto<DescriptorSetCollection> GetNewDescriptorSetCollection(int setIndex, out bool isNew)
{
return _plce.GetNewDescriptorSetCollection(setIndex, out isNew);
} }
protected virtual void Dispose(bool disposing) protected virtual void Dispose(bool disposing)

View File

@@ -347,7 +347,7 @@ namespace Ryujinx.Graphics.Vulkan
CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex); CommandBufferPool = new CommandBufferPool(Api, _device, Queue, QueueLock, queueFamilyIndex);
DescriptorSetManager = new DescriptorSetManager(_device); DescriptorSetManager = new DescriptorSetManager(_device, PipelineBase.DescriptorSetLayouts);
PipelineLayoutCache = new PipelineLayoutCache(); PipelineLayoutCache = new PipelineLayoutCache();