HostMemoryAllocator.cs
1 using Ryujinx.Common; 2 using Ryujinx.Common.Collections; 3 using Ryujinx.Common.Logging; 4 using Silk.NET.Vulkan; 5 using Silk.NET.Vulkan.Extensions.EXT; 6 using System; 7 using System.Collections.Generic; 8 9 namespace Ryujinx.Graphics.Vulkan 10 { 11 internal class HostMemoryAllocator 12 { 13 private readonly struct HostMemoryAllocation 14 { 15 public readonly Auto<MemoryAllocation> Allocation; 16 public readonly IntPtr Pointer; 17 public readonly ulong Size; 18 19 public ulong Start => (ulong)Pointer; 20 public ulong End => (ulong)Pointer + Size; 21 22 public HostMemoryAllocation(Auto<MemoryAllocation> allocation, IntPtr pointer, ulong size) 23 { 24 Allocation = allocation; 25 Pointer = pointer; 26 Size = size; 27 } 28 } 29 30 private readonly MemoryAllocator _allocator; 31 private readonly Vk _api; 32 private readonly ExtExternalMemoryHost _hostMemoryApi; 33 private readonly Device _device; 34 private readonly object _lock = new(); 35 36 private readonly List<HostMemoryAllocation> _allocations; 37 private readonly IntervalTree<ulong, HostMemoryAllocation> _allocationTree; 38 39 public HostMemoryAllocator(MemoryAllocator allocator, Vk api, ExtExternalMemoryHost hostMemoryApi, Device device) 40 { 41 _allocator = allocator; 42 _api = api; 43 _hostMemoryApi = hostMemoryApi; 44 _device = device; 45 46 _allocations = new List<HostMemoryAllocation>(); 47 _allocationTree = new IntervalTree<ulong, HostMemoryAllocation>(); 48 } 49 50 public unsafe bool TryImport( 51 MemoryRequirements requirements, 52 MemoryPropertyFlags flags, 53 IntPtr pointer, 54 ulong size) 55 { 56 lock (_lock) 57 { 58 // Does a compatible allocation exist in the tree? 59 var allocations = new HostMemoryAllocation[10]; 60 61 ulong start = (ulong)pointer; 62 ulong end = start + size; 63 64 int count = _allocationTree.Get(start, end, ref allocations); 65 66 // A compatible range is one that where the start and end completely cover the requested range. 67 for (int i = 0; i < count; i++) 68 { 69 HostMemoryAllocation existing = allocations[i]; 70 71 if (start >= existing.Start && end <= existing.End) 72 { 73 try 74 { 75 existing.Allocation.IncrementReferenceCount(); 76 77 return true; 78 } 79 catch (InvalidOperationException) 80 { 81 // Can throw if the allocation has been disposed. 82 // Just continue the search if this happens. 83 } 84 } 85 } 86 87 nint pageAlignedPointer = BitUtils.AlignDown(pointer, Environment.SystemPageSize); 88 nint pageAlignedEnd = BitUtils.AlignUp((nint)((ulong)pointer + size), Environment.SystemPageSize); 89 ulong pageAlignedSize = (ulong)(pageAlignedEnd - pageAlignedPointer); 90 91 Result getResult = _hostMemoryApi.GetMemoryHostPointerProperties(_device, ExternalMemoryHandleTypeFlags.HostAllocationBitExt, (void*)pageAlignedPointer, out MemoryHostPointerPropertiesEXT properties); 92 if (getResult < Result.Success) 93 { 94 return false; 95 } 96 97 int memoryTypeIndex = _allocator.FindSuitableMemoryTypeIndex(properties.MemoryTypeBits & requirements.MemoryTypeBits, flags); 98 if (memoryTypeIndex < 0) 99 { 100 return false; 101 } 102 103 ImportMemoryHostPointerInfoEXT importInfo = new() 104 { 105 SType = StructureType.ImportMemoryHostPointerInfoExt, 106 HandleType = ExternalMemoryHandleTypeFlags.HostAllocationBitExt, 107 PHostPointer = (void*)pageAlignedPointer, 108 }; 109 110 var memoryAllocateInfo = new MemoryAllocateInfo 111 { 112 SType = StructureType.MemoryAllocateInfo, 113 AllocationSize = pageAlignedSize, 114 MemoryTypeIndex = (uint)memoryTypeIndex, 115 PNext = &importInfo, 116 }; 117 118 Result result = _api.AllocateMemory(_device, in memoryAllocateInfo, null, out var deviceMemory); 119 120 if (result < Result.Success) 121 { 122 Logger.Debug?.PrintMsg(LogClass.Gpu, $"Host mapping import 0x{pageAlignedPointer:x16} 0x{pageAlignedSize:x8} failed."); 123 return false; 124 } 125 126 var allocation = new MemoryAllocation(this, deviceMemory, pageAlignedPointer, 0, pageAlignedSize); 127 var allocAuto = new Auto<MemoryAllocation>(allocation); 128 var hostAlloc = new HostMemoryAllocation(allocAuto, pageAlignedPointer, pageAlignedSize); 129 130 allocAuto.IncrementReferenceCount(); 131 allocAuto.Dispose(); // Kept alive by ref count only. 132 133 // Register this mapping for future use. 134 135 _allocationTree.Add(hostAlloc.Start, hostAlloc.End, hostAlloc); 136 _allocations.Add(hostAlloc); 137 } 138 139 return true; 140 } 141 142 public (Auto<MemoryAllocation>, ulong) GetExistingAllocation(IntPtr pointer, ulong size) 143 { 144 lock (_lock) 145 { 146 // Does a compatible allocation exist in the tree? 147 var allocations = new HostMemoryAllocation[10]; 148 149 ulong start = (ulong)pointer; 150 ulong end = start + size; 151 152 int count = _allocationTree.Get(start, end, ref allocations); 153 154 // A compatible range is one that where the start and end completely cover the requested range. 155 for (int i = 0; i < count; i++) 156 { 157 HostMemoryAllocation existing = allocations[i]; 158 159 if (start >= existing.Start && end <= existing.End) 160 { 161 return (existing.Allocation, start - existing.Start); 162 } 163 } 164 165 throw new InvalidOperationException($"No host allocation was prepared for requested range 0x{pointer:x16}:0x{size:x16}."); 166 } 167 } 168 169 public void Free(DeviceMemory memory, ulong offset, ulong size) 170 { 171 lock (_lock) 172 { 173 _allocations.RemoveAll(allocation => 174 { 175 if (allocation.Allocation.GetUnsafe().Memory.Handle == memory.Handle) 176 { 177 _allocationTree.Remove(allocation.Start, allocation); 178 return true; 179 } 180 181 return false; 182 }); 183 } 184 185 _api.FreeMemory(_device, memory, ReadOnlySpan<AllocationCallbacks>.Empty); 186 } 187 } 188 }