/ src / Ryujinx.Graphics.Gpu / Memory / VirtualRangeCache.cs
VirtualRangeCache.cs
  1  using Ryujinx.Memory.Range;
  2  using System;
  3  using System.Collections.Concurrent;
  4  using System.Threading;
  5  
  6  namespace Ryujinx.Graphics.Gpu.Memory
  7  {
  8      /// <summary>
  9      /// Virtual range cache.
 10      /// </summary>
 11      class VirtualRangeCache
 12      {
 13          private readonly MemoryManager _memoryManager;
 14  
 15          /// <summary>
 16          /// Represents a GPU virtual memory range.
 17          /// </summary>
 18          private readonly struct VirtualRange : IRange
 19          {
 20              /// <summary>
 21              /// GPU virtual address where the range starts.
 22              /// </summary>
 23              public ulong Address { get; }
 24  
 25              /// <summary>
 26              /// Size of the range in bytes.
 27              /// </summary>
 28              public ulong Size { get; }
 29  
 30              /// <summary>
 31              /// GPU virtual address where the range ends.
 32              /// </summary>
 33              public ulong EndAddress => Address + Size;
 34  
 35              /// <summary>
 36              /// Physical regions where the GPU virtual region is mapped.
 37              /// </summary>
 38              public MultiRange Range { get; }
 39  
 40              /// <summary>
 41              /// Creates a new virtual memory range.
 42              /// </summary>
 43              /// <param name="address">GPU virtual address where the range starts</param>
 44              /// <param name="size">Size of the range in bytes</param>
 45              /// <param name="range">Physical regions where the GPU virtual region is mapped</param>
 46              public VirtualRange(ulong address, ulong size, MultiRange range)
 47              {
 48                  Address = address;
 49                  Size = size;
 50                  Range = range;
 51              }
 52  
 53              /// <summary>
 54              /// Checks if a given range overlaps with the buffer.
 55              /// </summary>
 56              /// <param name="address">Start address of the range</param>
 57              /// <param name="size">Size in bytes of the range</param>
 58              /// <returns>True if the range overlaps, false otherwise</returns>
 59              public bool OverlapsWith(ulong address, ulong size)
 60              {
 61                  return Address < address + size && address < EndAddress;
 62              }
 63          }
 64  
 65          private readonly RangeList<VirtualRange> _virtualRanges;
 66          private VirtualRange[] _virtualRangeOverlaps;
 67          private readonly ConcurrentQueue<VirtualRange> _deferredUnmaps;
 68          private int _hasDeferredUnmaps;
 69  
 70          /// <summary>
 71          /// Creates a new instance of the virtual range cache.
 72          /// </summary>
 73          /// <param name="memoryManager">Memory manager that the virtual range cache belongs to</param>
 74          public VirtualRangeCache(MemoryManager memoryManager)
 75          {
 76              _memoryManager = memoryManager;
 77              _virtualRanges = new RangeList<VirtualRange>();
 78              _virtualRangeOverlaps = new VirtualRange[BufferCache.OverlapsBufferInitialCapacity];
 79              _deferredUnmaps = new ConcurrentQueue<VirtualRange>();
 80          }
 81  
 82          /// <summary>
 83          /// Handles removal of buffers written to a memory region being unmapped.
 84          /// </summary>
 85          /// <param name="sender">Sender object</param>
 86          /// <param name="e">Event arguments</param>
 87          public void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
 88          {
 89              void EnqueueUnmap()
 90              {
 91                  _deferredUnmaps.Enqueue(new VirtualRange(e.Address, e.Size, default));
 92  
 93                  Interlocked.Exchange(ref _hasDeferredUnmaps, 1);
 94              }
 95  
 96              e.AddRemapAction(EnqueueUnmap);
 97          }
 98  
 99          /// <summary>
100          /// Tries to get a existing, cached physical range for the specified virtual region.
101          /// If no cached range is found, a new one is created and added.
102          /// </summary>
103          /// <param name="gpuVa">GPU virtual address to get the physical range from</param>
104          /// <param name="size">Size in bytes of the region</param>
105          /// <param name="range">Physical range for the specified GPU virtual region</param>
106          /// <returns>True if the range already existed, false if a new one was created and added</returns>
107          public bool TryGetOrAddRange(ulong gpuVa, ulong size, out MultiRange range)
108          {
109              VirtualRange[] overlaps = _virtualRangeOverlaps;
110              int overlapsCount;
111  
112              if (Interlocked.Exchange(ref _hasDeferredUnmaps, 0) != 0)
113              {
114                  while (_deferredUnmaps.TryDequeue(out VirtualRange unmappedRange))
115                  {
116                      overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(unmappedRange.Address, unmappedRange.Size, ref overlaps);
117  
118                      for (int index = 0; index < overlapsCount; index++)
119                      {
120                          _virtualRanges.Remove(overlaps[index]);
121                      }
122                  }
123              }
124  
125              bool found = false;
126  
127              ulong originalVa = gpuVa;
128  
129              overlapsCount = _virtualRanges.FindOverlapsNonOverlapping(gpuVa, size, ref overlaps);
130  
131              if (overlapsCount != 0)
132              {
133                  // The virtual range already exists. We just need to check if our range fits inside
134                  // the existing one, and if not, we must extend the existing one.
135  
136                  ulong endAddress = gpuVa + size;
137                  VirtualRange overlap0 = overlaps[0];
138  
139                  if (overlap0.Address > gpuVa || overlap0.EndAddress < endAddress)
140                  {
141                      for (int index = 0; index < overlapsCount; index++)
142                      {
143                          VirtualRange virtualRange = overlaps[index];
144  
145                          gpuVa = Math.Min(gpuVa, virtualRange.Address);
146                          endAddress = Math.Max(endAddress, virtualRange.EndAddress);
147  
148                          _virtualRanges.Remove(virtualRange);
149                      }
150  
151                      ulong newSize = endAddress - gpuVa;
152                      MultiRange newRange = _memoryManager.GetPhysicalRegions(gpuVa, newSize);
153  
154                      _virtualRanges.Add(new(gpuVa, newSize, newRange));
155  
156                      range = newRange.Slice(originalVa - gpuVa, size);
157                  }
158                  else
159                  {
160                      found = overlap0.Range.Count == 1 || IsSparseAligned(overlap0.Range);
161                      range = overlap0.Range.Slice(gpuVa - overlap0.Address, size);
162                  }
163              }
164              else
165              {
166                  // No overlap, just create a new virtual range.
167                  range = _memoryManager.GetPhysicalRegions(gpuVa, size);
168  
169                  VirtualRange virtualRange = new(gpuVa, size, range);
170  
171                  _virtualRanges.Add(virtualRange);
172              }
173  
174              ShrinkOverlapsBufferIfNeeded();
175  
176              // If the range is not properly aligned for sparse mapping,
177              // let's just force it to a single range.
178              // This might cause issues in some applications that uses sparse
179              // mappings.
180              if (!IsSparseAligned(range))
181              {
182                  range = new MultiRange(range.GetSubRange(0).Address, size);
183              }
184  
185              return found;
186          }
187  
188          /// <summary>
189          /// Checks if the physical memory ranges are valid for sparse mapping,
190          /// which requires all sub-ranges to be 64KB aligned.
191          /// </summary>
192          /// <param name="range">Range to check</param>
193          /// <returns>True if the range is valid for sparse mapping, false otherwise</returns>
194          private static bool IsSparseAligned(MultiRange range)
195          {
196              if (range.Count == 1)
197              {
198                  return (range.GetSubRange(0).Address & (BufferCache.SparseBufferAlignmentSize - 1)) == 0;
199              }
200  
201              for (int i = 0; i < range.Count; i++)
202              {
203                  MemoryRange subRange = range.GetSubRange(i);
204  
205                  // Check if address is aligned. The address of the first sub-range can
206                  // be misaligned as it is at the start.
207                  if (i > 0 &&
208                      subRange.Address != MemoryManager.PteUnmapped &&
209                      (subRange.Address & (BufferCache.SparseBufferAlignmentSize - 1)) != 0)
210                  {
211                      return false;
212                  }
213  
214                  // Check if the size is aligned. The size of the last sub-range can
215                  // be misaligned as it is at the end.
216                  if (i < range.Count - 1 && (subRange.Size & (BufferCache.SparseBufferAlignmentSize - 1)) != 0)
217                  {
218                      return false;
219                  }
220              }
221  
222              return true;
223          }
224  
225          /// <summary>
226          /// Resizes the temporary buffer used for range list intersection results, if it has grown too much.
227          /// </summary>
228          private void ShrinkOverlapsBufferIfNeeded()
229          {
230              if (_virtualRangeOverlaps.Length > BufferCache.OverlapsBufferMaxCapacity)
231              {
232                  Array.Resize(ref _virtualRangeOverlaps, BufferCache.OverlapsBufferMaxCapacity);
233              }
234          }
235      }
236  }