Ryujinx/Ryujinx.Memory/Range/MultiRange.cs
riperiperi 1fc90e57d2
Update range for remapped sparse textures instead of recreating them (#4442)
* Update sparsely mapped texture ranges without recreating

Important TODO in TexturePool. Smaller TODO: should I look into making textures with views also do this? It needs to be able to detect if the views can be instantly deleted without issue if they're now remapped.

* Actually do partial updates

* Signal group dirty after mappings changed

* Fix various issues (should work now)

* Further optimisation

Should load a lot less data (16x) when partial updating 3d textures.

* Improve stability

* Allow granular uploads on large textures, improve rules

* Actually avoid updating slices that aren't modified.

* Address some feedback, minor optimisation

* Small tweak

* Refactor DereferenceRequest

More specific initialization methods.

* Improve code for resetting handles

* Explain data loading a bit more

* Add some safety for setting null from different threads.

All texture sets come from the one thread, but null sets can come from multiple. Only decrement ref count if we succeeded the null set first.

* Address feedback 1

* Make a bit safer
2023-03-14 17:08:44 -03:00

360 lines
12 KiB
C#

using System;
using System.Collections.Generic;
namespace Ryujinx.Memory.Range
{
/// <summary>
/// Sequence of physical memory regions that a single non-contiguous virtual memory region maps to.
/// </summary>
public readonly struct MultiRange : IEquatable<MultiRange>
{
private const ulong InvalidAddress = ulong.MaxValue;
private readonly MemoryRange _singleRange;
private readonly MemoryRange[] _ranges;
private bool HasSingleRange => _ranges == null;
/// <summary>
/// Total of physical sub-ranges on the virtual memory region.
/// </summary>
public int Count => HasSingleRange ? 1 : _ranges.Length;
/// <summary>
/// Minimum start address of all sub-ranges.
/// </summary>
public ulong MinAddress { get; }
/// <summary>
/// Maximum end address of all sub-ranges.
/// </summary>
public ulong MaxAddress { get; }
/// <summary>
/// Creates a new multi-range with a single physical region.
/// </summary>
/// <param name="address">Start address of the region</param>
/// <param name="size">Size of the region in bytes</param>
public MultiRange(ulong address, ulong size)
{
_singleRange = new MemoryRange(address, size);
_ranges = null;
MinAddress = address;
MaxAddress = address + size;
}
/// <summary>
/// Creates a new multi-range with multiple physical regions.
/// </summary>
/// <param name="ranges">Array of physical regions</param>
/// <exception cref="ArgumentNullException"><paramref name="ranges"/> is null</exception>
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;
}
}
/// <summary>
/// Gets a slice of the multi-range.
/// </summary>
/// <param name="offset">Offset of the slice into the multi-range in bytes</param>
/// <param name="size">Size of the slice in bytes</param>
/// <returns>A new multi-range representing the given slice of this one</returns>
public MultiRange GetSlice(ulong offset, ulong size)
{
if (HasSingleRange)
{
if (_singleRange.Size - offset < size)
{
throw new ArgumentOutOfRangeException(nameof(size));
}
return new MultiRange(_singleRange.Address + offset, size);
}
else
{
var ranges = new List<MemoryRange>();
foreach (MemoryRange range in _ranges)
{
if ((long)offset <= 0)
{
ranges.Add(new MemoryRange(range.Address, Math.Min(size, range.Size)));
size -= range.Size;
}
else if (offset < range.Size)
{
ulong sliceSize = Math.Min(size, range.Size - offset);
if (range.Address == InvalidAddress)
{
ranges.Add(new MemoryRange(range.Address, sliceSize));
}
else
{
ranges.Add(new MemoryRange(range.Address + offset, sliceSize));
}
size -= sliceSize;
}
if ((long)size <= 0)
{
break;
}
offset -= range.Size;
}
return new MultiRange(ranges.ToArray());
}
}
/// <summary>
/// Gets the physical region at the specified index.
/// </summary>
/// <param name="index">Index of the physical region</param>
/// <returns>Region at the index specified</returns>
/// <exception cref="ArgumentOutOfRangeException"><paramref name="index"/> is invalid</exception>
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];
}
}
/// <summary>
/// Gets the physical region at the specified index, without explicit bounds checking.
/// </summary>
/// <param name="index">Index of the physical region</param>
/// <returns>Region at the index specified</returns>
private MemoryRange GetSubRangeUnchecked(int index)
{
return HasSingleRange ? _singleRange : _ranges[index];
}
/// <summary>
/// Check if two multi-ranges overlap with each other.
/// </summary>
/// <param name="other">Other multi-range to check for overlap</param>
/// <returns>True if any sub-range overlaps, false otherwise</returns>
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;
}
/// <summary>
/// Checks if a given multi-range is fully contained inside another.
/// </summary>
/// <param name="other">Multi-range to be checked</param>
/// <returns>True if all the sub-ranges on <paramref name="other"/> are contained inside the multi-range, with the same order, false otherwise</returns>
public bool Contains(MultiRange other)
{
return FindOffset(other) >= 0;
}
/// <summary>
/// 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.
/// </summary>
/// <param name="other">Multi-range that should be fully contained inside this one</param>
/// <returns>Offset in bytes if fully contained, otherwise -1</returns>
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;
}
/// <summary>
/// Gets the total size of all sub-ranges in bytes.
/// </summary>
/// <returns>Total size in bytes</returns>
public ulong GetSize()
{
if (HasSingleRange)
{
return _singleRange.Size;
}
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();
}
}
}