/ src / Ryujinx.Graphics.Gpu / Memory / BufferCache.cs
BufferCache.cs
   1  using Ryujinx.Graphics.GAL;
   2  using Ryujinx.Memory.Range;
   3  using System;
   4  using System.Collections.Generic;
   5  using System.Linq;
   6  using System.Runtime.CompilerServices;
   7  
   8  namespace Ryujinx.Graphics.Gpu.Memory
   9  {
  10      /// <summary>
  11      /// Buffer cache.
  12      /// </summary>
  13      class BufferCache : IDisposable
  14      {
  15          /// <summary>
  16          /// Initial size for the array holding overlaps.
  17          /// </summary>
  18          public const int OverlapsBufferInitialCapacity = 10;
  19  
  20          /// <summary>
  21          /// Maximum size that an array holding overlaps may have after trimming.
  22          /// </summary>
  23          public const int OverlapsBufferMaxCapacity = 10000;
  24  
  25          private const ulong BufferAlignmentSize = 0x1000;
  26          private const ulong BufferAlignmentMask = BufferAlignmentSize - 1;
  27  
  28          /// <summary>
  29          /// Alignment required for sparse buffer mappings.
  30          /// </summary>
  31          public const ulong SparseBufferAlignmentSize = 0x10000;
  32  
  33          private const ulong MaxDynamicGrowthSize = 0x100000;
  34  
  35          private readonly GpuContext _context;
  36          private readonly PhysicalMemory _physicalMemory;
  37  
  38          /// <remarks>
  39          /// Only modified from the GPU thread. Must lock for add/remove.
  40          /// Must lock for any access from other threads.
  41          /// </remarks>
  42          private readonly RangeList<Buffer> _buffers;
  43          private readonly MultiRangeList<MultiRangeBuffer> _multiRangeBuffers;
  44  
  45          private Buffer[] _bufferOverlaps;
  46  
  47          private readonly Dictionary<ulong, BufferCacheEntry> _dirtyCache;
  48          private readonly Dictionary<ulong, BufferCacheEntry> _modifiedCache;
  49          private bool _pruneCaches;
  50          private int _virtualModifiedSequenceNumber;
  51  
  52          public event Action NotifyBuffersModified;
  53  
  54          /// <summary>
  55          /// Creates a new instance of the buffer manager.
  56          /// </summary>
  57          /// <param name="context">The GPU context that the buffer manager belongs to</param>
  58          /// <param name="physicalMemory">Physical memory where the cached buffers are mapped</param>
  59          public BufferCache(GpuContext context, PhysicalMemory physicalMemory)
  60          {
  61              _context = context;
  62              _physicalMemory = physicalMemory;
  63  
  64              _buffers = new RangeList<Buffer>();
  65              _multiRangeBuffers = new MultiRangeList<MultiRangeBuffer>();
  66  
  67              _bufferOverlaps = new Buffer[OverlapsBufferInitialCapacity];
  68  
  69              _dirtyCache = new Dictionary<ulong, BufferCacheEntry>();
  70  
  71              // There are a lot more entries on the modified cache, so it is separate from the one for ForceDirty.
  72              _modifiedCache = new Dictionary<ulong, BufferCacheEntry>();
  73          }
  74  
  75          /// <summary>
  76          /// Handles removal of buffers written to a memory region being unmapped.
  77          /// </summary>
  78          /// <param name="sender">Sender object</param>
  79          /// <param name="e">Event arguments</param>
  80          public void MemoryUnmappedHandler(object sender, UnmapEventArgs e)
  81          {
  82              Buffer[] overlaps = new Buffer[10];
  83              int overlapCount;
  84  
  85              MultiRange range = ((MemoryManager)sender).GetPhysicalRegions(e.Address, e.Size);
  86  
  87              for (int index = 0; index < range.Count; index++)
  88              {
  89                  MemoryRange subRange = range.GetSubRange(index);
  90  
  91                  lock (_buffers)
  92                  {
  93                      overlapCount = _buffers.FindOverlaps(subRange.Address, subRange.Size, ref overlaps);
  94                  }
  95  
  96                  for (int i = 0; i < overlapCount; i++)
  97                  {
  98                      overlaps[i].Unmapped(subRange.Address, subRange.Size);
  99                  }
 100              }
 101          }
 102  
 103          /// <summary>
 104          /// Performs address translation of the GPU virtual address, and creates a
 105          /// new buffer, if needed, for the specified contiguous range.
 106          /// </summary>
 107          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 108          /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
 109          /// <param name="size">Size in bytes of the buffer</param>
 110          /// <param name="stage">The type of usage that created the buffer</param>
 111          /// <returns>Contiguous physical range of the buffer, after address translation</returns>
 112          public MultiRange TranslateAndCreateBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
 113          {
 114              if (gpuVa == 0)
 115              {
 116                  return new MultiRange(MemoryManager.PteUnmapped, size);
 117              }
 118  
 119              ulong address = memoryManager.Translate(gpuVa);
 120  
 121              if (address != MemoryManager.PteUnmapped)
 122              {
 123                  CreateBuffer(address, size, stage);
 124              }
 125  
 126              return new MultiRange(address, size);
 127          }
 128  
 129          /// <summary>
 130          /// Performs address translation of the GPU virtual address, and creates
 131          /// new physical and virtual buffers, if needed, for the specified range.
 132          /// </summary>
 133          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 134          /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
 135          /// <param name="size">Size in bytes of the buffer</param>
 136          /// <param name="stage">The type of usage that created the buffer</param>
 137          /// <returns>Physical ranges of the buffer, after address translation</returns>
 138          public MultiRange TranslateAndCreateMultiBuffers(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
 139          {
 140              if (gpuVa == 0)
 141              {
 142                  return new MultiRange(MemoryManager.PteUnmapped, size);
 143              }
 144  
 145              // Fast path not taken for non-contiguous ranges,
 146              // since multi-range buffers are not coalesced, so a buffer that covers
 147              // the entire cached range might not actually exist.
 148              if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
 149                  range.Count == 1)
 150              {
 151                  return range;
 152              }
 153  
 154              CreateBuffer(range, stage);
 155  
 156              return range;
 157          }
 158  
 159          /// <summary>
 160          /// Performs address translation of the GPU virtual address, and creates
 161          /// new physical buffers, if needed, for the specified range.
 162          /// </summary>
 163          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 164          /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
 165          /// <param name="size">Size in bytes of the buffer</param>
 166          /// <param name="stage">The type of usage that created the buffer</param>
 167          /// <returns>Physical ranges of the buffer, after address translation</returns>
 168          public MultiRange TranslateAndCreateMultiBuffersPhysicalOnly(MemoryManager memoryManager, ulong gpuVa, ulong size, BufferStage stage)
 169          {
 170              if (gpuVa == 0)
 171              {
 172                  return new MultiRange(MemoryManager.PteUnmapped, size);
 173              }
 174  
 175              // Fast path not taken for non-contiguous ranges,
 176              // since multi-range buffers are not coalesced, so a buffer that covers
 177              // the entire cached range might not actually exist.
 178              if (memoryManager.VirtualRangeCache.TryGetOrAddRange(gpuVa, size, out MultiRange range) &&
 179                  range.Count == 1)
 180              {
 181                  return range;
 182              }
 183  
 184              for (int i = 0; i < range.Count; i++)
 185              {
 186                  MemoryRange subRange = range.GetSubRange(i);
 187  
 188                  if (subRange.Address != MemoryManager.PteUnmapped)
 189                  {
 190                      if (range.Count > 1)
 191                      {
 192                          CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
 193                      }
 194                      else
 195                      {
 196                          CreateBuffer(subRange.Address, subRange.Size, stage);
 197                      }
 198                  }
 199              }
 200  
 201              return range;
 202          }
 203  
 204          /// <summary>
 205          /// Creates a new buffer for the specified range, if it does not yet exist.
 206          /// This can be used to ensure the existance of a buffer.
 207          /// </summary>
 208          /// <param name="range">Physical ranges of memory where the buffer data is located</param>
 209          /// <param name="stage">The type of usage that created the buffer</param>
 210          public void CreateBuffer(MultiRange range, BufferStage stage)
 211          {
 212              if (range.Count > 1)
 213              {
 214                  CreateMultiRangeBuffer(range, stage);
 215              }
 216              else
 217              {
 218                  MemoryRange subRange = range.GetSubRange(0);
 219  
 220                  if (subRange.Address != MemoryManager.PteUnmapped)
 221                  {
 222                      CreateBuffer(subRange.Address, subRange.Size, stage);
 223                  }
 224              }
 225          }
 226  
 227          /// <summary>
 228          /// Creates a new buffer for the specified range, if it does not yet exist.
 229          /// This can be used to ensure the existance of a buffer.
 230          /// </summary>
 231          /// <param name="address">Address of the buffer in memory</param>
 232          /// <param name="size">Size of the buffer in bytes</param>
 233          /// <param name="stage">The type of usage that created the buffer</param>
 234          public void CreateBuffer(ulong address, ulong size, BufferStage stage)
 235          {
 236              ulong endAddress = address + size;
 237  
 238              ulong alignedAddress = address & ~BufferAlignmentMask;
 239              ulong alignedEndAddress = (endAddress + BufferAlignmentMask) & ~BufferAlignmentMask;
 240  
 241              // The buffer must have the size of at least one page.
 242              if (alignedEndAddress == alignedAddress)
 243              {
 244                  alignedEndAddress += BufferAlignmentSize;
 245              }
 246  
 247              CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage);
 248          }
 249  
 250          /// <summary>
 251          /// Creates a new buffer for the specified range, if it does not yet exist.
 252          /// This can be used to ensure the existance of a buffer.
 253          /// </summary>
 254          /// <param name="address">Address of the buffer in memory</param>
 255          /// <param name="size">Size of the buffer in bytes</param>
 256          /// <param name="stage">The type of usage that created the buffer</param>
 257          /// <param name="alignment">Alignment of the start address of the buffer in bytes</param>
 258          public void CreateBuffer(ulong address, ulong size, BufferStage stage, ulong alignment)
 259          {
 260              ulong alignmentMask = alignment - 1;
 261              ulong pageAlignmentMask = BufferAlignmentMask;
 262              ulong endAddress = address + size;
 263  
 264              ulong alignedAddress = address & ~alignmentMask;
 265              ulong alignedEndAddress = (endAddress + pageAlignmentMask) & ~pageAlignmentMask;
 266  
 267              // The buffer must have the size of at least one page.
 268              if (alignedEndAddress == alignedAddress)
 269              {
 270                  alignedEndAddress += pageAlignmentMask;
 271              }
 272  
 273              CreateBufferAligned(alignedAddress, alignedEndAddress - alignedAddress, stage, alignment);
 274          }
 275  
 276          /// <summary>
 277          /// Creates a buffer for a memory region composed of multiple physical ranges,
 278          /// if it does not exist yet.
 279          /// </summary>
 280          /// <param name="range">Physical ranges of memory</param>
 281          /// <param name="stage">The type of usage that created the buffer</param>
 282          private void CreateMultiRangeBuffer(MultiRange range, BufferStage stage)
 283          {
 284              // Ensure all non-contiguous buffer we might use are sparse aligned.
 285              for (int i = 0; i < range.Count; i++)
 286              {
 287                  MemoryRange subRange = range.GetSubRange(i);
 288  
 289                  if (subRange.Address != MemoryManager.PteUnmapped)
 290                  {
 291                      CreateBuffer(subRange.Address, subRange.Size, stage, SparseBufferAlignmentSize);
 292                  }
 293              }
 294  
 295              // Create sparse buffer.
 296              MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10];
 297  
 298              int overlapCount = _multiRangeBuffers.FindOverlaps(range, ref overlaps);
 299  
 300              for (int index = 0; index < overlapCount; index++)
 301              {
 302                  if (overlaps[index].Range.Contains(range))
 303                  {
 304                      return;
 305                  }
 306              }
 307  
 308              for (int index = 0; index < overlapCount; index++)
 309              {
 310                  if (range.Contains(overlaps[index].Range))
 311                  {
 312                      _multiRangeBuffers.Remove(overlaps[index]);
 313                      overlaps[index].Dispose();
 314                  }
 315              }
 316  
 317              MultiRangeBuffer multiRangeBuffer;
 318  
 319              MemoryRange[] alignedSubRanges = new MemoryRange[range.Count];
 320  
 321              ulong alignmentMask = SparseBufferAlignmentSize - 1;
 322  
 323              if (_context.Capabilities.SupportsSparseBuffer)
 324              {
 325                  BufferRange[] storages = new BufferRange[range.Count];
 326  
 327                  for (int i = 0; i < range.Count; i++)
 328                  {
 329                      MemoryRange subRange = range.GetSubRange(i);
 330  
 331                      if (subRange.Address != MemoryManager.PteUnmapped)
 332                      {
 333                          ulong endAddress = subRange.Address + subRange.Size;
 334  
 335                          ulong alignedAddress = subRange.Address & ~alignmentMask;
 336                          ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
 337                          ulong alignedSize = alignedEndAddress - alignedAddress;
 338  
 339                          Buffer buffer = _buffers.FindFirstOverlap(alignedAddress, alignedSize);
 340                          BufferRange bufferRange = buffer.GetRange(alignedAddress, alignedSize, false);
 341  
 342                          alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
 343                          storages[i] = bufferRange;
 344                      }
 345                      else
 346                      {
 347                          ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
 348  
 349                          alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
 350                          storages[i] = new BufferRange(BufferHandle.Null, 0, (int)alignedSize);
 351                      }
 352                  }
 353  
 354                  multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges), storages);
 355              }
 356              else
 357              {
 358                  for (int i = 0; i < range.Count; i++)
 359                  {
 360                      MemoryRange subRange = range.GetSubRange(i);
 361  
 362                      if (subRange.Address != MemoryManager.PteUnmapped)
 363                      {
 364                          ulong endAddress = subRange.Address + subRange.Size;
 365  
 366                          ulong alignedAddress = subRange.Address & ~alignmentMask;
 367                          ulong alignedEndAddress = (endAddress + alignmentMask) & ~alignmentMask;
 368                          ulong alignedSize = alignedEndAddress - alignedAddress;
 369  
 370                          alignedSubRanges[i] = new MemoryRange(alignedAddress, alignedSize);
 371                      }
 372                      else
 373                      {
 374                          ulong alignedSize = (subRange.Size + alignmentMask) & ~alignmentMask;
 375  
 376                          alignedSubRanges[i] = new MemoryRange(MemoryManager.PteUnmapped, alignedSize);
 377                      }
 378                  }
 379  
 380                  multiRangeBuffer = new(_context, new MultiRange(alignedSubRanges));
 381  
 382                  UpdateVirtualBufferDependencies(multiRangeBuffer);
 383              }
 384  
 385              _multiRangeBuffers.Add(multiRangeBuffer);
 386          }
 387  
 388          /// <summary>
 389          /// Adds two-way dependencies to all physical buffers contained within a given virtual buffer.
 390          /// </summary>
 391          /// <param name="virtualBuffer">Virtual buffer to have dependencies added</param>
 392          private void UpdateVirtualBufferDependencies(MultiRangeBuffer virtualBuffer)
 393          {
 394              virtualBuffer.ClearPhysicalDependencies();
 395  
 396              ulong dstOffset = 0;
 397  
 398              HashSet<Buffer> physicalBuffers = new();
 399  
 400              for (int i = 0; i < virtualBuffer.Range.Count; i++)
 401              {
 402                  MemoryRange subRange = virtualBuffer.Range.GetSubRange(i);
 403  
 404                  if (subRange.Address != MemoryManager.PteUnmapped)
 405                  {
 406                      Buffer buffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
 407  
 408                      virtualBuffer.AddPhysicalDependency(buffer, subRange.Address, dstOffset, subRange.Size);
 409                      physicalBuffers.Add(buffer);
 410                  }
 411  
 412                  dstOffset += subRange.Size;
 413              }
 414  
 415              foreach (var buffer in physicalBuffers)
 416              {
 417                  buffer.CopyToDependantVirtualBuffer(virtualBuffer);
 418              }
 419          }
 420  
 421          /// <summary>
 422          /// Performs address translation of the GPU virtual address, and attempts to force
 423          /// the buffer in the region as dirty.
 424          /// The buffer lookup for this function is cached in a dictionary for quick access, which
 425          /// accelerates common UBO updates.
 426          /// </summary>
 427          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 428          /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
 429          /// <param name="size">Size in bytes of the buffer</param>
 430          public void ForceDirty(MemoryManager memoryManager, ulong gpuVa, ulong size)
 431          {
 432              if (_pruneCaches)
 433              {
 434                  Prune();
 435              }
 436  
 437              if (!_dirtyCache.TryGetValue(gpuVa, out BufferCacheEntry result) ||
 438                  result.EndGpuAddress < gpuVa + size ||
 439                  result.UnmappedSequence != result.Buffer.UnmappedSequence)
 440              {
 441                  MultiRange range = TranslateAndCreateBuffer(memoryManager, gpuVa, size, BufferStage.Internal);
 442                  ulong address = range.GetSubRange(0).Address;
 443                  result = new BufferCacheEntry(address, gpuVa, GetBuffer(address, size, BufferStage.Internal));
 444  
 445                  _dirtyCache[gpuVa] = result;
 446              }
 447  
 448              result.Buffer.ForceDirty(result.Address, size);
 449          }
 450  
 451          /// <summary>
 452          /// Checks if the given buffer range has been GPU modifed.
 453          /// </summary>
 454          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 455          /// <param name="gpuVa">Start GPU virtual address of the buffer</param>
 456          /// <param name="size">Size in bytes of the buffer</param>
 457          /// <returns>True if modified, false otherwise</returns>
 458          public bool CheckModified(MemoryManager memoryManager, ulong gpuVa, ulong size, out ulong outAddr)
 459          {
 460              if (_pruneCaches)
 461              {
 462                  Prune();
 463              }
 464  
 465              // Align the address to avoid creating too many entries on the quick lookup dictionary.
 466              ulong mask = BufferAlignmentMask;
 467              ulong alignedGpuVa = gpuVa & (~mask);
 468              ulong alignedEndGpuVa = (gpuVa + size + mask) & (~mask);
 469  
 470              size = alignedEndGpuVa - alignedGpuVa;
 471  
 472              if (!_modifiedCache.TryGetValue(alignedGpuVa, out BufferCacheEntry result) ||
 473                  result.EndGpuAddress < alignedEndGpuVa ||
 474                  result.UnmappedSequence != result.Buffer.UnmappedSequence)
 475              {
 476                  MultiRange range = TranslateAndCreateBuffer(memoryManager, alignedGpuVa, size, BufferStage.None);
 477                  ulong address = range.GetSubRange(0).Address;
 478                  result = new BufferCacheEntry(address, alignedGpuVa, GetBuffer(address, size, BufferStage.None));
 479  
 480                  _modifiedCache[alignedGpuVa] = result;
 481              }
 482  
 483              outAddr = result.Address | (gpuVa & mask);
 484  
 485              return result.Buffer.IsModified(result.Address, size);
 486          }
 487  
 488          /// <summary>
 489          /// Creates a new buffer for the specified range, if needed.
 490          /// If a buffer where this range can be fully contained already exists,
 491          /// then the creation of a new buffer is not necessary.
 492          /// </summary>
 493          /// <param name="address">Address of the buffer in guest memory</param>
 494          /// <param name="size">Size in bytes of the buffer</param>
 495          /// <param name="stage">The type of usage that created the buffer</param>
 496          private void CreateBufferAligned(ulong address, ulong size, BufferStage stage)
 497          {
 498              Buffer[] overlaps = _bufferOverlaps;
 499              int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
 500  
 501              if (overlapsCount != 0)
 502              {
 503                  // The buffer already exists. We can just return the existing buffer
 504                  // if the buffer we need is fully contained inside the overlapping buffer.
 505                  // Otherwise, we must delete the overlapping buffers and create a bigger buffer
 506                  // that fits all the data we need. We also need to copy the contents from the
 507                  // old buffer(s) to the new buffer.
 508  
 509                  ulong endAddress = address + size;
 510                  Buffer overlap0 = overlaps[0];
 511  
 512                  if (overlap0.Address > address || overlap0.EndAddress < endAddress)
 513                  {
 514                      bool anySparseCompatible = false;
 515  
 516                      // Check if the following conditions are met:
 517                      // - We have a single overlap.
 518                      // - The overlap starts at or before the requested range. That is, the overlap happens at the end.
 519                      // - The size delta between the new, merged buffer and the old one is of at most 2 pages.
 520                      // In this case, we attempt to extend the buffer further than the requested range,
 521                      // this can potentially avoid future resizes if the application keeps using overlapping
 522                      // sequential memory.
 523                      // Allowing for 2 pages (rather than just one) is necessary to catch cases where the
 524                      // range crosses a page, and after alignment, ends having a size of 2 pages.
 525                      if (overlapsCount == 1 &&
 526                          address >= overlap0.Address &&
 527                          endAddress - overlap0.EndAddress <= BufferAlignmentSize * 2)
 528                      {
 529                          // Try to grow the buffer by 1.5x of its current size.
 530                          // This improves performance in the cases where the buffer is resized often by small amounts.
 531                          ulong existingSize = overlap0.Size;
 532                          ulong growthSize = (existingSize + Math.Min(existingSize >> 1, MaxDynamicGrowthSize)) & ~BufferAlignmentMask;
 533  
 534                          size = Math.Max(size, growthSize);
 535                          endAddress = address + size;
 536  
 537                          overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
 538                      }
 539  
 540                      for (int index = 0; index < overlapsCount; index++)
 541                      {
 542                          Buffer buffer = overlaps[index];
 543  
 544                          anySparseCompatible |= buffer.SparseCompatible;
 545  
 546                          address = Math.Min(address, buffer.Address);
 547                          endAddress = Math.Max(endAddress, buffer.EndAddress);
 548  
 549                          lock (_buffers)
 550                          {
 551                              _buffers.Remove(buffer);
 552                          }
 553                      }
 554  
 555                      ulong newSize = endAddress - address;
 556  
 557                      CreateBufferAligned(address, newSize, stage, anySparseCompatible, overlaps, overlapsCount);
 558                  }
 559              }
 560              else
 561              {
 562                  // No overlap, just create a new buffer.
 563                  Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseCompatible: false);
 564  
 565                  lock (_buffers)
 566                  {
 567                      _buffers.Add(buffer);
 568                  }
 569              }
 570  
 571              ShrinkOverlapsBufferIfNeeded();
 572          }
 573  
 574          /// <summary>
 575          /// Creates a new buffer for the specified range, if needed.
 576          /// If a buffer where this range can be fully contained already exists,
 577          /// then the creation of a new buffer is not necessary.
 578          /// </summary>
 579          /// <param name="address">Address of the buffer in guest memory</param>
 580          /// <param name="size">Size in bytes of the buffer</param>
 581          /// <param name="stage">The type of usage that created the buffer</param>
 582          /// <param name="alignment">Alignment of the start address of the buffer</param>
 583          private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, ulong alignment)
 584          {
 585              Buffer[] overlaps = _bufferOverlaps;
 586              int overlapsCount = _buffers.FindOverlapsNonOverlapping(address, size, ref overlaps);
 587              bool sparseAligned = alignment >= SparseBufferAlignmentSize;
 588  
 589              if (overlapsCount != 0)
 590              {
 591                  // If the buffer already exists, make sure if covers the entire range,
 592                  // and make sure it is properly aligned, otherwise sparse mapping may fail.
 593  
 594                  ulong endAddress = address + size;
 595                  Buffer overlap0 = overlaps[0];
 596  
 597                  if (overlap0.Address > address ||
 598                      overlap0.EndAddress < endAddress ||
 599                      (overlap0.Address & (alignment - 1)) != 0 ||
 600                      (!overlap0.SparseCompatible && sparseAligned))
 601                  {
 602                      // We need to make sure the new buffer is properly aligned.
 603                      // However, after the range is aligned, it is possible that it
 604                      // overlaps more buffers, so try again after each extension
 605                      // and ensure we cover all overlaps.
 606  
 607                      int oldOverlapsCount;
 608  
 609                      do
 610                      {
 611                          for (int index = 0; index < overlapsCount; index++)
 612                          {
 613                              Buffer buffer = overlaps[index];
 614  
 615                              address = Math.Min(address, buffer.Address);
 616                              endAddress = Math.Max(endAddress, buffer.EndAddress);
 617                          }
 618  
 619                          address &= ~(alignment - 1);
 620  
 621                          oldOverlapsCount = overlapsCount;
 622                          overlapsCount = _buffers.FindOverlapsNonOverlapping(address, endAddress - address, ref overlaps);
 623                      }
 624                      while (oldOverlapsCount != overlapsCount);
 625  
 626                      lock (_buffers)
 627                      {
 628                          for (int index = 0; index < overlapsCount; index++)
 629                          {
 630                              _buffers.Remove(overlaps[index]);
 631                          }
 632                      }
 633  
 634                      ulong newSize = endAddress - address;
 635  
 636                      CreateBufferAligned(address, newSize, stage, sparseAligned, overlaps, overlapsCount);
 637                  }
 638              }
 639              else
 640              {
 641                  // No overlap, just create a new buffer.
 642                  Buffer buffer = new(_context, _physicalMemory, address, size, stage, sparseAligned);
 643  
 644                  lock (_buffers)
 645                  {
 646                      _buffers.Add(buffer);
 647                  }
 648              }
 649  
 650              ShrinkOverlapsBufferIfNeeded();
 651          }
 652  
 653          /// <summary>
 654          /// Creates a new buffer for the specified range, if needed.
 655          /// If a buffer where this range can be fully contained already exists,
 656          /// then the creation of a new buffer is not necessary.
 657          /// </summary>
 658          /// <param name="address">Address of the buffer in guest memory</param>
 659          /// <param name="size">Size in bytes of the buffer</param>
 660          /// <param name="stage">The type of usage that created the buffer</param>
 661          /// <param name="sparseCompatible">Indicates if the buffer can be used in a sparse buffer mapping</param>
 662          /// <param name="overlaps">Buffers overlapping the range</param>
 663          /// <param name="overlapsCount">Total of overlaps</param>
 664          private void CreateBufferAligned(ulong address, ulong size, BufferStage stage, bool sparseCompatible, Buffer[] overlaps, int overlapsCount)
 665          {
 666              Buffer newBuffer = new Buffer(_context, _physicalMemory, address, size, stage, sparseCompatible, overlaps.Take(overlapsCount));
 667  
 668              lock (_buffers)
 669              {
 670                  _buffers.Add(newBuffer);
 671              }
 672  
 673              for (int index = 0; index < overlapsCount; index++)
 674              {
 675                  Buffer buffer = overlaps[index];
 676  
 677                  int dstOffset = (int)(buffer.Address - newBuffer.Address);
 678  
 679                  buffer.CopyTo(newBuffer, dstOffset);
 680                  newBuffer.InheritModifiedRanges(buffer);
 681  
 682                  buffer.DecrementReferenceCount();
 683              }
 684  
 685              newBuffer.SynchronizeMemory(address, size);
 686  
 687              // Existing buffers were modified, we need to rebind everything.
 688              NotifyBuffersModified?.Invoke();
 689  
 690              RecreateMultiRangeBuffers(address, size);
 691          }
 692  
 693          /// <summary>
 694          /// Recreates all the multi-range buffers that overlaps a given physical memory range.
 695          /// </summary>
 696          /// <param name="address">Start address of the range</param>
 697          /// <param name="size">Size of the range in bytes</param>
 698          private void RecreateMultiRangeBuffers(ulong address, ulong size)
 699          {
 700              if ((address & (SparseBufferAlignmentSize - 1)) != 0 || (size & (SparseBufferAlignmentSize - 1)) != 0)
 701              {
 702                  return;
 703              }
 704  
 705              MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10];
 706  
 707              int overlapCount = _multiRangeBuffers.FindOverlaps(address, size, ref overlaps);
 708  
 709              for (int index = 0; index < overlapCount; index++)
 710              {
 711                  _multiRangeBuffers.Remove(overlaps[index]);
 712                  overlaps[index].Dispose();
 713              }
 714  
 715              for (int index = 0; index < overlapCount; index++)
 716              {
 717                  CreateMultiRangeBuffer(overlaps[index].Range, BufferStage.None);
 718              }
 719          }
 720  
 721          /// <summary>
 722          /// Resizes the temporary buffer used for range list intersection results, if it has grown too much.
 723          /// </summary>
 724          private void ShrinkOverlapsBufferIfNeeded()
 725          {
 726              if (_bufferOverlaps.Length > OverlapsBufferMaxCapacity)
 727              {
 728                  Array.Resize(ref _bufferOverlaps, OverlapsBufferMaxCapacity);
 729              }
 730          }
 731  
 732          /// <summary>
 733          /// Copy a buffer data from a given address to another.
 734          /// </summary>
 735          /// <remarks>
 736          /// This does a GPU side copy.
 737          /// </remarks>
 738          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 739          /// <param name="srcVa">GPU virtual address of the copy source</param>
 740          /// <param name="dstVa">GPU virtual address of the copy destination</param>
 741          /// <param name="size">Size in bytes of the copy</param>
 742          public void CopyBuffer(MemoryManager memoryManager, ulong srcVa, ulong dstVa, ulong size)
 743          {
 744              MultiRange srcRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, srcVa, size, BufferStage.Copy);
 745              MultiRange dstRange = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, dstVa, size, BufferStage.Copy);
 746  
 747              if (srcRange.Count == 1 && dstRange.Count == 1)
 748              {
 749                  CopyBufferSingleRange(memoryManager, srcRange.GetSubRange(0).Address, dstRange.GetSubRange(0).Address, size);
 750              }
 751              else
 752              {
 753                  ulong copiedSize = 0;
 754                  ulong srcOffset = 0;
 755                  ulong dstOffset = 0;
 756                  int srcRangeIndex = 0;
 757                  int dstRangeIndex = 0;
 758  
 759                  while (copiedSize < size)
 760                  {
 761                      if (srcRange.GetSubRange(srcRangeIndex).Size == srcOffset)
 762                      {
 763                          srcRangeIndex++;
 764                          srcOffset = 0;
 765                      }
 766  
 767                      if (dstRange.GetSubRange(dstRangeIndex).Size == dstOffset)
 768                      {
 769                          dstRangeIndex++;
 770                          dstOffset = 0;
 771                      }
 772  
 773                      MemoryRange srcSubRange = srcRange.GetSubRange(srcRangeIndex);
 774                      MemoryRange dstSubRange = dstRange.GetSubRange(dstRangeIndex);
 775  
 776                      ulong srcSize = srcSubRange.Size - srcOffset;
 777                      ulong dstSize = dstSubRange.Size - dstOffset;
 778                      ulong copySize = Math.Min(srcSize, dstSize);
 779  
 780                      CopyBufferSingleRange(memoryManager, srcSubRange.Address + srcOffset, dstSubRange.Address + dstOffset, copySize);
 781  
 782                      srcOffset += copySize;
 783                      dstOffset += copySize;
 784                      copiedSize += copySize;
 785                  }
 786              }
 787          }
 788  
 789          /// <summary>
 790          /// Copy a buffer data from a given address to another.
 791          /// </summary>
 792          /// <remarks>
 793          /// This does a GPU side copy.
 794          /// </remarks>
 795          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 796          /// <param name="srcAddress">Physical address of the copy source</param>
 797          /// <param name="dstAddress">Physical address of the copy destination</param>
 798          /// <param name="size">Size in bytes of the copy</param>
 799          private void CopyBufferSingleRange(MemoryManager memoryManager, ulong srcAddress, ulong dstAddress, ulong size)
 800          {
 801              Buffer srcBuffer = GetBuffer(srcAddress, size, BufferStage.Copy);
 802              Buffer dstBuffer = GetBuffer(dstAddress, size, BufferStage.Copy);
 803  
 804              int srcOffset = (int)(srcAddress - srcBuffer.Address);
 805              int dstOffset = (int)(dstAddress - dstBuffer.Address);
 806  
 807              _context.Renderer.Pipeline.CopyBuffer(
 808                  srcBuffer.Handle,
 809                  dstBuffer.Handle,
 810                  srcOffset,
 811                  dstOffset,
 812                  (int)size);
 813  
 814              if (srcBuffer.IsModified(srcAddress, size))
 815              {
 816                  dstBuffer.SignalModified(dstAddress, size, BufferStage.Copy);
 817              }
 818              else
 819              {
 820                  // Optimization: If the data being copied is already in memory, then copy it directly instead of flushing from GPU.
 821  
 822                  dstBuffer.ClearModified(dstAddress, size);
 823                  memoryManager.Physical.WriteTrackedResource(dstAddress, memoryManager.Physical.GetSpan(srcAddress, (int)size), ResourceKind.Buffer);
 824              }
 825  
 826              dstBuffer.CopyToDependantVirtualBuffers(dstAddress, size);
 827          }
 828  
 829          /// <summary>
 830          /// Clears a buffer at a given address with the specified value.
 831          /// </summary>
 832          /// <remarks>
 833          /// Both the address and size must be aligned to 4 bytes.
 834          /// </remarks>
 835          /// <param name="memoryManager">GPU memory manager where the buffer is mapped</param>
 836          /// <param name="gpuVa">GPU virtual address of the region to clear</param>
 837          /// <param name="size">Number of bytes to clear</param>
 838          /// <param name="value">Value to be written into the buffer</param>
 839          public void ClearBuffer(MemoryManager memoryManager, ulong gpuVa, ulong size, uint value)
 840          {
 841              MultiRange range = TranslateAndCreateMultiBuffersPhysicalOnly(memoryManager, gpuVa, size, BufferStage.Copy);
 842  
 843              for (int index = 0; index < range.Count; index++)
 844              {
 845                  MemoryRange subRange = range.GetSubRange(index);
 846                  Buffer buffer = GetBuffer(subRange.Address, subRange.Size, BufferStage.Copy);
 847  
 848                  int offset = (int)(subRange.Address - buffer.Address);
 849  
 850                  _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)subRange.Size, value);
 851  
 852                  memoryManager.Physical.FillTrackedResource(subRange.Address, subRange.Size, value, ResourceKind.Buffer);
 853  
 854                  buffer.CopyToDependantVirtualBuffers(subRange.Address, subRange.Size);
 855              }
 856          }
 857  
 858          /// <summary>
 859          /// Gets a buffer sub-range starting at a given memory address, aligned to the next page boundary.
 860          /// </summary>
 861          /// <param name="range">Physical regions of memory where the buffer is mapped</param>
 862          /// <param name="stage">Buffer stage that triggered the access</param>
 863          /// <param name="write">Whether the buffer will be written to by this use</param>
 864          /// <returns>The buffer sub-range starting at the given memory address</returns>
 865          public BufferRange GetBufferRangeAligned(MultiRange range, BufferStage stage, bool write = false)
 866          {
 867              if (range.Count > 1)
 868              {
 869                  return GetBuffer(range, stage, write).GetRange(range);
 870              }
 871              else
 872              {
 873                  MemoryRange subRange = range.GetSubRange(0);
 874                  return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRangeAligned(subRange.Address, subRange.Size, write);
 875              }
 876          }
 877  
 878          /// <summary>
 879          /// Gets a buffer sub-range for a given memory range.
 880          /// </summary>
 881          /// <param name="range">Physical regions of memory where the buffer is mapped</param>
 882          /// <param name="stage">Buffer stage that triggered the access</param>
 883          /// <param name="write">Whether the buffer will be written to by this use</param>
 884          /// <returns>The buffer sub-range for the given range</returns>
 885          public BufferRange GetBufferRange(MultiRange range, BufferStage stage, bool write = false)
 886          {
 887              if (range.Count > 1)
 888              {
 889                  return GetBuffer(range, stage, write).GetRange(range);
 890              }
 891              else
 892              {
 893                  MemoryRange subRange = range.GetSubRange(0);
 894                  return GetBuffer(subRange.Address, subRange.Size, stage, write).GetRange(subRange.Address, subRange.Size, write);
 895              }
 896          }
 897  
 898          /// <summary>
 899          /// Gets a buffer for a given memory range.
 900          /// A buffer overlapping with the specified range is assumed to already exist on the cache.
 901          /// </summary>
 902          /// <param name="range">Physical regions of memory where the buffer is mapped</param>
 903          /// <param name="stage">Buffer stage that triggered the access</param>
 904          /// <param name="write">Whether the buffer will be written to by this use</param>
 905          /// <returns>The buffer where the range is fully contained</returns>
 906          private MultiRangeBuffer GetBuffer(MultiRange range, BufferStage stage, bool write = false)
 907          {
 908              for (int i = 0; i < range.Count; i++)
 909              {
 910                  MemoryRange subRange = range.GetSubRange(i);
 911  
 912                  Buffer subBuffer = _buffers.FindFirstOverlap(subRange.Address, subRange.Size);
 913  
 914                  subBuffer.SynchronizeMemory(subRange.Address, subRange.Size);
 915  
 916                  if (write)
 917                  {
 918                      subBuffer.SignalModified(subRange.Address, subRange.Size, stage);
 919                  }
 920              }
 921  
 922              MultiRangeBuffer[] overlaps = new MultiRangeBuffer[10];
 923  
 924              int overlapCount = _multiRangeBuffers.FindOverlaps(range, ref overlaps);
 925  
 926              MultiRangeBuffer buffer = null;
 927  
 928              for (int i = 0; i < overlapCount; i++)
 929              {
 930                  if (overlaps[i].Range.Contains(range))
 931                  {
 932                      buffer = overlaps[i];
 933                      break;
 934                  }
 935              }
 936  
 937              if (write && buffer != null && !_context.Capabilities.SupportsSparseBuffer)
 938              {
 939                  buffer.AddModifiedRegion(range, ++_virtualModifiedSequenceNumber);
 940              }
 941  
 942              return buffer;
 943          }
 944  
 945          /// <summary>
 946          /// Gets a buffer for a given memory range.
 947          /// A buffer overlapping with the specified range is assumed to already exist on the cache.
 948          /// </summary>
 949          /// <param name="address">Start address of the memory range</param>
 950          /// <param name="size">Size in bytes of the memory range</param>
 951          /// <param name="stage">Buffer stage that triggered the access</param>
 952          /// <param name="write">Whether the buffer will be written to by this use</param>
 953          /// <returns>The buffer where the range is fully contained</returns>
 954          private Buffer GetBuffer(ulong address, ulong size, BufferStage stage, bool write = false)
 955          {
 956              Buffer buffer;
 957  
 958              if (size != 0)
 959              {
 960                  buffer = _buffers.FindFirstOverlap(address, size);
 961  
 962                  buffer.CopyFromDependantVirtualBuffers();
 963                  buffer.SynchronizeMemory(address, size);
 964  
 965                  if (write)
 966                  {
 967                      buffer.SignalModified(address, size, stage);
 968                  }
 969              }
 970              else
 971              {
 972                  buffer = _buffers.FindFirstOverlap(address, 1);
 973              }
 974  
 975              return buffer;
 976          }
 977  
 978          /// <summary>
 979          /// Performs guest to host memory synchronization of a given memory range.
 980          /// </summary>
 981          /// <param name="range">Physical regions of memory where the buffer is mapped</param>
 982          public void SynchronizeBufferRange(MultiRange range)
 983          {
 984              if (range.Count == 1)
 985              {
 986                  MemoryRange subRange = range.GetSubRange(0);
 987                  SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: true);
 988              }
 989              else
 990              {
 991                  for (int index = 0; index < range.Count; index++)
 992                  {
 993                      MemoryRange subRange = range.GetSubRange(index);
 994                      SynchronizeBufferRange(subRange.Address, subRange.Size, copyBackVirtual: false);
 995                  }
 996              }
 997          }
 998  
 999          /// <summary>
