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