Buffer.cs 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643
  1. using Ryujinx.Graphics.GAL;
  2. using Ryujinx.Graphics.Gpu.Synchronization;
  3. using Ryujinx.Memory.Range;
  4. using Ryujinx.Memory.Tracking;
  5. using System;
  6. using System.Collections.Generic;
  7. using System.Linq;
  8. namespace Ryujinx.Graphics.Gpu.Memory
  9. {
  10. /// <summary>
  11. /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
  12. /// </summary>
  13. class Buffer : IRange, ISyncActionHandler, IDisposable
  14. {
  15. private const ulong GranularBufferThreshold = 4096;
  16. private readonly GpuContext _context;
  17. private readonly PhysicalMemory _physicalMemory;
  18. /// <summary>
  19. /// Host buffer handle.
  20. /// </summary>
  21. public BufferHandle Handle { get; }
  22. /// <summary>
  23. /// Start address of the buffer in guest memory.
  24. /// </summary>
  25. public ulong Address { get; }
  26. /// <summary>
  27. /// Size of the buffer in bytes.
  28. /// </summary>
  29. public ulong Size { get; }
  30. /// <summary>
  31. /// End address of the buffer in guest memory.
  32. /// </summary>
  33. public ulong EndAddress => Address + Size;
  34. /// <summary>
  35. /// Increments when the buffer is (partially) unmapped or disposed.
  36. /// </summary>
  37. public int UnmappedSequence { get; private set; }
  38. /// <summary>
  39. /// Ranges of the buffer that have been modified on the GPU.
  40. /// Ranges defined here cannot be updated from CPU until a CPU waiting sync point is reached.
  41. /// Then, write tracking will signal, wait for GPU sync (generated at the syncpoint) and flush these regions.
  42. /// </summary>
  43. /// <remarks>
  44. /// This is null until at least one modification occurs.
  45. /// </remarks>
  46. private BufferModifiedRangeList _modifiedRanges = null;
  47. private readonly MultiRegionHandle _memoryTrackingGranular;
  48. private readonly RegionHandle _memoryTracking;
  49. private readonly RegionSignal _externalFlushDelegate;
  50. private readonly Action<ulong, ulong> _loadDelegate;
  51. private readonly Action<ulong, ulong> _modifiedDelegate;
  52. private int _sequenceNumber;
  53. private readonly bool _useGranular;
  54. private bool _syncActionRegistered;
  55. private int _referenceCount = 1;
  56. private ulong _dirtyStart = ulong.MaxValue;
  57. private ulong _dirtyEnd = ulong.MaxValue;
  58. /// <summary>
  59. /// Creates a new instance of the buffer.
  60. /// </summary>
  61. /// <param name="context">GPU context that the buffer belongs to</param>
  62. /// <param name="physicalMemory">Physical memory where the buffer is mapped</param>
  63. /// <param name="address">Start address of the buffer</param>
  64. /// <param name="size">Size of the buffer in bytes</param>
  65. /// <param name="baseBuffers">Buffers which this buffer contains, and will inherit tracking handles from</param>
  66. public Buffer(GpuContext context, PhysicalMemory physicalMemory, ulong address, ulong size, IEnumerable<Buffer> baseBuffers = null)
  67. {
  68. _context = context;
  69. _physicalMemory = physicalMemory;
  70. Address = address;
  71. Size = size;
  72. Handle = context.Renderer.CreateBuffer((int)size, baseBuffers?.MaxBy(x => x.Size).Handle ?? BufferHandle.Null);
  73. _useGranular = size > GranularBufferThreshold;
  74. IEnumerable<IRegionHandle> baseHandles = null;
  75. if (baseBuffers != null)
  76. {
  77. baseHandles = baseBuffers.SelectMany(buffer =>
  78. {
  79. if (buffer._useGranular)
  80. {
  81. return buffer._memoryTrackingGranular.GetHandles();
  82. }
  83. else
  84. {
  85. return Enumerable.Repeat(buffer._memoryTracking, 1);
  86. }
  87. });
  88. }
  89. if (_useGranular)
  90. {
  91. _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles);
  92. _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
  93. }
  94. else
  95. {
  96. _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer);
  97. if (baseHandles != null)
  98. {
  99. _memoryTracking.Reprotect(false);
  100. foreach (IRegionHandle handle in baseHandles)
  101. {
  102. if (handle.Dirty)
  103. {
  104. _memoryTracking.Reprotect(true);
  105. }
  106. handle.Dispose();
  107. }
  108. }
  109. _memoryTracking.RegisterPreciseAction(PreciseAction);
  110. }
  111. _externalFlushDelegate = new RegionSignal(ExternalFlush);
  112. _loadDelegate = new Action<ulong, ulong>(LoadRegion);
  113. _modifiedDelegate = new Action<ulong, ulong>(RegionModified);
  114. }
  115. /// <summary>
  116. /// Gets a sub-range from the buffer, from a start address till the end of the buffer.
  117. /// </summary>
  118. /// <remarks>
  119. /// This can be used to bind and use sub-ranges of the buffer on the host API.
  120. /// </remarks>
  121. /// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
  122. /// <returns>The buffer sub-range</returns>
  123. public BufferRange GetRange(ulong address)
  124. {
  125. ulong offset = address - Address;
  126. return new BufferRange(Handle, (int)offset, (int)(Size - offset));
  127. }
  128. /// <summary>
  129. /// Gets a sub-range from the buffer.
  130. /// </summary>
  131. /// <remarks>
  132. /// This can be used to bind and use sub-ranges of the buffer on the host API.
  133. /// </remarks>
  134. /// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
  135. /// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
  136. /// <returns>The buffer sub-range</returns>
  137. public BufferRange GetRange(ulong address, ulong size)
  138. {
  139. int offset = (int)(address - Address);
  140. return new BufferRange(Handle, offset, (int)size);
  141. }
  142. /// <summary>
  143. /// Checks if a given range overlaps with the buffer.
  144. /// </summary>
  145. /// <param name="address">Start address of the range</param>
  146. /// <param name="size">Size in bytes of the range</param>
  147. /// <returns>True if the range overlaps, false otherwise</returns>
  148. public bool OverlapsWith(ulong address, ulong size)
  149. {
  150. return Address < address + size && address < EndAddress;
  151. }
  152. /// <summary>
  153. /// Checks if a given range is fully contained in the buffer.
  154. /// </summary>
  155. /// <param name="address">Start address of the range</param>
  156. /// <param name="size">Size in bytes of the range</param>
  157. /// <returns>True if the range is contained, false otherwise</returns>
  158. public bool FullyContains(ulong address, ulong size)
  159. {
  160. return address >= Address && address + size <= EndAddress;
  161. }
  162. /// <summary>
  163. /// Performs guest to host memory synchronization of the buffer data.
  164. /// </summary>
  165. /// <remarks>
  166. /// This causes the buffer data to be overwritten if a write was detected from the CPU,
  167. /// since the last call to this method.
  168. /// </remarks>
  169. /// <param name="address">Start address of the range to synchronize</param>
  170. /// <param name="size">Size in bytes of the range to synchronize</param>
  171. public void SynchronizeMemory(ulong address, ulong size)
  172. {
  173. if (_useGranular)
  174. {
  175. _memoryTrackingGranular.QueryModified(address, size, _modifiedDelegate, _context.SequenceNumber);
  176. }
  177. else
  178. {
  179. if (_context.SequenceNumber != _sequenceNumber && _memoryTracking.DirtyOrVolatile())
  180. {
  181. _memoryTracking.Reprotect();
  182. if (_modifiedRanges != null)
  183. {
  184. _modifiedRanges.ExcludeModifiedRegions(Address, Size, _loadDelegate);
  185. }
  186. else
  187. {
  188. _context.Renderer.SetBufferData(Handle, 0, _physicalMemory.GetSpan(Address, (int)Size));
  189. }
  190. _sequenceNumber = _context.SequenceNumber;
  191. _dirtyStart = ulong.MaxValue;
  192. }
  193. }
  194. if (_dirtyStart != ulong.MaxValue)
  195. {
  196. ulong end = address + size;
  197. if (end > _dirtyStart && address < _dirtyEnd)
  198. {
  199. if (_modifiedRanges != null)
  200. {
  201. _modifiedRanges.ExcludeModifiedRegions(_dirtyStart, _dirtyEnd - _dirtyStart, _loadDelegate);
  202. }
  203. else
  204. {
  205. LoadRegion(_dirtyStart, _dirtyEnd - _dirtyStart);
  206. }
  207. _dirtyStart = ulong.MaxValue;
  208. }
  209. }
  210. }
  211. /// <summary>
  212. /// Ensure that the modified range list exists.
  213. /// </summary>
  214. private void EnsureRangeList()
  215. {
  216. _modifiedRanges ??= new BufferModifiedRangeList(_context, this, Flush);
  217. }
  218. /// <summary>
  219. /// Signal that the given region of the buffer has been modified.
  220. /// </summary>
  221. /// <param name="address">The start address of the modified region</param>
  222. /// <param name="size">The size of the modified region</param>
  223. public void SignalModified(ulong address, ulong size)
  224. {
  225. EnsureRangeList();
  226. _modifiedRanges.SignalModified(address, size);
  227. if (!_syncActionRegistered)
  228. {
  229. _context.RegisterSyncAction(this);
  230. _syncActionRegistered = true;
  231. }
  232. }
  233. /// <summary>
  234. /// Indicate that mofifications in a given region of this buffer have been overwritten.
  235. /// </summary>
  236. /// <param name="address">The start address of the region</param>
  237. /// <param name="size">The size of the region</param>
  238. public void ClearModified(ulong address, ulong size)
  239. {
  240. _modifiedRanges?.Clear(address, size);
  241. }
  242. /// <summary>
  243. /// Action to be performed when a syncpoint is reached after modification.
  244. /// This will register read/write tracking to flush the buffer from GPU when its memory is used.
  245. /// </summary>
  246. /// <inheritdoc/>
  247. public bool SyncAction(bool syncpoint)
  248. {
  249. _syncActionRegistered = false;
  250. if (_useGranular)
  251. {
  252. _modifiedRanges?.GetRanges(Address, Size, (address, size) =>
  253. {
  254. _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
  255. SynchronizeMemory(address, size);
  256. });
  257. }
  258. else
  259. {
  260. _memoryTracking.RegisterAction(_externalFlushDelegate);
  261. SynchronizeMemory(Address, Size);
  262. }
  263. return true;
  264. }
  265. /// <summary>
  266. /// Inherit modified and dirty ranges from another buffer.
  267. /// </summary>
  268. /// <param name="from">The buffer to inherit from</param>
  269. public void InheritModifiedRanges(Buffer from)
  270. {
  271. if (from._modifiedRanges != null && from._modifiedRanges.HasRanges)
  272. {
  273. if (from._syncActionRegistered && !_syncActionRegistered)
  274. {
  275. _context.RegisterSyncAction(this);
  276. _syncActionRegistered = true;
  277. }
  278. void registerRangeAction(ulong address, ulong size)
  279. {
  280. if (_useGranular)
  281. {
  282. _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
  283. }
  284. else
  285. {
  286. _memoryTracking.RegisterAction(_externalFlushDelegate);
  287. }
  288. }
  289. EnsureRangeList();
  290. _modifiedRanges.InheritRanges(from._modifiedRanges, registerRangeAction);
  291. }
  292. if (from._dirtyStart != ulong.MaxValue)
  293. {
  294. ForceDirty(from._dirtyStart, from._dirtyEnd - from._dirtyStart);
  295. }
  296. }
  297. /// <summary>
  298. /// Determine if a given region of the buffer has been modified, and must be flushed.
  299. /// </summary>
  300. /// <param name="address">The start address of the region</param>
  301. /// <param name="size">The size of the region</param>
  302. /// <returns></returns>
  303. public bool IsModified(ulong address, ulong size)
  304. {
  305. if (_modifiedRanges != null)
  306. {
  307. return _modifiedRanges.HasRange(address, size);
  308. }
  309. return false;
  310. }
  311. /// <summary>
  312. /// Clear the dirty range that overlaps with the given region.
  313. /// </summary>
  314. /// <param name="address">Start address of the modified region</param>
  315. /// <param name="size">Size of the modified region</param>
  316. private void ClearDirty(ulong address, ulong size)
  317. {
  318. if (_dirtyStart != ulong.MaxValue)
  319. {
  320. ulong end = address + size;
  321. if (end > _dirtyStart && address < _dirtyEnd)
  322. {
  323. if (address <= _dirtyStart)
  324. {
  325. // Cut off the start.
  326. if (end < _dirtyEnd)
  327. {
  328. _dirtyStart = end;
  329. }
  330. else
  331. {
  332. _dirtyStart = ulong.MaxValue;
  333. }
  334. }
  335. else if (end >= _dirtyEnd)
  336. {
  337. // Cut off the end.
  338. _dirtyEnd = address;
  339. }
  340. // If fully contained, do nothing.
  341. }
  342. }
  343. }
  344. /// <summary>
  345. /// Indicate that a region of the buffer was modified, and must be loaded from memory.
  346. /// </summary>
  347. /// <param name="mAddress">Start address of the modified region</param>
  348. /// <param name="mSize">Size of the modified region</param>
  349. private void RegionModified(ulong mAddress, ulong mSize)
  350. {
  351. if (mAddress < Address)
  352. {
  353. mAddress = Address;
  354. }
  355. ulong maxSize = Address + Size - mAddress;
  356. if (mSize > maxSize)
  357. {
  358. mSize = maxSize;
  359. }
  360. ClearDirty(mAddress, mSize);
  361. if (_modifiedRanges != null)
  362. {
  363. _modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate);
  364. }
  365. else
  366. {
  367. LoadRegion(mAddress, mSize);
  368. }
  369. }
  370. /// <summary>
  371. /// Load a region of the buffer from memory.
  372. /// </summary>
  373. /// <param name="mAddress">Start address of the modified region</param>
  374. /// <param name="mSize">Size of the modified region</param>
  375. private void LoadRegion(ulong mAddress, ulong mSize)
  376. {
  377. int offset = (int)(mAddress - Address);
  378. _context.Renderer.SetBufferData(Handle, offset, _physicalMemory.GetSpan(mAddress, (int)mSize));
  379. }
  380. /// <summary>
  381. /// Force a region of the buffer to be dirty within the memory tracking. Avoids reprotection and nullifies sequence number check.
  382. /// </summary>
  383. /// <param name="mAddress">Start address of the modified region</param>
  384. /// <param name="mSize">Size of the region to force dirty</param>
  385. private void ForceTrackingDirty(ulong mAddress, ulong mSize)
  386. {
  387. if (_useGranular)
  388. {
  389. _memoryTrackingGranular.ForceDirty(mAddress, mSize);
  390. }
  391. else
  392. {
  393. _memoryTracking.ForceDirty();
  394. _sequenceNumber--;
  395. }
  396. }
  397. /// <summary>
  398. /// Force a region of the buffer to be dirty. Avoids reprotection and nullifies sequence number check.
  399. /// </summary>
  400. /// <param name="mAddress">Start address of the modified region</param>
  401. /// <param name="mSize">Size of the region to force dirty</param>
  402. public void ForceDirty(ulong mAddress, ulong mSize)
  403. {
  404. _modifiedRanges?.Clear(mAddress, mSize);
  405. ulong end = mAddress + mSize;
  406. if (_dirtyStart == ulong.MaxValue)
  407. {
  408. _dirtyStart = mAddress;
  409. _dirtyEnd = end;
  410. }
  411. else
  412. {
  413. // Is the new range more than a page away from the existing one?
  414. if ((long)(mAddress - _dirtyEnd) >= (long)MemoryManager.PageSize ||
  415. (long)(_dirtyStart - end) >= (long)MemoryManager.PageSize)
  416. {
  417. ForceTrackingDirty(mAddress, mSize);
  418. }
  419. else
  420. {
  421. _dirtyStart = Math.Min(_dirtyStart, mAddress);
  422. _dirtyEnd = Math.Max(_dirtyEnd, end);
  423. }
  424. }
  425. }
  426. /// <summary>
  427. /// Performs copy of all the buffer data from one buffer to another.
  428. /// </summary>
  429. /// <param name="destination">The destination buffer to copy the data into</param>
  430. /// <param name="dstOffset">The offset of the destination buffer to copy into</param>
  431. public void CopyTo(Buffer destination, int dstOffset)
  432. {
  433. _context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
  434. }
  435. /// <summary>
  436. /// Flushes a range of the buffer.
  437. /// This writes the range data back into guest memory.
  438. /// </summary>
  439. /// <param name="address">Start address of the range</param>
  440. /// <param name="size">Size in bytes of the range</param>
  441. public void Flush(ulong address, ulong size)
  442. {
  443. int offset = (int)(address - Address);
  444. using PinnedSpan<byte> data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
  445. // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
  446. _physicalMemory.WriteUntracked(address, data.Get());
  447. }
  448. /// <summary>
  449. /// Align a given address and size region to page boundaries.
  450. /// </summary>
  451. /// <param name="address">The start address of the region</param>
  452. /// <param name="size">The size of the region</param>
  453. /// <returns>The page aligned address and size</returns>
  454. private static (ulong address, ulong size) PageAlign(ulong address, ulong size)
  455. {
  456. ulong pageMask = MemoryManager.PageMask;
  457. ulong rA = address & ~pageMask;
  458. ulong rS = ((address + size + pageMask) & ~pageMask) - rA;
  459. return (rA, rS);
  460. }
  461. /// <summary>
  462. /// Flush modified ranges of the buffer from another thread.
  463. /// This will flush all modifications made before the active SyncNumber was set, and may block to wait for GPU sync.
  464. /// </summary>
  465. /// <param name="address">Address of the memory action</param>
  466. /// <param name="size">Size in bytes</param>
  467. public void ExternalFlush(ulong address, ulong size)
  468. {
  469. _context.Renderer.BackgroundContextAction(() =>
  470. {
  471. var ranges = _modifiedRanges;
  472. if (ranges != null)
  473. {
  474. (address, size) = PageAlign(address, size);
  475. ranges.WaitForAndFlushRanges(address, size);
  476. }
  477. }, true);
  478. }
  479. /// <summary>
  480. /// An action to be performed when a precise memory access occurs to this resource.
  481. /// For buffers, this skips flush-on-write by punching holes directly into the modified range list.
  482. /// </summary>
  483. /// <param name="address">Address of the memory action</param>
  484. /// <param name="size">Size in bytes</param>
  485. /// <param name="write">True if the access was a write, false otherwise</param>
  486. private bool PreciseAction(ulong address, ulong size, bool write)
  487. {
  488. if (!write)
  489. {
  490. // We only want to skip flush-on-write.
  491. return false;
  492. }
  493. ulong maxAddress = Math.Max(address, Address);
  494. ulong minEndAddress = Math.Min(address + size, Address + Size);
  495. if (maxAddress >= minEndAddress)
  496. {
  497. // Access doesn't overlap.
  498. return false;
  499. }
  500. ForceDirty(maxAddress, minEndAddress - maxAddress);
  501. return true;
  502. }
  503. /// <summary>
  504. /// Called when part of the memory for this buffer has been unmapped.
  505. /// Calls are from non-GPU threads.
  506. /// </summary>
  507. /// <param name="address">Start address of the unmapped region</param>
  508. /// <param name="size">Size of the unmapped region</param>
  509. public void Unmapped(ulong address, ulong size)
  510. {
  511. BufferModifiedRangeList modifiedRanges = _modifiedRanges;
  512. modifiedRanges?.Clear(address, size);
  513. UnmappedSequence++;
  514. }
  515. /// <summary>
  516. /// Increments the buffer reference count.
  517. /// </summary>
  518. public void IncrementReferenceCount()
  519. {
  520. _referenceCount++;
  521. }
  522. /// <summary>
  523. /// Decrements the buffer reference count.
  524. /// </summary>
  525. public void DecrementReferenceCount()
  526. {
  527. if (--_referenceCount == 0)
  528. {
  529. DisposeData();
  530. }
  531. }
  532. /// <summary>
  533. /// Disposes the host buffer's data, not its tracking handles.
  534. /// </summary>
  535. public void DisposeData()
  536. {
  537. _modifiedRanges?.Clear();
  538. _context.Renderer.DeleteBuffer(Handle);
  539. UnmappedSequence++;
  540. }
  541. /// <summary>
  542. /// Disposes the host buffer.
  543. /// </summary>
  544. public void Dispose()
  545. {
  546. _memoryTrackingGranular?.Dispose();
  547. _memoryTracking?.Dispose();
  548. DecrementReferenceCount();
  549. }
  550. }
  551. }