1000          /// Performs guest to host memory synchronization of a given memory range.
1001          /// </summary>
1002          /// <param name="address">Start address of the memory range</param>
1003          /// <param name="size">Size in bytes of the memory range</param>
1004          /// <param name="copyBackVirtual">Whether virtual buffers that uses this buffer as backing memory should have its data copied back if modified</param>
1005          [MethodImpl(MethodImplOptions.AggressiveInlining)]
1006          private void SynchronizeBufferRange(ulong address, ulong size, bool copyBackVirtual)
1007          {
1008              if (size != 0)
1009              {
1010                  Buffer buffer = _buffers.FindFirstOverlap(address, size);
1011  
1012                  if (copyBackVirtual)
1013                  {
1014                      buffer.CopyFromDependantVirtualBuffers();
1015                  }
1016  
1017                  buffer.SynchronizeMemory(address, size);
1018              }
1019          }
1020  
1021          /// <summary>
1022          /// Signal that the given buffer's handle has changed,
1023          /// forcing rebind and any overlapping multi-range buffers to be recreated.
1024          /// </summary>
1025          /// <param name="buffer">The buffer that has changed handle</param>
1026          public void BufferBackingChanged(Buffer buffer)
1027          {
1028              NotifyBuffersModified?.Invoke();
1029  
1030              RecreateMultiRangeBuffers(buffer.Address, buffer.Size);
1031          }
1032  
1033          /// <summary>
1034          /// Prune any invalid entries from a quick access dictionary.
1035          /// </summary>
1036          /// <param name="dictionary">Dictionary to prune</param>
1037          /// <param name="toDelete">List used to track entries to delete</param>
1038          private static void Prune(Dictionary<ulong, BufferCacheEntry> dictionary, ref List<ulong> toDelete)
1039          {
1040              foreach (var entry in dictionary)
1041              {
1042                  if (entry.Value.UnmappedSequence != entry.Value.Buffer.UnmappedSequence)
1043                  {
1044                      (toDelete ??= new()).Add(entry.Key);
1045                  }
1046              }
1047  
1048              if (toDelete != null)
1049              {
1050                  foreach (ulong entry in toDelete)
1051                  {
1052                      dictionary.Remove(entry);
1053                  }
1054              }
1055          }
1056  
1057          /// <summary>
1058          /// Prune any invalid entries from the quick access dictionaries.
1059          /// </summary>
1060          private void Prune()
1061          {
1062              List<ulong> toDelete = null;
1063  
1064              Prune(_dirtyCache, ref toDelete);
1065  
1066              toDelete?.Clear();
1067  
1068              Prune(_modifiedCache, ref toDelete);
1069  
1070              _pruneCaches = false;
1071          }
1072  
1073          /// <summary>
1074          /// Queues a prune of invalid entries the next time a dictionary cache is accessed.
1075          /// </summary>
1076          public void QueuePrune()
1077          {
1078              _pruneCaches = true;
1079          }
1080  
1081          /// <summary>
1082          /// Disposes all buffers in the cache.
1083          /// It's an error to use the buffer cache after disposal.
1084          /// </summary>
1085          public void Dispose()
1086          {
1087              lock (_buffers)
1088              {
1089                  foreach (Buffer buffer in _buffers)
1090                  {
1091                      buffer.Dispose();
1092                  }
1093              }
1094          }
1095      }
1096  }