/ src / Ryujinx.Graphics.Vulkan / HostMemoryAllocator.cs
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  }