BufferHolder.cs 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788
  1. using Ryujinx.Common.Logging;
  2. using Ryujinx.Graphics.GAL;
  3. using Silk.NET.Vulkan;
  4. using System;
  5. using System.Collections.Generic;
  6. using System.Runtime.CompilerServices;
  7. using System.Threading;
  8. using VkBuffer = Silk.NET.Vulkan.Buffer;
  9. using VkFormat = Silk.NET.Vulkan.Format;
  10. namespace Ryujinx.Graphics.Vulkan
  11. {
  12. class BufferHolder : IDisposable
  13. {
  14. private const int MaxUpdateBufferSize = 0x10000;
  15. private const int SetCountThreshold = 100;
  16. private const int WriteCountThreshold = 50;
  17. private const int FlushCountThreshold = 5;
  18. public const int DeviceLocalSizeThreshold = 256 * 1024; // 256kb
  19. public const AccessFlags DefaultAccessFlags =
  20. AccessFlags.IndirectCommandReadBit |
  21. AccessFlags.ShaderReadBit |
  22. AccessFlags.ShaderWriteBit |
  23. AccessFlags.TransferReadBit |
  24. AccessFlags.TransferWriteBit |
  25. AccessFlags.UniformReadBit;
  26. private readonly VulkanRenderer _gd;
  27. private readonly Device _device;
  28. private MemoryAllocation _allocation;
  29. private Auto<DisposableBuffer> _buffer;
  30. private Auto<MemoryAllocation> _allocationAuto;
  31. private ulong _bufferHandle;
  32. private CacheByRange<BufferHolder> _cachedConvertedBuffers;
  33. public int Size { get; }
  34. private IntPtr _map;
  35. private MultiFenceHolder _waitable;
  36. private bool _lastAccessIsWrite;
  37. private BufferAllocationType _baseType;
  38. private BufferAllocationType _currentType;
  39. private bool _swapQueued;
  40. public BufferAllocationType DesiredType { get; private set; }
  41. private int _setCount;
  42. private int _writeCount;
  43. private int _flushCount;
  44. private int _flushTemp;
  45. private ReaderWriterLock _flushLock;
  46. private FenceHolder _flushFence;
  47. private int _flushWaiting;
  48. private List<Action> _swapActions;
  49. public BufferHolder(VulkanRenderer gd, Device device, VkBuffer buffer, MemoryAllocation allocation, int size, BufferAllocationType type, BufferAllocationType currentType)
  50. {
  51. _gd = gd;
  52. _device = device;
  53. _allocation = allocation;
  54. _allocationAuto = new Auto<MemoryAllocation>(allocation);
  55. _waitable = new MultiFenceHolder(size);
  56. _buffer = new Auto<DisposableBuffer>(new DisposableBuffer(gd.Api, device, buffer), _waitable, _allocationAuto);
  57. _bufferHandle = buffer.Handle;
  58. Size = size;
  59. _map = allocation.HostPointer;
  60. _baseType = type;
  61. _currentType = currentType;
  62. DesiredType = currentType;
  63. _flushLock = new ReaderWriterLock();
  64. }
  65. public bool TryBackingSwap(ref CommandBufferScoped? cbs)
  66. {
  67. if (_swapQueued && DesiredType != _currentType)
  68. {
  69. // Only swap if the buffer is not used in any queued command buffer.
  70. bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
  71. if (!isRented && _gd.CommandBufferPool.OwnedByCurrentThread && !_flushLock.IsReaderLockHeld)
  72. {
  73. var currentAllocation = _allocationAuto;
  74. var currentBuffer = _buffer;
  75. IntPtr currentMap = _map;
  76. (VkBuffer buffer, MemoryAllocation allocation, BufferAllocationType resultType) = _gd.BufferManager.CreateBacking(_gd, Size, DesiredType, false, _currentType);
  77. if (buffer.Handle != 0)
  78. {
  79. _flushLock.AcquireWriterLock(Timeout.Infinite);
  80. ClearFlushFence();
  81. _waitable = new MultiFenceHolder(Size);
  82. _allocation = allocation;
  83. _allocationAuto = new Auto<MemoryAllocation>(allocation);
  84. _buffer = new Auto<DisposableBuffer>(new DisposableBuffer(_gd.Api, _device, buffer), _waitable, _allocationAuto);
  85. _bufferHandle = buffer.Handle;
  86. _map = allocation.HostPointer;
  87. if (_map != IntPtr.Zero && currentMap != IntPtr.Zero)
  88. {
  89. // Copy data directly. Readbacks don't have to wait if this is done.
  90. unsafe
  91. {
  92. new Span<byte>((void*)currentMap, Size).CopyTo(new Span<byte>((void*)_map, Size));
  93. }
  94. }
  95. else
  96. {
  97. if (cbs == null)
  98. {
  99. cbs = _gd.CommandBufferPool.Rent();
  100. }
  101. CommandBufferScoped cbsV = cbs.Value;
  102. Copy(_gd, cbsV, currentBuffer, _buffer, 0, 0, Size);
  103. // Need to wait for the data to reach the new buffer before data can be flushed.
  104. _flushFence = _gd.CommandBufferPool.GetFence(cbsV.CommandBufferIndex);
  105. _flushFence.Get();
  106. }
  107. Logger.Debug?.PrintMsg(LogClass.Gpu, $"Converted {Size} buffer {_currentType} to {resultType}");
  108. _currentType = resultType;
  109. if (_swapActions != null)
  110. {
  111. foreach (var action in _swapActions)
  112. {
  113. action();
  114. }
  115. _swapActions.Clear();
  116. }
  117. currentBuffer.Dispose();
  118. currentAllocation.Dispose();
  119. _gd.PipelineInternal.SwapBuffer(currentBuffer, _buffer);
  120. _flushLock.ReleaseWriterLock();
  121. }
  122. _swapQueued = false;
  123. return true;
  124. }
  125. else
  126. {
  127. return false;
  128. }
  129. }
  130. else
  131. {
  132. _swapQueued = false;
  133. return true;
  134. }
  135. }
  136. private void ConsiderBackingSwap()
  137. {
  138. if (_baseType == BufferAllocationType.Auto)
  139. {
  140. if (_writeCount >= WriteCountThreshold || _setCount >= SetCountThreshold || _flushCount >= FlushCountThreshold)
  141. {
  142. if (_flushCount > 0 || _flushTemp-- > 0)
  143. {
  144. // Buffers that flush should ideally be mapped in host address space for easy copies.
  145. // If the buffer is large it will do better on GPU memory, as there will be more writes than data flushes (typically individual pages).
  146. // If it is small, then it's likely most of the buffer will be flushed so we want it on host memory, as access is cached.
  147. DesiredType = Size > DeviceLocalSizeThreshold ? BufferAllocationType.DeviceLocalMapped : BufferAllocationType.HostMapped;
  148. // It's harder for a buffer that is flushed to revert to another type of mapping.
  149. if (_flushCount > 0)
  150. {
  151. _flushTemp = 1000;
  152. }
  153. }
  154. else if (_writeCount >= WriteCountThreshold)
  155. {
  156. // Buffers that are written often should ideally be in the device local heap. (Storage buffers)
  157. DesiredType = BufferAllocationType.DeviceLocal;
  158. }
  159. else if (_setCount > SetCountThreshold)
  160. {
  161. // Buffers that have their data set often should ideally be host mapped. (Constant buffers)
  162. DesiredType = BufferAllocationType.HostMapped;
  163. }
  164. _flushCount = 0;
  165. _writeCount = 0;
  166. _setCount = 0;
  167. }
  168. if (!_swapQueued && DesiredType != _currentType)
  169. {
  170. _swapQueued = true;
  171. _gd.PipelineInternal.AddBackingSwap(this);
  172. }
  173. }
  174. }
  175. public unsafe Auto<DisposableBufferView> CreateView(VkFormat format, int offset, int size, Action invalidateView)
  176. {
  177. var bufferViewCreateInfo = new BufferViewCreateInfo()
  178. {
  179. SType = StructureType.BufferViewCreateInfo,
  180. Buffer = new VkBuffer(_bufferHandle),
  181. Format = format,
  182. Offset = (uint)offset,
  183. Range = (uint)size
  184. };
  185. _gd.Api.CreateBufferView(_device, bufferViewCreateInfo, null, out var bufferView).ThrowOnError();
  186. (_swapActions ??= new List<Action>()).Add(invalidateView);
  187. return new Auto<DisposableBufferView>(new DisposableBufferView(_gd.Api, _device, bufferView), _waitable, _buffer);
  188. }
  189. public void InheritMetrics(BufferHolder other)
  190. {
  191. _setCount = other._setCount;
  192. _writeCount = other._writeCount;
  193. _flushCount = other._flushCount;
  194. _flushTemp = other._flushTemp;
  195. }
  196. public unsafe void InsertBarrier(CommandBuffer commandBuffer, bool isWrite)
  197. {
  198. // If the last access is write, we always need a barrier to be sure we will read or modify
  199. // the correct data.
  200. // If the last access is read, and current one is a write, we need to wait until the
  201. // read finishes to avoid overwriting data still in use.
  202. // Otherwise, if the last access is a read and the current one too, we don't need barriers.
  203. bool needsBarrier = isWrite || _lastAccessIsWrite;
  204. _lastAccessIsWrite = isWrite;
  205. if (needsBarrier)
  206. {
  207. MemoryBarrier memoryBarrier = new MemoryBarrier()
  208. {
  209. SType = StructureType.MemoryBarrier,
  210. SrcAccessMask = DefaultAccessFlags,
  211. DstAccessMask = DefaultAccessFlags
  212. };
  213. _gd.Api.CmdPipelineBarrier(
  214. commandBuffer,
  215. PipelineStageFlags.AllCommandsBit,
  216. PipelineStageFlags.AllCommandsBit,
  217. DependencyFlags.DeviceGroupBit,
  218. 1,
  219. memoryBarrier,
  220. 0,
  221. null,
  222. 0,
  223. null);
  224. }
  225. }
  226. public Auto<DisposableBuffer> GetBuffer()
  227. {
  228. return _buffer;
  229. }
  230. public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, bool isWrite = false, bool isSSBO = false)
  231. {
  232. if (isWrite)
  233. {
  234. _writeCount++;
  235. SignalWrite(0, Size);
  236. }
  237. else if (isSSBO)
  238. {
  239. // Always consider SSBO access for swapping to device local memory.
  240. _writeCount++;
  241. ConsiderBackingSwap();
  242. }
  243. return _buffer;
  244. }
  245. public Auto<DisposableBuffer> GetBuffer(CommandBuffer commandBuffer, int offset, int size, bool isWrite = false)
  246. {
  247. if (isWrite)
  248. {
  249. _writeCount++;
  250. SignalWrite(offset, size);
  251. }
  252. return _buffer;
  253. }
  254. public void SignalWrite(int offset, int size)
  255. {
  256. ConsiderBackingSwap();
  257. if (offset == 0 && size == Size)
  258. {
  259. _cachedConvertedBuffers.Clear();
  260. }
  261. else
  262. {
  263. _cachedConvertedBuffers.ClearRange(offset, size);
  264. }
  265. }
  266. public BufferHandle GetHandle()
  267. {
  268. var handle = _bufferHandle;
  269. return Unsafe.As<ulong, BufferHandle>(ref handle);
  270. }
  271. public unsafe IntPtr Map(int offset, int mappingSize)
  272. {
  273. return _map;
  274. }
  275. private void ClearFlushFence()
  276. {
  277. // Asusmes _flushLock is held as writer.
  278. if (_flushFence != null)
  279. {
  280. if (_flushWaiting == 0)
  281. {
  282. _flushFence.Put();
  283. }
  284. _flushFence = null;
  285. }
  286. }
  287. private void WaitForFlushFence()
  288. {
  289. // Assumes the _flushLock is held as reader, returns in same state.
  290. if (_flushFence != null)
  291. {
  292. // If storage has changed, make sure the fence has been reached so that the data is in place.
  293. var cookie = _flushLock.UpgradeToWriterLock(Timeout.Infinite);
  294. if (_flushFence != null)
  295. {
  296. var fence = _flushFence;
  297. Interlocked.Increment(ref _flushWaiting);
  298. // Don't wait in the lock.
  299. var restoreCookie = _flushLock.ReleaseLock();
  300. fence.Wait();
  301. _flushLock.RestoreLock(ref restoreCookie);
  302. if (Interlocked.Decrement(ref _flushWaiting) == 0)
  303. {
  304. fence.Put();
  305. }
  306. _flushFence = null;
  307. }
  308. _flushLock.DowngradeFromWriterLock(ref cookie);
  309. }
  310. }
  311. public unsafe PinnedSpan<byte> GetData(int offset, int size)
  312. {
  313. _flushLock.AcquireReaderLock(Timeout.Infinite);
  314. WaitForFlushFence();
  315. _flushCount++;
  316. Span<byte> result;
  317. if (_map != IntPtr.Zero)
  318. {
  319. result = GetDataStorage(offset, size);
  320. // Need to be careful here, the buffer can't be unmapped while the data is being used.
  321. _buffer.IncrementReferenceCount();
  322. _flushLock.ReleaseReaderLock();
  323. return PinnedSpan<byte>.UnsafeFromSpan(result, _buffer.DecrementReferenceCount);
  324. }
  325. else
  326. {
  327. BackgroundResource resource = _gd.BackgroundResources.Get();
  328. if (_gd.CommandBufferPool.OwnedByCurrentThread)
  329. {
  330. _gd.FlushAllCommands();
  331. result = resource.GetFlushBuffer().GetBufferData(_gd.CommandBufferPool, this, offset, size);
  332. }
  333. else
  334. {
  335. result = resource.GetFlushBuffer().GetBufferData(resource.GetPool(), this, offset, size);
  336. }
  337. _flushLock.ReleaseReaderLock();
  338. // Flush buffer is pinned until the next GetBufferData on the thread, which is fine for current uses.
  339. return PinnedSpan<byte>.UnsafeFromSpan(result);
  340. }
  341. }
  342. public unsafe Span<byte> GetDataStorage(int offset, int size)
  343. {
  344. int mappingSize = Math.Min(size, Size - offset);
  345. if (_map != IntPtr.Zero)
  346. {
  347. return new Span<byte>((void*)(_map + offset), mappingSize);
  348. }
  349. throw new InvalidOperationException("The buffer is not host mapped.");
  350. }
  351. public unsafe void SetData(int offset, ReadOnlySpan<byte> data, CommandBufferScoped? cbs = null, Action endRenderPass = null)
  352. {
  353. int dataSize = Math.Min(data.Length, Size - offset);
  354. if (dataSize == 0)
  355. {
  356. return;
  357. }
  358. _setCount++;
  359. if (_map != IntPtr.Zero)
  360. {
  361. // If persistently mapped, set the data directly if the buffer is not currently in use.
  362. bool isRented = _buffer.HasRentedCommandBufferDependency(_gd.CommandBufferPool);
  363. // If the buffer is rented, take a little more time and check if the use overlaps this handle.
  364. bool needsFlush = isRented && _waitable.IsBufferRangeInUse(offset, dataSize);
  365. if (!needsFlush)
  366. {
  367. WaitForFences(offset, dataSize);
  368. data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
  369. SignalWrite(offset, dataSize);
  370. return;
  371. }
  372. }
  373. if (cbs != null &&
  374. _gd.PipelineInternal.RenderPassActive &&
  375. !(_buffer.HasCommandBufferDependency(cbs.Value) &&
  376. _waitable.IsBufferRangeInUse(cbs.Value.CommandBufferIndex, offset, dataSize)))
  377. {
  378. // If the buffer hasn't been used on the command buffer yet, try to preload the data.
  379. // This avoids ending and beginning render passes on each buffer data upload.
  380. cbs = _gd.PipelineInternal.GetPreloadCommandBuffer();
  381. endRenderPass = null;
  382. }
  383. if (cbs == null ||
  384. !VulkanConfiguration.UseFastBufferUpdates ||
  385. data.Length > MaxUpdateBufferSize ||
  386. !TryPushData(cbs.Value, endRenderPass, offset, data))
  387. {
  388. _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, cbs, endRenderPass, this, offset, data);
  389. }
  390. }
  391. public unsafe void SetDataUnchecked(int offset, ReadOnlySpan<byte> data)
  392. {
  393. int dataSize = Math.Min(data.Length, Size - offset);
  394. if (dataSize == 0)
  395. {
  396. return;
  397. }
  398. if (_map != IntPtr.Zero)
  399. {
  400. data.Slice(0, dataSize).CopyTo(new Span<byte>((void*)(_map + offset), dataSize));
  401. }
  402. else
  403. {
  404. _gd.BufferManager.StagingBuffer.PushData(_gd.CommandBufferPool, null, null, this, offset, data);
  405. }
  406. }
  407. public void SetDataInline(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> data)
  408. {
  409. if (!TryPushData(cbs, endRenderPass, dstOffset, data))
  410. {
  411. throw new ArgumentException($"Invalid offset 0x{dstOffset:X} or data size 0x{data.Length:X}.");
  412. }
  413. }
  414. private unsafe bool TryPushData(CommandBufferScoped cbs, Action endRenderPass, int dstOffset, ReadOnlySpan<byte> data)
  415. {
  416. if ((dstOffset & 3) != 0 || (data.Length & 3) != 0)
  417. {
  418. return false;
  419. }
  420. endRenderPass?.Invoke();
  421. var dstBuffer = GetBuffer(cbs.CommandBuffer, dstOffset, data.Length, true).Get(cbs, dstOffset, data.Length).Value;
  422. _writeCount--;
  423. InsertBufferBarrier(
  424. _gd,
  425. cbs.CommandBuffer,
  426. dstBuffer,
  427. DefaultAccessFlags,
  428. AccessFlags.TransferWriteBit,
  429. PipelineStageFlags.AllCommandsBit,
  430. PipelineStageFlags.TransferBit,
  431. dstOffset,
  432. data.Length);
  433. fixed (byte* pData = data)
  434. {
  435. for (ulong offset = 0; offset < (ulong)data.Length;)
  436. {
  437. ulong size = Math.Min(MaxUpdateBufferSize, (ulong)data.Length - offset);
  438. _gd.Api.CmdUpdateBuffer(cbs.CommandBuffer, dstBuffer, (ulong)dstOffset + offset, size, pData + offset);
  439. offset += size;
  440. }
  441. }
  442. InsertBufferBarrier(
  443. _gd,
  444. cbs.CommandBuffer,
  445. dstBuffer,
  446. AccessFlags.TransferWriteBit,
  447. DefaultAccessFlags,
  448. PipelineStageFlags.TransferBit,
  449. PipelineStageFlags.AllCommandsBit,
  450. dstOffset,
  451. data.Length);
  452. return true;
  453. }
  454. public static unsafe void Copy(
  455. VulkanRenderer gd,
  456. CommandBufferScoped cbs,
  457. Auto<DisposableBuffer> src,
  458. Auto<DisposableBuffer> dst,
  459. int srcOffset,
  460. int dstOffset,
  461. int size)
  462. {
  463. var srcBuffer = src.Get(cbs, srcOffset, size).Value;
  464. var dstBuffer = dst.Get(cbs, dstOffset, size).Value;
  465. InsertBufferBarrier(
  466. gd,
  467. cbs.CommandBuffer,
  468. dstBuffer,
  469. DefaultAccessFlags,
  470. AccessFlags.TransferWriteBit,
  471. PipelineStageFlags.AllCommandsBit,
  472. PipelineStageFlags.TransferBit,
  473. dstOffset,
  474. size);
  475. var region = new BufferCopy((ulong)srcOffset, (ulong)dstOffset, (ulong)size);
  476. gd.Api.CmdCopyBuffer(cbs.CommandBuffer, srcBuffer, dstBuffer, 1, &region);
  477. InsertBufferBarrier(
  478. gd,
  479. cbs.CommandBuffer,
  480. dstBuffer,
  481. AccessFlags.TransferWriteBit,
  482. DefaultAccessFlags,
  483. PipelineStageFlags.TransferBit,
  484. PipelineStageFlags.AllCommandsBit,
  485. dstOffset,
  486. size);
  487. }
  488. public static unsafe void InsertBufferBarrier(
  489. VulkanRenderer gd,
  490. CommandBuffer commandBuffer,
  491. VkBuffer buffer,
  492. AccessFlags srcAccessMask,
  493. AccessFlags dstAccessMask,
  494. PipelineStageFlags srcStageMask,
  495. PipelineStageFlags dstStageMask,
  496. int offset,
  497. int size)
  498. {
  499. BufferMemoryBarrier memoryBarrier = new BufferMemoryBarrier()
  500. {
  501. SType = StructureType.BufferMemoryBarrier,
  502. SrcAccessMask = srcAccessMask,
  503. DstAccessMask = dstAccessMask,
  504. SrcQueueFamilyIndex = Vk.QueueFamilyIgnored,
  505. DstQueueFamilyIndex = Vk.QueueFamilyIgnored,
  506. Buffer = buffer,
  507. Offset = (ulong)offset,
  508. Size = (ulong)size
  509. };
  510. gd.Api.CmdPipelineBarrier(
  511. commandBuffer,
  512. srcStageMask,
  513. dstStageMask,
  514. 0,
  515. 0,
  516. null,
  517. 1,
  518. memoryBarrier,
  519. 0,
  520. null);
  521. }
  522. public void WaitForFences()
  523. {
  524. _waitable.WaitForFences(_gd.Api, _device);
  525. }
  526. public void WaitForFences(int offset, int size)
  527. {
  528. _waitable.WaitForFences(_gd.Api, _device, offset, size);
  529. }
  530. private bool BoundToRange(int offset, ref int size)
  531. {
  532. if (offset >= Size)
  533. {
  534. return false;
  535. }
  536. size = Math.Min(Size - offset, size);
  537. return true;
  538. }
  539. public Auto<DisposableBuffer> GetBufferI8ToI16(CommandBufferScoped cbs, int offset, int size)
  540. {
  541. if (!BoundToRange(offset, ref size))
  542. {
  543. return null;
  544. }
  545. var key = new I8ToI16CacheKey(_gd);
  546. if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
  547. {
  548. holder = _gd.BufferManager.Create(_gd, (size * 2 + 3) & ~3);
  549. _gd.PipelineInternal.EndRenderPass();
  550. _gd.HelperShader.ConvertI8ToI16(_gd, cbs, this, holder, offset, size);
  551. key.SetBuffer(holder.GetBuffer());
  552. _cachedConvertedBuffers.Add(offset, size, key, holder);
  553. }
  554. return holder.GetBuffer();
  555. }
  556. public Auto<DisposableBuffer> GetAlignedVertexBuffer(CommandBufferScoped cbs, int offset, int size, int stride, int alignment)
  557. {
  558. if (!BoundToRange(offset, ref size))
  559. {
  560. return null;
  561. }
  562. var key = new AlignedVertexBufferCacheKey(_gd, stride, alignment);
  563. if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
  564. {
  565. int alignedStride = (stride + (alignment - 1)) & -alignment;
  566. holder = _gd.BufferManager.Create(_gd, (size / stride) * alignedStride);
  567. _gd.PipelineInternal.EndRenderPass();
  568. _gd.HelperShader.ChangeStride(_gd, cbs, this, holder, offset, size, stride, alignedStride);
  569. key.SetBuffer(holder.GetBuffer());
  570. _cachedConvertedBuffers.Add(offset, size, key, holder);
  571. }
  572. return holder.GetBuffer();
  573. }
  574. public Auto<DisposableBuffer> GetBufferTopologyConversion(CommandBufferScoped cbs, int offset, int size, IndexBufferPattern pattern, int indexSize)
  575. {
  576. if (!BoundToRange(offset, ref size))
  577. {
  578. return null;
  579. }
  580. var key = new TopologyConversionCacheKey(_gd, pattern, indexSize);
  581. if (!_cachedConvertedBuffers.TryGetValue(offset, size, key, out var holder))
  582. {
  583. // The destination index size is always I32.
  584. int indexCount = size / indexSize;
  585. int convertedCount = pattern.GetConvertedCount(indexCount);
  586. holder = _gd.BufferManager.Create(_gd, convertedCount * 4);
  587. _gd.PipelineInternal.EndRenderPass();
  588. _gd.HelperShader.ConvertIndexBuffer(_gd, cbs, this, holder, pattern, indexSize, offset, indexCount);
  589. key.SetBuffer(holder.GetBuffer());
  590. _cachedConvertedBuffers.Add(offset, size, key, holder);
  591. }
  592. return holder.GetBuffer();
  593. }
  594. public bool TryGetCachedConvertedBuffer(int offset, int size, ICacheKey key, out BufferHolder holder)
  595. {
  596. return _cachedConvertedBuffers.TryGetValue(offset, size, key, out holder);
  597. }
  598. public void AddCachedConvertedBuffer(int offset, int size, ICacheKey key, BufferHolder holder)
  599. {
  600. _cachedConvertedBuffers.Add(offset, size, key, holder);
  601. }
  602. public void AddCachedConvertedBufferDependency(int offset, int size, ICacheKey key, Dependency dependency)
  603. {
  604. _cachedConvertedBuffers.AddDependency(offset, size, key, dependency);
  605. }
  606. public void RemoveCachedConvertedBuffer(int offset, int size, ICacheKey key)
  607. {
  608. _cachedConvertedBuffers.Remove(offset, size, key);
  609. }
  610. public void Dispose()
  611. {
  612. _swapQueued = false;
  613. _gd.PipelineInternal?.FlushCommandsIfWeightExceeding(_buffer, (ulong)Size);
  614. _buffer.Dispose();
  615. _allocationAuto.Dispose();
  616. _cachedConvertedBuffers.Dispose();
  617. _flushLock.AcquireWriterLock(Timeout.Infinite);
  618. ClearFlushFence();
  619. _flushLock.ReleaseWriterLock();
  620. }
  621. }
  622. }