/ src / Ryujinx.Graphics.Gpu / Memory / BufferMigration.cs
BufferMigration.cs
  1  using System;
  2  using System.Threading;
  3  
  4  namespace Ryujinx.Graphics.Gpu.Memory
  5  {
  6      /// <summary>
  7      /// A record of when buffer data was copied from multiple buffers to one migration target,
  8      /// along with the SyncNumber when the migration will be complete.
  9      /// Keeps the source buffers alive for data flushes until the migration is complete.
 10      /// All spans cover the full range of the "destination" buffer.
 11      /// </summary>
 12      internal class BufferMigration : IDisposable
 13      {
 14          /// <summary>
 15          /// Ranges from source buffers that were copied as part of this migration.
 16          /// Ordered by increasing base address.
 17          /// </summary>
 18          public BufferMigrationSpan[] Spans { get; private set; }
 19  
 20          /// <summary>
 21          /// The destination range list. This range list must be updated when flushing the source.
 22          /// </summary>
 23          public readonly BufferModifiedRangeList Destination;
 24  
 25          /// <summary>
 26          /// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation.
 27          /// </summary>
 28          public readonly ulong SyncNumber;
 29  
 30          /// <summary>
 31          /// Number of active users there are traversing this migration's spans.
 32          /// </summary>
 33          private int _refCount;
 34  
 35          /// <summary>
 36          /// Create a new buffer migration.
 37          /// </summary>
 38          /// <param name="spans">Source spans for the migration</param>
 39          /// <param name="destination">Destination buffer range list</param>
 40          /// <param name="syncNumber">Sync number where this migration will be complete</param>
 41          public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
 42          {
 43              Spans = spans;
 44              Destination = destination;
 45              SyncNumber = syncNumber;
 46          }
 47  
 48          /// <summary>
 49          /// Add a span to the migration. Allocates a new array with the target size, and replaces it.
 50          /// </summary>
 51          /// <remarks>
 52          /// The base address for the span is assumed to be higher than all other spans in the migration,
 53          /// to keep the span array ordered.
 54          /// </remarks>
 55          public void AddSpanToEnd(BufferMigrationSpan span)
 56          {
 57              BufferMigrationSpan[] oldSpans = Spans;
 58  
 59              BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1];
 60  
 61              oldSpans.CopyTo(newSpans, 0);
 62  
 63              newSpans[oldSpans.Length] = span;
 64  
 65              Spans = newSpans;
 66          }
 67  
 68          /// <summary>
 69          /// Performs the given range action, or one from a migration that overlaps and has not synced yet.
 70          /// </summary>
 71          /// <param name="offset">The offset to pass to the action</param>
 72          /// <param name="size">The size to pass to the action</param>
 73          /// <param name="syncNumber">The sync number that has been reached</param>
 74          /// <param name="rangeAction">The action to perform</param>
 75          public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction)
 76          {
 77              long syncDiff = (long)(syncNumber - SyncNumber);
 78  
 79              if (syncDiff >= 0)
 80              {
 81                  // The migration has completed. Run the parent action.
 82                  rangeAction(offset, size, syncNumber);
 83              }
 84              else
 85              {
 86                  Interlocked.Increment(ref _refCount);
 87  
 88                  ulong prevAddress = offset;
 89                  ulong endAddress = offset + size;
 90  
 91                  foreach (BufferMigrationSpan span in Spans)
 92                  {
 93                      if (!span.Overlaps(offset, size))
 94                      {
 95                          continue;
 96                      }
 97  
 98                      if (span.Address > prevAddress)
 99                      {
100                          // There's a gap between this span and the last (or the start address). Flush the range using the parent action.
101  
102                          rangeAction(prevAddress, span.Address - prevAddress, syncNumber);
103                      }
104  
105                      span.RangeActionWithMigration(offset, size, syncNumber);
106  
107                      prevAddress = span.Address + span.Size;
108                  }
109  
110                  if (endAddress > prevAddress)
111                  {
112                      // There's a gap at the end of the range with no migration. Flush the range using the parent action.
113                      rangeAction(prevAddress, endAddress - prevAddress, syncNumber);
114                  }
115  
116                  Interlocked.Decrement(ref _refCount);
117              }
118          }
119  
120          /// <summary>
121          /// Dispose the buffer migration. This removes the reference from the destination range list,
122          /// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer)
123          /// </summary>
124          public void Dispose()
125          {
126              while (Volatile.Read(ref _refCount) > 0)
127              {
128                  // Coming into this method, the sync for the migration will be met, so nothing can increment the ref count.
129                  // However, an existing traversal of the spans for data flush could still be in progress.
130                  // Spin if this is ever the case, so they don't get disposed before the operation is complete.
131              }
132  
133              Destination.RemoveMigration(this);
134  
135              foreach (BufferMigrationSpan span in Spans)
136              {
137                  span.Dispose();
138              }
139          }
140      }
141  
142      /// <summary>
143      /// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer.
144      /// Keeps the source buffer alive for data flushes until the migration is complete.
145      /// </summary>
146      internal readonly struct BufferMigrationSpan : IDisposable
147      {
148          /// <summary>
149          /// The offset for the migrated region.
150          /// </summary>
151          public readonly ulong Address;
152  
153          /// <summary>
154          /// The size for the migrated region.
155          /// </summary>
156          public readonly ulong Size;
157  
158          /// <summary>
159          /// The action to perform when the migration isn't needed anymore.
160          /// </summary>
161          private readonly Action _disposeAction;
162  
163          /// <summary>
164          /// The source range action, to be called on overlap with an unreached sync number.
165          /// </summary>
166          private readonly BufferFlushAction _sourceRangeAction;
167  
168          /// <summary>
169          /// Optional migration for the source data. Can chain together if many migrations happen in a short time.
170          /// If this is null, then _sourceRangeAction will always provide up to date data.
171          /// </summary>
172          private readonly BufferMigration _source;
173  
174          /// <summary>
175          /// Creates a record for a buffer migration.
176          /// </summary>
177          /// <param name="buffer">The source buffer for this migration</param>
178          /// <param name="disposeAction">The action to perform when the migration isn't needed anymore</param>
179          /// <param name="sourceRangeAction">The flush action for the source buffer</param>
180          /// <param name="source">Pending migration for the source buffer</param>
181          public BufferMigrationSpan(
182              Buffer buffer,
183              Action disposeAction,
184              BufferFlushAction sourceRangeAction,
185              BufferMigration source)
186          {
187              Address = buffer.Address;
188              Size = buffer.Size;
189              _disposeAction = disposeAction;
190              _sourceRangeAction = sourceRangeAction;
191              _source = source;
192          }
193  
194          /// <summary>
195          /// Creates a record for a buffer migration, using the default buffer dispose action.
196          /// </summary>
197          /// <param name="buffer">The source buffer for this migration</param>
198          /// <param name="sourceRangeAction">The flush action for the source buffer</param>
199          /// <param name="source">Pending migration for the source buffer</param>
200          public BufferMigrationSpan(
201              Buffer buffer,
202              BufferFlushAction sourceRangeAction,
203              BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
204  
205          /// <summary>
206          /// Determine if the given range overlaps this migration, and has not been completed yet.
207          /// </summary>
208          /// <param name="offset">Start offset</param>
209          /// <param name="size">Range size</param>
210          /// <returns>True if overlapping and in progress, false otherwise</returns>
211          public bool Overlaps(ulong offset, ulong size)
212          {
213              ulong end = offset + size;
214              ulong destEnd = Address + Size;
215  
216              return !(end <= Address || offset >= destEnd);
217          }
218  
219          /// <summary>
220          /// Perform the migration source's range action on the range provided, clamped to the bounds of the source buffer.
221          /// </summary>
222          /// <param name="offset">Start offset</param>
223          /// <param name="size">Range size</param>
224          /// <param name="syncNumber">Current sync number</param>
225          public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber)
226          {
227              ulong end = offset + size;
228              end = Math.Min(Address + Size, end);
229              offset = Math.Max(Address, offset);
230  
231              size = end - offset;
232  
233              if (_source != null)
234              {
235                  _source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction);
236              }
237              else
238              {
239                  _sourceRangeAction(offset, size, syncNumber);
240              }
241          }
242  
243          /// <summary>
244          /// Removes this migration span, potentially allowing for the source buffer to be disposed.
245          /// </summary>
246          public void Dispose()
247          {
248              _disposeAction();
249          }
250      }
251  }