CircularSpanPool.cs 3.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. using System;
  2. using System.Runtime.CompilerServices;
  3. using System.Runtime.InteropServices;
  4. using System.Threading;
  5. namespace Ryujinx.Graphics.GAL.Multithreading.Model
  6. {
  7. /// <summary>
  8. /// A memory pool for passing through Span<T> resources with one producer and consumer.
  9. /// Data is copied on creation to part of the pool, then that region is reserved until it is disposed by the consumer.
  10. /// Similar to the command queue, this pool assumes that data is created and disposed in the same order.
  11. /// </summary>
  12. class CircularSpanPool
  13. {
  14. private ThreadedRenderer _renderer;
  15. private byte[] _pool;
  16. private int _size;
  17. private int _producerPtr;
  18. private int _producerSkipPosition = -1;
  19. private int _consumerPtr;
  20. public CircularSpanPool(ThreadedRenderer renderer, int size)
  21. {
  22. _renderer = renderer;
  23. _size = size;
  24. _pool = new byte[size];
  25. }
  26. public SpanRef<T> Insert<T>(ReadOnlySpan<T> data) where T : unmanaged
  27. {
  28. int size = data.Length * Unsafe.SizeOf<T>();
  29. // Wrapping aware circular queue.
  30. // If there's no space at the end of the pool for this span, we can't fragment it.
  31. // So just loop back around to the start. Remember the last skipped position.
  32. bool wraparound = _producerPtr + size >= _size;
  33. int index = wraparound ? 0 : _producerPtr;
  34. // _consumerPtr is from another thread, and we're taking it without a lock, so treat this as a snapshot in the past.
  35. // We know that it will always be before or equal to the producer pointer, and it cannot pass it.
  36. // This is enough to reason about if there is space in the queue for the data, even if we're checking against an outdated value.
  37. int consumer = _consumerPtr;
  38. bool beforeConsumer = _producerPtr < consumer;
  39. if (size > _size - 1 || (wraparound && beforeConsumer) || ((index < consumer || wraparound) && index + size >= consumer))
  40. {
  41. // Just get an array in the following situations:
  42. // - The data is too large to fit in the pool.
  43. // - A wraparound would happen but the consumer would be covered by it.
  44. // - The producer would catch up to the consumer as a result.
  45. return new SpanRef<T>(_renderer, data.ToArray());
  46. }
  47. data.CopyTo(MemoryMarshal.Cast<byte, T>(new Span<byte>(_pool).Slice(index, size)));
  48. if (wraparound)
  49. {
  50. _producerSkipPosition = _producerPtr;
  51. }
  52. _producerPtr = index + size;
  53. return new SpanRef<T>(data.Length);
  54. }
  55. public Span<T> Get<T>(int length) where T : unmanaged
  56. {
  57. int size = length * Unsafe.SizeOf<T>();
  58. if (_consumerPtr == Interlocked.CompareExchange(ref _producerSkipPosition, -1, _consumerPtr))
  59. {
  60. _consumerPtr = 0;
  61. }
  62. return MemoryMarshal.Cast<byte, T>(new Span<byte>(_pool).Slice(_consumerPtr, size));
  63. }
  64. public void Dispose<T>(int length) where T : unmanaged
  65. {
  66. int size = length * Unsafe.SizeOf<T>();
  67. _consumerPtr = _consumerPtr + size;
  68. }
  69. }
  70. }