/ src / Ryujinx.Graphics.GAL / Multithreading / Model / CircularSpanPool.cs
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  }