From cee712105850ac3385cd0091a923438167433f9f Mon Sep 17 00:00:00 2001 From: TSR Berry <20988865+TSRBerry@users.noreply.github.com> Date: Sat, 8 Apr 2023 01:22:00 +0200 Subject: Move solution and projects to src --- .../Memory/BufferModifiedRangeList.cs | 514 +++++++++++++++++++++ 1 file changed, 514 insertions(+) create mode 100644 src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs (limited to 'src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs') diff --git a/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs new file mode 100644 index 00000000..d0230b62 --- /dev/null +++ b/src/Ryujinx.Graphics.Gpu/Memory/BufferModifiedRangeList.cs @@ -0,0 +1,514 @@ +using Ryujinx.Common.Logging; +using Ryujinx.Common.Pools; +using Ryujinx.Memory.Range; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Ryujinx.Graphics.Gpu.Memory +{ + /// + /// A range within a buffer that has been modified by the GPU. + /// + class BufferModifiedRange : IRange + { + /// + /// Start address of the range in guest memory. + /// + public ulong Address { get; } + + /// + /// Size of the range in bytes. + /// + public ulong Size { get; } + + /// + /// End address of the range in guest memory. + /// + public ulong EndAddress => Address + Size; + + /// + /// The GPU sync number at the time of the last modification. + /// + public ulong SyncNumber { get; internal set; } + + /// + /// The range list that originally owned this range. + /// + public BufferModifiedRangeList Parent { get; internal set; } + + /// + /// Creates a new instance of a modified range. + /// + /// Start address of the range + /// Size of the range in bytes + /// The GPU sync number at the time of creation + /// The range list that owns this range + public BufferModifiedRange(ulong address, ulong size, ulong syncNumber, BufferModifiedRangeList parent) + { + Address = address; + Size = size; + SyncNumber = syncNumber; + Parent = parent; + } + + /// + /// Checks if a given range overlaps with the modified range. + /// + /// Start address of the range + /// Size in bytes of the range + /// True if the range overlaps, false otherwise + public bool OverlapsWith(ulong address, ulong size) + { + return Address < address + size && address < EndAddress; + } + } + + /// + /// A structure used to track GPU modified ranges within a buffer. + /// + class BufferModifiedRangeList : RangeList + { + private const int BackingInitialSize = 8; + + private GpuContext _context; + private Buffer _parent; + private Action _flushAction; + + private List _sources; + private BufferMigration _migrationTarget; + + private object _lock = new object(); + + /// + /// Whether the modified range list has any entries or not. + /// + public bool HasRanges + { + get + { + lock (_lock) + { + return Count > 0; + } + } + } + + /// + /// Creates a new instance of a modified range list. + /// + /// GPU context that the buffer range list belongs to + /// The parent buffer that owns this range list + /// The flush action for the parent buffer + public BufferModifiedRangeList(GpuContext context, Buffer parent, Action flushAction) : base(BackingInitialSize) + { + _context = context; + _parent = parent; + _flushAction = flushAction; + } + + /// + /// Given an input range, calls the given action with sub-ranges which exclude any of the modified regions. + /// + /// Start address of the query range + /// Size of the query range in bytes + /// Action to perform for each remaining sub-range of the input range + public void ExcludeModifiedRegions(ulong address, ulong size, Action action) + { + lock (_lock) + { + // Slices a given region using the modified regions in the list. Calls the action for the new slices. + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = FindOverlapsNonOverlapping(address, size, ref overlaps); + + for (int i = 0; i < count; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + if (overlap.Address > address) + { + // The start of the remaining region is uncovered by this overlap. Call the action for it. + action(address, overlap.Address - address); + } + + // Remaining region is after this overlap. + size -= overlap.EndAddress - address; + address = overlap.EndAddress; + } + + if ((long)size > 0) + { + // If there is any region left after removing the overlaps, signal it. + action(address, size); + } + } + } + + /// + /// Signal that a region of the buffer has been modified, and add the new region to the range list. + /// Any overlapping ranges will be (partially) removed. + /// + /// Start address of the modified region + /// Size of the modified region in bytes + public void SignalModified(ulong address, ulong size) + { + // Must lock, as this can affect flushes from the background thread. + lock (_lock) + { + // We may overlap with some existing modified regions. They must be cut into by the new entry. + ref var overlaps = ref ThreadStaticArray.Get(); + + int count = FindOverlapsNonOverlapping(address, size, ref overlaps); + + ulong endAddress = address + size; + ulong syncNumber = _context.SyncNumber; + + for (int i = 0; i < count; i++) + { + // The overlaps must be removed or split. + + BufferModifiedRange overlap = overlaps[i]; + + if (overlap.Address == address && overlap.Size == size) + { + // Region already exists. Just update the existing sync number. + overlap.SyncNumber = syncNumber; + overlap.Parent = this; + + return; + } + + Remove(overlap); + + if (overlap.Address < address && overlap.EndAddress > address) + { + // A split item must be created behind this overlap. + + Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); + } + + if (overlap.Address < endAddress && overlap.EndAddress > endAddress) + { + // A split item must be created after this overlap. + + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + } + } + + Add(new BufferModifiedRange(address, size, syncNumber, this)); + } + } + + /// + /// Gets modified ranges within the specified region, and then fires the given action for each range individually. + /// + /// Start address to query + /// Size to query + /// The action to call for each modified range + public void GetRanges(ulong address, ulong size, Action rangeAction) + { + int count = 0; + + ref var overlaps = ref ThreadStaticArray.Get(); + + // Range list must be consistent for this operation. + lock (_lock) + { + count = FindOverlapsNonOverlapping(address, size, ref overlaps); + } + + for (int i = 0; i < count; i++) + { + BufferModifiedRange overlap = overlaps[i]; + rangeAction(overlap.Address, overlap.Size); + } + } + + /// + /// Queries if a range exists within the specified region. + /// + /// Start address to query + /// Size to query + /// True if a range exists in the specified region, false otherwise + public bool HasRange(ulong address, ulong size) + { + // Range list must be consistent for this operation. + lock (_lock) + { + return FindOverlapsNonOverlapping(address, size, ref ThreadStaticArray.Get()) > 0; + } + } + + /// + /// 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 modified range list that originally owned this range + /// The action to perform + public void RangeActionWithMigration(ulong offset, ulong size, ulong syncNumber, BufferModifiedRangeList parent, Action rangeAction) + { + bool firstSource = true; + + if (parent != this) + { + lock (_lock) + { + if (_sources != null) + { + foreach (BufferMigration source in _sources) + { + if (source.Overlaps(offset, size, syncNumber)) + { + if (firstSource && !source.FullyMatches(offset, size)) + { + // Perform this buffer's action first. The migrations will run after. + rangeAction(offset, size); + } + + source.RangeActionWithMigration(offset, size, syncNumber, parent); + + firstSource = false; + } + } + } + } + } + + if (firstSource) + { + // No overlapping migrations, or they are not meant for this range, flush the data using the given action. + rangeAction(offset, size); + } + } + + /// + /// Removes modified ranges ready by the sync number from the list, and flushes their buffer data within a given address range. + /// + /// Overlapping ranges to check + /// Number of overlapping ranges + /// The highest difference between an overlapping range's sync number and the current one + /// The current sync number + /// The start address of the flush range + /// The end address of the flush range + private void RemoveRangesAndFlush( + BufferModifiedRange[] overlaps, + int rangeCount, + long highestDiff, + ulong currentSync, + ulong address, + ulong endAddress) + { + lock (_lock) + { + if (_migrationTarget == null) + { + ulong waitSync = currentSync + (ulong)highestDiff; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + long diff = (long)(overlap.SyncNumber - currentSync); + + if (diff <= highestDiff) + { + ulong clampAddress = Math.Max(address, overlap.Address); + ulong clampEnd = Math.Min(endAddress, overlap.EndAddress); + + ClearPart(overlap, clampAddress, clampEnd); + + RangeActionWithMigration(clampAddress, clampEnd - clampAddress, waitSync, overlap.Parent, _flushAction); + } + } + + return; + } + } + + // There is a migration target to call instead. This can't be changed after set so accessing it outside the lock is fine. + + _migrationTarget.Destination.RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); + } + + /// + /// Gets modified ranges within the specified region, waits on ones from a previous sync number, + /// and then fires the flush action for each range individually. + /// + /// + /// This function assumes it is called from the background thread. + /// Modifications from the current sync number are ignored because the guest should not expect them to be available yet. + /// They will remain reserved, so that any data sync prioritizes the data in the GPU. + /// + /// Start address to query + /// Size to query + public void WaitForAndFlushRanges(ulong address, ulong size) + { + ulong endAddress = address + size; + ulong currentSync = _context.SyncNumber; + + int rangeCount = 0; + + ref var overlaps = ref ThreadStaticArray.Get(); + + // Range list must be consistent for this operation + lock (_lock) + { + if (_migrationTarget != null) + { + rangeCount = -1; + } + else + { + rangeCount = FindOverlapsNonOverlapping(address, size, ref overlaps); + } + } + + if (rangeCount == -1) + { + _migrationTarget.Destination.WaitForAndFlushRanges(address, size); + + return; + } + else if (rangeCount == 0) + { + return; + } + + // First, determine which syncpoint to wait on. + // This is the latest syncpoint that is not equal to the current sync. + + long highestDiff = long.MinValue; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = overlaps[i]; + + long diff = (long)(overlap.SyncNumber - currentSync); + + if (diff < 0 && diff > highestDiff) + { + highestDiff = diff; + } + } + + if (highestDiff == long.MinValue) + { + return; + } + + // Wait for the syncpoint. + _context.Renderer.WaitSync(currentSync + (ulong)highestDiff); + + RemoveRangesAndFlush(overlaps, rangeCount, highestDiff, currentSync, address, endAddress); + } + + /// + /// Inherit ranges from another modified range list. + /// + /// The range list to inherit from + /// The action to call for each modified range + public void InheritRanges(BufferModifiedRangeList ranges, Action registerRangeAction) + { + BufferModifiedRange[] inheritRanges; + + lock (ranges._lock) + { + BufferMigration migration = new(ranges._parent, ranges._flushAction, ranges, this, _context.SyncNumber); + + ranges._parent.IncrementReferenceCount(); + ranges._migrationTarget = migration; + + _context.RegisterBufferMigration(migration); + + inheritRanges = ranges.ToArray(); + + lock (_lock) + { + (_sources ??= new List()).Add(migration); + + foreach (BufferModifiedRange range in inheritRanges) + { + Add(range); + } + } + } + + ulong currentSync = _context.SyncNumber; + foreach (BufferModifiedRange range in inheritRanges) + { + if (range.SyncNumber != currentSync) + { + registerRangeAction(range.Address, range.Size); + } + } + } + + /// + /// Removes a source buffer migration, indicating its copy has completed. + /// + /// The migration to remove + public void RemoveMigration(BufferMigration migration) + { + lock (_lock) + { + _sources.Remove(migration); + } + } + + private void ClearPart(BufferModifiedRange overlap, ulong address, ulong endAddress) + { + Remove(overlap); + + // If the overlap extends outside of the clear range, make sure those parts still exist. + + if (overlap.Address < address) + { + Add(new BufferModifiedRange(overlap.Address, address - overlap.Address, overlap.SyncNumber, overlap.Parent)); + } + + if (overlap.EndAddress > endAddress) + { + Add(new BufferModifiedRange(endAddress, overlap.EndAddress - endAddress, overlap.SyncNumber, overlap.Parent)); + } + } + + /// + /// Clear modified ranges within the specified area. + /// + /// Start address to clear + /// Size to clear + public void Clear(ulong address, ulong size) + { + lock (_lock) + { + // This function can be called from any thread, so it cannot use the arrays for background or foreground. + BufferModifiedRange[] toClear = new BufferModifiedRange[1]; + + int rangeCount = FindOverlapsNonOverlapping(address, size, ref toClear); + + ulong endAddress = address + size; + + for (int i = 0; i < rangeCount; i++) + { + BufferModifiedRange overlap = toClear[i]; + + ClearPart(overlap, address, endAddress); + } + } + } + + /// + /// Clear all modified ranges. + /// + public void Clear() + { + lock (_lock) + { + Count = 0; + } + } + } +} -- cgit v1.2.3