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();
}
}
}