using System; using System.Threading; namespace Ryujinx.Graphics.Gpu.Memory { /// /// A record of when buffer data was copied from multiple buffers to one migration target, /// along with the SyncNumber when the migration will be complete. /// Keeps the source buffers alive for data flushes until the migration is complete. /// All spans cover the full range of the "destination" buffer. /// internal class BufferMigration : IDisposable { /// /// Ranges from source buffers that were copied as part of this migration. /// Ordered by increasing base address. /// public BufferMigrationSpan[] Spans { get; private set; } /// /// The destination range list. This range list must be updated when flushing the source. /// public readonly BufferModifiedRangeList Destination; /// /// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation. /// public readonly ulong SyncNumber; /// /// Number of active users there are traversing this migration's spans. /// private int _refCount; /// /// Create a new buffer migration. /// /// Source spans for the migration /// Destination buffer range list /// Sync number where this migration will be complete public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber) { Spans = spans; Destination = destination; SyncNumber = syncNumber; } /// /// Add a span to the migration. Allocates a new array with the target size, and replaces it. /// /// /// The base address for the span is assumed to be higher than all other spans in the migration, /// to keep the span array ordered. /// public void AddSpanToEnd(BufferMigrationSpan span) { BufferMigrationSpan[] oldSpans = Spans; BufferMigrationSpan[] newSpans = new BufferMigrationSpan[oldSpans.Length + 1]; oldSpans.CopyTo(newSpans, 0); newSpans[oldSpans.Length] = span; Spans = newSpans; } /// /// Performs the given range action, or one from a migration that overlaps and has not synced yet. /// /// The offset to pass to the action /// The size to pass to the action /// The sync number that has been reached /// The action to perform public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferFlushAction rangeAction) { long syncDiff = (long)(syncNumber - SyncNumber); if (syncDiff >= 0) { // The migration has completed. Run the parent action. rangeAction(offset, size, syncNumber); } else { Interlocked.Increment(ref _refCount); ulong prevAddress = offset; ulong endAddress = offset + size; foreach (BufferMigrationSpan span in Spans) { if (!span.Overlaps(offset, size)) { continue; } if (span.Address > prevAddress) { // There's a gap between this span and the last (or the start address). Flush the range using the parent action. rangeAction(prevAddress, span.Address - prevAddress, syncNumber); } span.RangeActionWithMigration(offset, size, syncNumber); prevAddress = span.Address + span.Size; } if (endAddress > prevAddress) { // There's a gap at the end of the range with no migration. Flush the range using the parent action. rangeAction(prevAddress, endAddress - prevAddress, syncNumber); } Interlocked.Decrement(ref _refCount); } } /// /// Dispose the buffer migration. This removes the reference from the destination range list, /// and runs all the dispose buffers for the migration spans. (typically disposes the source buffer) /// public void Dispose() { while (Volatile.Read(ref _refCount) > 0) { // Coming into this method, the sync for the migration will be met, so nothing can increment the ref count. // However, an existing traversal of the spans for data flush could still be in progress. // Spin if this is ever the case, so they don't get disposed before the operation is complete. } Destination.RemoveMigration(this); foreach (BufferMigrationSpan span in Spans) { span.Dispose(); } } } /// /// A record of when buffer data was copied from one buffer to another, for a specific range in a source buffer. /// Keeps the source buffer alive for data flushes until the migration is complete. /// internal readonly struct BufferMigrationSpan : IDisposable { /// /// The offset for the migrated region. /// public readonly ulong Address; /// /// The size for the migrated region. /// public readonly ulong Size; /// /// The action to perform when the migration isn't needed anymore. /// private readonly Action _disposeAction; /// /// The source range action, to be called on overlap with an unreached sync number. /// private readonly BufferFlushAction _sourceRangeAction; /// /// Optional migration for the source data. Can chain together if many migrations happen in a short time. /// If this is null, then _sourceRangeAction will always provide up to date data. /// private readonly BufferMigration _source; /// /// Creates a record for a buffer migration. /// /// The source buffer for this migration /// The action to perform when the migration isn't needed anymore /// The flush action for the source buffer /// Pending migration for the source buffer public BufferMigrationSpan( Buffer buffer, Action disposeAction, BufferFlushAction sourceRangeAction, BufferMigration source) { Address = buffer.Address; Size = buffer.Size; _disposeAction = disposeAction; _sourceRangeAction = sourceRangeAction; _source = source; } /// /// Creates a record for a buffer migration, using the default buffer dispose action. /// /// The source buffer for this migration /// The flush action for the source buffer /// Pending migration for the source buffer public BufferMigrationSpan( Buffer buffer, BufferFlushAction sourceRangeAction, BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { } /// /// Determine if the given range overlaps this migration, and has not been completed yet. /// /// Start offset /// Range size /// True if overlapping and in progress, false otherwise public bool Overlaps(ulong offset, ulong size) { ulong end = offset + size; ulong destEnd = Address + Size; return !(end <= Address || offset >= destEnd); } /// /// Perform the migration source's range action on the range provided, clamped to the bounds of the source buffer. /// /// Start offset /// Range size /// Current sync number public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber) { ulong end = offset + size; end = Math.Min(Address + Size, end); offset = Math.Max(Address, offset); size = end - offset; if (_source != null) { _source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction); } else { _sourceRangeAction(offset, size, syncNumber); } } /// /// Removes this migration span, potentially allowing for the source buffer to be disposed. /// public void Dispose() { _disposeAction(); } } }