aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs')
-rw-r--r--src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs234
1 files changed, 180 insertions, 54 deletions
diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
index 0a526803..ce998531 100644
--- a/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
+++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferMigration.cs
@@ -1,97 +1,219 @@
using System;
+using System.Threading;
namespace Ryujinx.Graphics.Gpu.Memory
{
/// <summary>
- /// A record of when buffer data was copied from one buffer to another, along with the SyncNumber when the migration will be complete.
- /// Keeps the source buffer alive for data flushes until the migration is complete.
+ /// 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.
/// </summary>
internal class BufferMigration : IDisposable
{
/// <summary>
- /// The offset for the migrated region.
+ /// Ranges from source buffers that were copied as part of this migration.
+ /// Ordered by increasing base address.
/// </summary>
- private readonly ulong _offset;
+ public BufferMigrationSpan[] Spans { get; private set; }
/// <summary>
- /// The size for the migrated region.
+ /// The destination range list. This range list must be updated when flushing the source.
/// </summary>
- private readonly ulong _size;
+ public readonly BufferModifiedRangeList Destination;
/// <summary>
- /// The buffer that was migrated from.
+ /// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation.
/// </summary>
- private readonly Buffer _buffer;
+ public readonly ulong SyncNumber;
/// <summary>
- /// The source range action, to be called on overlap with an unreached sync number.
+ /// Number of active users there are traversing this migration's spans.
/// </summary>
- private readonly Action<ulong, ulong> _sourceRangeAction;
+ private int _refCount;
/// <summary>
- /// The source range list.
+ /// Create a new buffer migration.
/// </summary>
- private readonly BufferModifiedRangeList _source;
+ /// <param name="spans">Source spans for the migration</param>
+ /// <param name="destination">Destination buffer range list</param>
+ /// <param name="syncNumber">Sync number where this migration will be complete</param>
+ public BufferMigration(BufferMigrationSpan[] spans, BufferModifiedRangeList destination, ulong syncNumber)
+ {
+ Spans = spans;
+ Destination = destination;
+ SyncNumber = syncNumber;
+ }
/// <summary>
- /// The destination range list. This range list must be updated when flushing the source.
+ /// Add a span to the migration. Allocates a new array with the target size, and replaces it.
/// </summary>
- public readonly BufferModifiedRangeList Destination;
+ /// <remarks>
+ /// The base address for the span is assumed to be higher than all other spans in the migration,
+ /// to keep the span array ordered.
+ /// </remarks>
+ 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;
+ }
/// <summary>
- /// The sync number that needs to be reached for this migration to be removed. This is set to the pending sync number on creation.
+ /// Performs the given range action, or one from a migration that overlaps and has not synced yet.
/// </summary>
- public readonly ulong SyncNumber;
+ /// <param name="offset">The offset to pass to the action</param>
+ /// <param name="size">The size to pass to the action</param>
+ /// <param name="syncNumber">The sync number that has been reached</param>
+ /// <param name="rangeAction">The action to perform</param>
+ 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);
+ }
+ }
+
+ /// <summary>
+ /// 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)
+ /// </summary>
+ 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();
+ }
+ }
+ }
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ internal readonly struct BufferMigrationSpan : IDisposable
+ {
+ /// <summary>
+ /// The offset for the migrated region.
+ /// </summary>
+ public readonly ulong Address;
+
+ /// <summary>
+ /// The size for the migrated region.
+ /// </summary>
+ public readonly ulong Size;
+
+ /// <summary>
+ /// The action to perform when the migration isn't needed anymore.
+ /// </summary>
+ private readonly Action _disposeAction;
+
+ /// <summary>
+ /// The source range action, to be called on overlap with an unreached sync number.
+ /// </summary>
+ private readonly BufferFlushAction _sourceRangeAction;
+
+ /// <summary>
+ /// 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.
+ /// </summary>
+ private readonly BufferMigration _source;
/// <summary>
/// Creates a record for a buffer migration.
/// </summary>
/// <param name="buffer">The source buffer for this migration</param>
+ /// <param name="disposeAction">The action to perform when the migration isn't needed anymore</param>
/// <param name="sourceRangeAction">The flush action for the source buffer</param>
- /// <param name="source">The modified range list for the source buffer</param>
- /// <param name="dest">The modified range list for the destination buffer</param>
- /// <param name="syncNumber">The sync number for when the migration is complete</param>
- public BufferMigration(
+ /// <param name="source">Pending migration for the source buffer</param>
+ public BufferMigrationSpan(
Buffer buffer,
- Action<ulong, ulong> sourceRangeAction,
- BufferModifiedRangeList source,
- BufferModifiedRangeList dest,
- ulong syncNumber)
+ Action disposeAction,
+ BufferFlushAction sourceRangeAction,
+ BufferMigration source)
{
- _offset = buffer.Address;
- _size = buffer.Size;
- _buffer = buffer;
+ Address = buffer.Address;
+ Size = buffer.Size;
+ _disposeAction = disposeAction;
_sourceRangeAction = sourceRangeAction;
_source = source;
- Destination = dest;
- SyncNumber = syncNumber;
}
/// <summary>
+ /// Creates a record for a buffer migration, using the default buffer dispose action.
+ /// </summary>
+ /// <param name="buffer">The source buffer for this migration</param>
+ /// <param name="sourceRangeAction">The flush action for the source buffer</param>
+ /// <param name="source">Pending migration for the source buffer</param>
+ public BufferMigrationSpan(
+ Buffer buffer,
+ BufferFlushAction sourceRangeAction,
+ BufferMigration source) : this(buffer, buffer.DecrementReferenceCount, sourceRangeAction, source) { }
+
+ /// <summary>
/// Determine if the given range overlaps this migration, and has not been completed yet.
/// </summary>
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
- /// <param name="syncNumber">The sync number that was waited on</param>
/// <returns>True if overlapping and in progress, false otherwise</returns>
- public bool Overlaps(ulong offset, ulong size, ulong syncNumber)
+ public bool Overlaps(ulong offset, ulong size)
{
ulong end = offset + size;
- ulong destEnd = _offset + _size;
- long syncDiff = (long)(syncNumber - SyncNumber); // syncNumber is less if the copy has not completed.
+ ulong destEnd = Address + Size;
- return !(end <= _offset || offset >= destEnd) && syncDiff < 0;
- }
-
- /// <summary>
- /// Determine if the given range matches this migration.
- /// </summary>
- /// <param name="offset">Start offset</param>
- /// <param name="size">Range size</param>
- /// <returns>True if the range exactly matches, false otherwise</returns>
- public bool FullyMatches(ulong offset, ulong size)
- {
- return _offset == offset && _size == size;
+ return !(end <= Address || offset >= destEnd);
}
/// <summary>
@@ -100,26 +222,30 @@ namespace Ryujinx.Graphics.Gpu.Memory
/// <param name="offset">Start offset</param>
/// <param name="size">Range size</param>
/// <param name="syncNumber">Current sync number</param>
- /// <param name="parent">The modified range list that originally owned this range</param>
- public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent)
+ public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber)
{
ulong end = offset + size;
- end = Math.Min(_offset + _size, end);
- offset = Math.Max(_offset, offset);
+ end = Math.Min(Address + Size, end);
+ offset = Math.Max(Address, offset);
size = end - offset;
- _source.RangeActionWithMigration(offset, size, syncNumber, parent, _sourceRangeAction);
+ if (_source != null)
+ {
+ _source.RangeActionWithMigration(offset, size, syncNumber, _sourceRangeAction);
+ }
+ else
+ {
+ _sourceRangeAction(offset, size, syncNumber);
+ }
}
/// <summary>
- /// Removes this reference to the range list, potentially allowing for the source buffer to be disposed.
+ /// Removes this migration span, potentially allowing for the source buffer to be disposed.
/// </summary>
public void Dispose()
{
- Destination.RemoveMigration(this);
-
- _buffer.DecrementReferenceCount();
+ _disposeAction();
}
}
}