Buffer.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388
  1. using Ryujinx.Cpu.Tracking;
  2. using Ryujinx.Graphics.GAL;
  3. using Ryujinx.Memory.Range;
  4. using Ryujinx.Memory.Tracking;
  5. using System;
  6. namespace Ryujinx.Graphics.Gpu.Memory
  7. {
  8. /// <summary>
  9. /// Buffer, used to store vertex and index data, uniform and storage buffers, and others.
  10. /// </summary>
  11. class Buffer : IRange, IDisposable
  12. {
  13. private static ulong GranularBufferThreshold = 4096;
  14. private readonly GpuContext _context;
  15. /// <summary>
  16. /// Host buffer handle.
  17. /// </summary>
  18. public BufferHandle Handle { get; }
  19. /// <summary>
  20. /// Start address of the buffer in guest memory.
  21. /// </summary>
  22. public ulong Address { get; }
  23. /// <summary>
  24. /// Size of the buffer in bytes.
  25. /// </summary>
  26. public ulong Size { get; }
  27. /// <summary>
  28. /// End address of the buffer in guest memory.
  29. /// </summary>
  30. public ulong EndAddress => Address + Size;
  31. /// <summary>
  32. /// Ranges of the buffer that have been modified on the GPU.
  33. /// Ranges defined here cannot be updated from CPU until a CPU waiting sync point is reached.
  34. /// Then, write tracking will signal, wait for GPU sync (generated at the syncpoint) and flush these regions.
  35. /// </summary>
  36. /// <remarks>
  37. /// This is null until at least one modification occurs.
  38. /// </remarks>
  39. private BufferModifiedRangeList _modifiedRanges = null;
  40. private CpuMultiRegionHandle _memoryTrackingGranular;
  41. private CpuRegionHandle _memoryTracking;
  42. private readonly RegionSignal _externalFlushDelegate;
  43. private readonly Action<ulong, ulong> _loadDelegate;
  44. private readonly Action<ulong, ulong> _modifiedDelegate;
  45. private int _sequenceNumber;
  46. private bool _useGranular;
  47. private bool _syncActionRegistered;
  48. /// <summary>
  49. /// Creates a new instance of the buffer.
  50. /// </summary>
  51. /// <param name="context">GPU context that the buffer belongs to</param>
  52. /// <param name="address">Start address of the buffer</param>
  53. /// <param name="size">Size of the buffer in bytes</param>
  54. public Buffer(GpuContext context, ulong address, ulong size)
  55. {
  56. _context = context;
  57. Address = address;
  58. Size = size;
  59. Handle = context.Renderer.CreateBuffer((int)size);
  60. _useGranular = size > GranularBufferThreshold;
  61. if (_useGranular)
  62. {
  63. _memoryTrackingGranular = context.PhysicalMemory.BeginGranularTracking(address, size);
  64. }
  65. else
  66. {
  67. _memoryTracking = context.PhysicalMemory.BeginTracking(address, size);
  68. }
  69. _externalFlushDelegate = new RegionSignal(ExternalFlush);
  70. _loadDelegate = new Action<ulong, ulong>(LoadRegion);
  71. _modifiedDelegate = new Action<ulong, ulong>(RegionModified);
  72. }
  73. /// <summary>
  74. /// Gets a sub-range from the buffer.
  75. /// </summary>
  76. /// <remarks>
  77. /// This can be used to bind and use sub-ranges of the buffer on the host API.
  78. /// </remarks>
  79. /// <param name="address">Start address of the sub-range, must be greater than or equal to the buffer address</param>
  80. /// <param name="size">Size in bytes of the sub-range, must be less than or equal to the buffer size</param>
  81. /// <returns>The buffer sub-range</returns>
  82. public BufferRange GetRange(ulong address, ulong size)
  83. {
  84. int offset = (int)(address - Address);
  85. return new BufferRange(Handle, offset, (int)size);
  86. }
  87. /// <summary>
  88. /// Checks if a given range overlaps with the buffer.
  89. /// </summary>
  90. /// <param name="address">Start address of the range</param>
  91. /// <param name="size">Size in bytes of the range</param>
  92. /// <returns>True if the range overlaps, false otherwise</returns>
  93. public bool OverlapsWith(ulong address, ulong size)
  94. {
  95. return Address < address + size && address < EndAddress;
  96. }
  97. /// <summary>
  98. /// Performs guest to host memory synchronization of the buffer data.
  99. /// </summary>
  100. /// <remarks>
  101. /// This causes the buffer data to be overwritten if a write was detected from the CPU,
  102. /// since the last call to this method.
  103. /// </remarks>
  104. /// <param name="address">Start address of the range to synchronize</param>
  105. /// <param name="size">Size in bytes of the range to synchronize</param>
  106. public void SynchronizeMemory(ulong address, ulong size)
  107. {
  108. if (_useGranular)
  109. {
  110. _memoryTrackingGranular.QueryModified(address, size, _modifiedDelegate, _context.SequenceNumber);
  111. }
  112. else
  113. {
  114. if (_memoryTracking.Dirty && _context.SequenceNumber != _sequenceNumber)
  115. {
  116. _memoryTracking.Reprotect();
  117. if (_modifiedRanges != null)
  118. {
  119. _modifiedRanges.ExcludeModifiedRegions(Address, Size, _loadDelegate);
  120. }
  121. else
  122. {
  123. _context.Renderer.SetBufferData(Handle, 0, _context.PhysicalMemory.GetSpan(Address, (int)Size));
  124. }
  125. _sequenceNumber = _context.SequenceNumber;
  126. }
  127. }
  128. }
  129. /// <summary>
  130. /// Ensure that the modified range list exists.
  131. /// </summary>
  132. private void EnsureRangeList()
  133. {
  134. if (_modifiedRanges == null)
  135. {
  136. _modifiedRanges = new BufferModifiedRangeList(_context);
  137. }
  138. }
  139. /// <summary>
  140. /// Signal that the given region of the buffer has been modified.
  141. /// </summary>
  142. /// <param name="address">The start address of the modified region</param>
  143. /// <param name="size">The size of the modified region</param>
  144. public void SignalModified(ulong address, ulong size)
  145. {
  146. EnsureRangeList();
  147. _modifiedRanges.SignalModified(address, size);
  148. if (!_syncActionRegistered)
  149. {
  150. _context.RegisterSyncAction(SyncAction);
  151. _syncActionRegistered = true;
  152. }
  153. }
  154. /// <summary>
  155. /// Indicate that mofifications in a given region of this buffer have been overwritten.
  156. /// </summary>
  157. /// <param name="address">The start address of the region</param>
  158. /// <param name="size">The size of the region</param>
  159. public void ClearModified(ulong address, ulong size)
  160. {
  161. if (_modifiedRanges != null)
  162. {
  163. _modifiedRanges.Clear(address, size);
  164. }
  165. }
  166. /// <summary>
  167. /// Action to be performed when a syncpoint is reached after modification.
  168. /// This will register read/write tracking to flush the buffer from GPU when its memory is used.
  169. /// </summary>
  170. private void SyncAction()
  171. {
  172. _syncActionRegistered = false;
  173. if (_useGranular)
  174. {
  175. _modifiedRanges.GetRanges(Address, Size, (address, size) =>
  176. {
  177. _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
  178. SynchronizeMemory(address, size);
  179. });
  180. }
  181. else
  182. {
  183. _memoryTracking.RegisterAction(_externalFlushDelegate);
  184. SynchronizeMemory(Address, Size);
  185. }
  186. }
  187. /// <summary>
  188. /// Inherit modified ranges from another buffer.
  189. /// </summary>
  190. /// <param name="from">The buffer to inherit from</param>
  191. public void InheritModifiedRanges(Buffer from)
  192. {
  193. if (from._modifiedRanges != null)
  194. {
  195. if (from._syncActionRegistered && !_syncActionRegistered)
  196. {
  197. _context.RegisterSyncAction(SyncAction);
  198. _syncActionRegistered = true;
  199. }
  200. EnsureRangeList();
  201. _modifiedRanges.InheritRanges(from._modifiedRanges, (ulong address, ulong size) =>
  202. {
  203. if (_useGranular)
  204. {
  205. _memoryTrackingGranular.RegisterAction(address, size, _externalFlushDelegate);
  206. }
  207. else
  208. {
  209. _memoryTracking.RegisterAction(_externalFlushDelegate);
  210. }
  211. });
  212. }
  213. }
  214. /// <summary>
  215. /// Determine if a given region of the buffer has been modified, and must be flushed.
  216. /// </summary>
  217. /// <param name="address">The start address of the region</param>
  218. /// <param name="size">The size of the region</param>
  219. /// <returns></returns>
  220. public bool IsModified(ulong address, ulong size)
  221. {
  222. if (_modifiedRanges != null)
  223. {
  224. return _modifiedRanges.HasRange(address, size);
  225. }
  226. return false;
  227. }
  228. /// <summary>
  229. /// Indicate that a region of the buffer was modified, and must be loaded from memory.
  230. /// </summary>
  231. /// <param name="mAddress">Start address of the modified region</param>
  232. /// <param name="mSize">Size of the modified region</param>
  233. private void RegionModified(ulong mAddress, ulong mSize)
  234. {
  235. if (mAddress < Address)
  236. {
  237. mAddress = Address;
  238. }
  239. ulong maxSize = Address + Size - mAddress;
  240. if (mSize > maxSize)
  241. {
  242. mSize = maxSize;
  243. }
  244. if (_modifiedRanges != null)
  245. {
  246. _modifiedRanges.ExcludeModifiedRegions(mAddress, mSize, _loadDelegate);
  247. }
  248. else
  249. {
  250. LoadRegion(mAddress, mSize);
  251. }
  252. }
  253. /// <summary>
  254. /// Load a region of the buffer from memory.
  255. /// </summary>
  256. /// <param name="mAddress">Start address of the modified region</param>
  257. /// <param name="mSize">Size of the modified region</param>
  258. private void LoadRegion(ulong mAddress, ulong mSize)
  259. {
  260. int offset = (int)(mAddress - Address);
  261. _context.Renderer.SetBufferData(Handle, offset, _context.PhysicalMemory.GetSpan(mAddress, (int)mSize));
  262. }
  263. /// <summary>
  264. /// Performs copy of all the buffer data from one buffer to another.
  265. /// </summary>
  266. /// <param name="destination">The destination buffer to copy the data into</param>
  267. /// <param name="dstOffset">The offset of the destination buffer to copy into</param>
  268. public void CopyTo(Buffer destination, int dstOffset)
  269. {
  270. _context.Renderer.Pipeline.CopyBuffer(Handle, destination.Handle, 0, dstOffset, (int)Size);
  271. }
  272. /// <summary>
  273. /// Flushes a range of the buffer.
  274. /// This writes the range data back into guest memory.
  275. /// </summary>
  276. /// <param name="address">Start address of the range</param>
  277. /// <param name="size">Size in bytes of the range</param>
  278. public void Flush(ulong address, ulong size)
  279. {
  280. int offset = (int)(address - Address);
  281. byte[] data = _context.Renderer.GetBufferData(Handle, offset, (int)size);
  282. // TODO: When write tracking shaders, they will need to be aware of changes in overlapping buffers.
  283. _context.PhysicalMemory.WriteUntracked(address, data);
  284. }
  285. /// <summary>
  286. /// Align a given address and size region to page boundaries.
  287. /// </summary>
  288. /// <param name="address">The start address of the region</param>
  289. /// <param name="size">The size of the region</param>
  290. /// <returns>The page aligned address and size</returns>
  291. private static (ulong address, ulong size) PageAlign(ulong address, ulong size)
  292. {
  293. ulong pageMask = MemoryManager.PageMask;
  294. ulong rA = address & ~pageMask;
  295. ulong rS = ((address + size + pageMask) & ~pageMask) - rA;
  296. return (rA, rS);
  297. }
  298. /// <summary>
  299. /// Flush modified ranges of the buffer from another thread.
  300. /// This will flush all modifications made before the active SyncNumber was set, and may block to wait for GPU sync.
  301. /// </summary>
  302. /// <param name="address">Address of the memory action</param>
  303. /// <param name="size">Size in bytes</param>
  304. public void ExternalFlush(ulong address, ulong size)
  305. {
  306. _context.Renderer.BackgroundContextAction(() =>
  307. {
  308. var ranges = _modifiedRanges;
  309. if (ranges != null)
  310. {
  311. (address, size) = PageAlign(address, size);
  312. ranges.WaitForAndGetRanges(address, size, Flush);
  313. }
  314. });
  315. }
  316. /// <summary>
  317. /// Called when part of the memory for this buffer has been unmapped.
  318. /// Calls are from non-GPU threads.
  319. /// </summary>
  320. /// <param name="address">Start address of the unmapped region</param>
  321. /// <param name="size">Size of the unmapped region</param>
  322. public void Unmapped(ulong address, ulong size)
  323. {
  324. _modifiedRanges?.Clear(address, size);
  325. }
  326. /// <summary>
  327. /// Disposes the host buffer.
  328. /// </summary>
  329. public void Dispose()
  330. {
  331. _modifiedRanges?.Clear();
  332. _memoryTrackingGranular?.Dispose();
  333. _memoryTracking?.Dispose();
  334. _context.Renderer.DeleteBuffer(Handle);
  335. }
  336. }
  337. }