BufferHolder.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385
  1. using Ryujinx.Graphics.GAL;
  2. using SharpMetal.Metal;
  3. using System;
  4. using System.Runtime.InteropServices;
  5. using System.Runtime.Versioning;
  6. using System.Threading;
  7. namespace Ryujinx.Graphics.Metal
  8. {
  9. [SupportedOSPlatform("macos")]
  10. class BufferHolder : IDisposable
  11. {
  12. private CacheByRange<BufferHolder> _cachedConvertedBuffers;
  13. public int Size { get; }
  14. private readonly IntPtr _map;
  15. private readonly MetalRenderer _renderer;
  16. private readonly Pipeline _pipeline;
  17. private readonly MultiFenceHolder _waitable;
  18. private readonly Auto<DisposableBuffer> _buffer;
  19. private readonly ReaderWriterLockSlim _flushLock;
  20. private FenceHolder _flushFence;
  21. private int _flushWaiting;
  22. public BufferHolder(MetalRenderer renderer, Pipeline pipeline, MTLBuffer buffer, int size)
  23. {
  24. _renderer = renderer;
  25. _pipeline = pipeline;
  26. _map = buffer.Contents;
  27. _waitable = new MultiFenceHolder(size);
  28. _buffer = new Auto<DisposableBuffer>(new(buffer), _waitable);
  29. _flushLock = new ReaderWriterLockSlim();
  30. Size = size;
  31. }
  32. public Auto<DisposableBuffer> GetBuffer()
  33. {
  34. return _buffer;
  35. }
  36. public Auto<DisposableBuffer> GetBuffer(bool isWrite)
  37. {
  38. if (isWrite)
  39. {
  40. SignalWrite(0, Size);
  41. }
  42. return _buffer;
  43. }
  44. public Auto<DisposableBuffer> GetBuffer(int offset, int size, bool isWrite)
  45. {
  46. if (isWrite)
  47. {
  48. SignalWrite(offset, size);
  49. }
  50. return _buffer;
  51. }
  52. public void SignalWrite(int offset, int size)
  53. {
  54. if (offset == 0 && size == Size)
  55. {
  56. _cachedConvertedBuffers.Clear();
  57. }
  58. else
  59. {
  60. _cachedConvertedBuffers.ClearRange(offset, size);
  61. }
  62. }
  63. private void ClearFlushFence()
  64. {
  65. // Assumes _flushLock is held as writer.
  66. if (_flushFence != null)
  67. {
  68. if (_flushWaiting == 0)
  69. {
  70. _flushFence.Put();
  71. }
  72. _flushFence = null;
  73. }
  74. }
  75. private void WaitForFlushFence()
  76. {
  77. if (_flushFence == null)
  78. {
  79. return;
  80. }
  81. // If storage has changed, make sure the fence has been reached so that the data is in place.
  82. _flushLock.ExitReadLock();
  83. _flushLock.EnterWriteLock();
  84. if (_flushFence != null)
  85. {
  86. var fence = _flushFence;
  87. Interlocked.Increment(ref _flushWaiting);
  88. // Don't wait in the lock.
  89. _flushLock.ExitWriteLock();
  90. fence.Wait();
  91. _flushLock.EnterWriteLock();
  92. if (Interlocked.Decrement(ref _flushWaiting) == 0)
  93. {
  94. fence.Put();
  95. }
  96. _flushFence = null;
  97. }
  98. // Assumes the _flushLock is held as reader, returns in same state.
  99. _flushLock.ExitWriteLock();
  100. _flushLock.EnterReadLock();
  101. }
  102. public PinnedSpan<byte> GetData(int offset, int size)
  103. {
  104. _flushLock.EnterReadLock();
  105. WaitForFlushFence();
  106. Span<byte> result;
  107. if (_map != IntPtr.Zero)
  108. {
  109. result = GetDataStorage(offset, size);
  110. // Need to be careful here, the buffer can't be unmapped while the data is being used.
  111. _buffer.IncrementReferenceCount();
  112. _flushLock.ExitReadLock();
  113. return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
  114. }
  115. throw new InvalidOperationException("The buffer is not mapped");
  116. }
  117. public unsafe Span<byte> GetDataStorage(int offset, int size)
  118. {
  119. int mappingSize = Math.Min(size, Size - offset);
  120. if (_map != IntPtr.Zero)
  121. {
  122. return new Span<byte>((void*)(_map + offset), mappingSize);
  123. }
  124. throw new InvalidOperationException("The buffer is not mapped.");
  125. }
  126. public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, bool allowCbsWait = true)
  127. {
  128. int dataSize = Math.Min(data.Length, Size - offset);
  129. if (dataSize == 0)
  130. {
  131. return;
  132. }
  133. if (_map != IntPtr.Zero)
  134. {
  135. // If persistently mapped, set the data directly if the buffer is not currently in use.
  136. bool isRented = _buffer.HasRentedCommandBufferDependency(_renderer.CommandBufferPool);
  137. // If the buffer is rented, take a little more time and check if the use overlaps this handle.
  138. bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize, false);
  139. if (!needsFlush)
  140. {
  141. WaitForFences(offset, dataSize);
  142. data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
  143. SignalWrite(offset, dataSize);
  144. return;
  145. }
  146. }
  147. if (cbs != null &&
  148. cbs.Value.Encoders.CurrentEncoderType == EncoderType.Render &&
  149. !(_buffer.HasCommandBufferDependency(cbs.Value) &&
  150. _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
  151. {
  152. // If the buffer hasn't been used on the command buffer yet, try to preload the data.
  153. // This avoids ending and beginning render passes on each buffer data upload.
  154. cbs = _pipeline.GetPreloadCommandBuffer();
  155. }
  156. if (allowCbsWait)
  157. {
  158. _renderer.BufferManager.StagingBuffer.PushData(_renderer.CommandBufferPool, cbs, this, offset, data);
  159. }
  160. else
  161. {
  162. bool rentCbs = cbs == null;
  163. if (rentCbs)
  164. {
  165. cbs = _renderer.CommandBufferPool.Rent();
  166. }
  167. if (!_renderer.BufferManager.StagingBuffer.TryPushData(cbs.Value, this, offset, data))
  168. {
  169. // Need to do a slow upload.
  170. BufferHolder srcHolder = _renderer.BufferManager.Create(dataSize);
  171. srcHolder.SetDataUnchecked(0, data);
  172. var srcBuffer = srcHolder.GetBuffer();
  173. var dstBuffer = this.GetBuffer(true);
  174. Copy(cbs.Value, srcBuffer, dstBuffer, 0, offset, dataSize);
  175. srcHolder.Dispose();
  176. }
  177. if (rentCbs)
  178. {
  179. cbs.Value.Dispose();
  180. }
  181. }
  182. }
  183. public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
  184. {
  185. int dataSize = Math.Min(data.Length, Size - offset);
  186. if (dataSize == 0)
  187. {
  188. return;
  189. }
  190. if (_map != IntPtr.Zero)
  191. {
  192. data[..dataSize].CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
  193. }
  194. }
  195. public void SetDataUnchecked<T>(int offset, ReadOnlySpan<T> data) where T : unmanaged
  196. {
  197. SetDataUnchecked(offset, MemoryMarshal.AsBytes(data));
  198. }
  199. public static void Copy(
  200. CommandBufferScoped cbs,
  201. Auto<DisposableBuffer> src,
  202. Auto<DisposableBuffer> dst,
  203. int srcOffset,
  204. int dstOffset,
  205. int size,
  206. bool registerSrcUsage = true)
  207. {
  208. var srcBuffer = registerSrcUsage ? src.Get(cbs, srcOffset, size).Value : src.GetUnsafe().Value;
  209. var dstbuffer = dst.Get(cbs, dstOffset, size, true).Value;
  210. cbs.Encoders.EnsureBlitEncoder().CopyFromBuffer(
  211. srcBuffer,
  212. (ulong)srcOffset,
  213. dstbuffer,
  214. (ulong)dstOffset,
  215. (ulong)size);
  216. }
  217. public void WaitForFences()
  218. {
  219. _waitable.WaitForFences();
  220. }
  221. public void WaitForFences(int offset, int size)
  222. {
  223. _waitable.WaitForFences(offset, size);
  224. }
  225. private bool BoundToRange(int offset, ref int size)
  226. {
  227. if (offset >= Size)
  228. {
  229. return false;
  230. }
  231. size = Math.Min(Size - offset, size);
  232. return true;
  233. }
  234. public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
  235. {
  236. if (!BoundToRange(offset, ref size))
  237. {
  238. return null;
  239. }
  240. var key = new I8ToI16CacheKey(_renderer);
  241. if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
  242. {
  243. holder = _renderer.BufferManager.Create((size * 2 + 3) & ~3);
  244. _renderer.HelperShader.ConvertI8ToI16(cbs, this, holder, offset, size);
  245. key.SetBuffer(holder.GetBuffer());
  246. _cachedConvertedBuffers.Add(offset, size, key, holder);
  247. }
  248. return holder.GetBuffer();
  249. }
  250. public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
  251. {
  252. if (!BoundToRange(offset, ref size))
  253. {
  254. return null;
  255. }
  256. var key = new TopologyConversionCacheKey(_renderer, pattern, indexSize);
  257. if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
  258. {
  259. // The destination index size is always I32.
  260. int indexCount = size / indexSize;
  261. int convertedCount = pattern.GetConvertedCount(indexCount);
  262. holder = _renderer.BufferManager.Create(convertedCount * 4);
  263. _renderer.HelperShader.ConvertIndexBuffer(cbs, this, holder, pattern, indexSize, offset, indexCount);
  264. key.SetBuffer(holder.GetBuffer());
  265. _cachedConvertedBuffers.Add(offset, size, key, holder);
  266. }
  267. return holder.GetBuffer();
  268. }
  269. public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder)
  270. {
  271. return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder);
  272. }
  273. public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder)
  274. {
  275. _cachedConvertedBuffers.Add(offset, size, key, holder);
  276. }
  277. public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency)
  278. {
  279. _cachedConvertedBuffers.AddDependency(offset, size, key, dependency);
  280. }
  281. public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key)
  282. {
  283. _cachedConvertedBuffers.Remove(offset, size, key);
  284. }
  285. public void Dispose()
  286. {
  287. _pipeline.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
  288. _buffer.Dispose();
  289. _cachedConvertedBuffers.Dispose();
  290. _flushLock.EnterWriteLock();
  291. ClearFlushFence();
  292. _flushLock.ExitWriteLock();
  293. }
  294. }
  295. }