From c4f56c570494a6186792439e3c0e74458cc82b5c Mon Sep 17 00:00:00 2001 From: gdkchan Date: Sun, 17 Jan 2021 15:44:34 -0300 Subject: Support for resources on non-contiguous GPU memory regions (#1905) * Support for resources on non-contiguous GPU memory regions * Implement MultiRange physical addresses, only used with a single range for now * Actually use non-contiguous ranges * GetPhysicalRegions fixes * Documentation and remove Address property from TextureInfo * Finish implementing GetWritableRegion * Fix typo --- Ryujinx.Memory/Range/MultiRange.cs | 295 +++++++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 Ryujinx.Memory/Range/MultiRange.cs (limited to 'Ryujinx.Memory/Range/MultiRange.cs') diff --git a/Ryujinx.Memory/Range/MultiRange.cs b/Ryujinx.Memory/Range/MultiRange.cs new file mode 100644 index 00000000..b40daa8a --- /dev/null +++ b/Ryujinx.Memory/Range/MultiRange.cs @@ -0,0 +1,295 @@ +using System; + +namespace Ryujinx.Memory.Range +{ + /// + /// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to. + /// + public struct MultiRange : IEquatable + { + private readonly MemoryRange _singleRange; + private readonly MemoryRange[] _ranges; + + private bool HasSingleRange => _ranges == null; + + /// + /// Total of physical sub-ranges on the virtual memory region. + /// + public int Count => HasSingleRange ? 1 : _ranges.Length; + + /// + /// Minimum start address of all sub-ranges. + /// + public ulong MinAddress { get; } + + /// + /// Maximum end address of all sub-ranges. + /// + public ulong MaxAddress { get; } + + /// + /// Creates a new multi-range with a single physical region. + /// + /// Start address of the region + /// Size of the region in bytes + public MultiRange(ulong address, ulong size) + { + _singleRange = new MemoryRange(address, size); + _ranges = null; + MinAddress = address; + MaxAddress = address + size; + } + + /// + /// Creates a new multi-range with multiple physical regions. + /// + /// Array of physical regions + /// is null + public MultiRange(MemoryRange[] ranges) + { + _singleRange = MemoryRange.Empty; + _ranges = ranges ?? throw new ArgumentNullException(nameof(ranges)); + + if (ranges.Length != 0) + { + MinAddress = ulong.MaxValue; + MaxAddress = 0UL; + + foreach (MemoryRange range in ranges) + { + if (MinAddress > range.Address) + { + MinAddress = range.Address; + } + + if (MaxAddress < range.EndAddress) + { + MaxAddress = range.EndAddress; + } + } + } + else + { + MinAddress = 0UL; + MaxAddress = 0UL; + } + } + + /// + /// Gets the physical region at the specified index. + /// + /// Index of the physical region + /// Region at the index specified + /// is invalid + public MemoryRange GetSubRange(int index) + { + if (HasSingleRange) + { + if (index != 0) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _singleRange; + } + else + { + if ((uint)index >= _ranges.Length) + { + throw new ArgumentOutOfRangeException(nameof(index)); + } + + return _ranges[index]; + } + } + + /// + /// Gets the physical region at the specified index, without explicit bounds checking. + /// + /// Index of the physical region + /// Region at the index specified + private MemoryRange GetSubRangeUnchecked(int index) + { + return HasSingleRange ? _singleRange : _ranges[index]; + } + + /// + /// Check if two multi-ranges overlap with each other. + /// + /// Other multi-range to check for overlap + /// True if any sub-range overlaps, false otherwise + public bool OverlapsWith(MultiRange other) + { + if (HasSingleRange && other.HasSingleRange) + { + return _singleRange.OverlapsWith(other._singleRange); + } + else + { + for (int i = 0; i < Count; i++) + { + MemoryRange currentRange = GetSubRangeUnchecked(i); + + for (int j = 0; j < other.Count; j++) + { + if (currentRange.OverlapsWith(other.GetSubRangeUnchecked(j))) + { + return true; + } + } + } + } + + return false; + } + + /// + /// Checks if a given multi-range is fully contained inside another. + /// + /// Multi-range to be checked + /// True if all the sub-ranges on are contained inside the multi-range, with the same order, false otherwise + public bool Contains(MultiRange other) + { + return FindOffset(other) >= 0; + } + + /// + /// Calculates the offset of a given multi-range inside another, when the multi-range is fully contained + /// inside the other multi-range, otherwise returns -1. + /// + /// Multi-range that should be fully contained inside this one + /// Offset in bytes if fully contained, otherwise -1 + public int FindOffset(MultiRange other) + { + int thisCount = Count; + int otherCount = other.Count; + + if (thisCount == 1 && otherCount == 1) + { + MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0); + MemoryRange currentFirstRange = GetSubRangeUnchecked(0); + + if (otherFirstRange.Address >= currentFirstRange.Address && + otherFirstRange.EndAddress <= currentFirstRange.EndAddress) + { + return (int)(otherFirstRange.Address - currentFirstRange.Address); + } + } + else if (thisCount >= otherCount) + { + ulong baseOffset = 0; + + MemoryRange otherFirstRange = other.GetSubRangeUnchecked(0); + MemoryRange otherLastRange = other.GetSubRangeUnchecked(otherCount - 1); + + for (int i = 0; i < (thisCount - otherCount) + 1; baseOffset += GetSubRangeUnchecked(i).Size, i++) + { + MemoryRange currentFirstRange = GetSubRangeUnchecked(i); + MemoryRange currentLastRange = GetSubRangeUnchecked(i + otherCount - 1); + + if (otherCount > 1) + { + if (otherFirstRange.Address < currentFirstRange.Address || + otherFirstRange.EndAddress != currentFirstRange.EndAddress) + { + continue; + } + + if (otherLastRange.Address != currentLastRange.Address || + otherLastRange.EndAddress > currentLastRange.EndAddress) + { + continue; + } + + bool fullMatch = true; + + for (int j = 1; j < otherCount - 1; j++) + { + if (!GetSubRangeUnchecked(i + j).Equals(other.GetSubRangeUnchecked(j))) + { + fullMatch = false; + break; + } + } + + if (!fullMatch) + { + continue; + } + } + else if (currentFirstRange.Address > otherFirstRange.Address || + currentFirstRange.EndAddress < otherFirstRange.EndAddress) + { + continue; + } + + return (int)(baseOffset + (otherFirstRange.Address - currentFirstRange.Address)); + } + } + + return -1; + } + + /// + /// Gets the total size of all sub-ranges in bytes. + /// + /// Total size in bytes + public ulong GetSize() + { + ulong sum = 0; + + foreach (MemoryRange range in _ranges) + { + sum += range.Size; + } + + return sum; + } + + public override bool Equals(object obj) + { + return obj is MultiRange other && Equals(other); + } + + public bool Equals(MultiRange other) + { + if (HasSingleRange && other.HasSingleRange) + { + return _singleRange.Equals(other._singleRange); + } + + int thisCount = Count; + if (thisCount != other.Count) + { + return false; + } + + for (int i = 0; i < thisCount; i++) + { + if (!GetSubRangeUnchecked(i).Equals(other.GetSubRangeUnchecked(i))) + { + return false; + } + } + + return true; + } + + public override int GetHashCode() + { + if (HasSingleRange) + { + return _singleRange.GetHashCode(); + } + + HashCode hash = new HashCode(); + + foreach (MemoryRange range in _ranges) + { + hash.Add(range); + } + + return hash.ToHashCode(); + } + } +} -- cgit v1.2.3