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 }