|
|
@@ -1,130 +1,83 @@
|
|
|
+using ChocolArm64.Events;
|
|
|
using ChocolArm64.Memory;
|
|
|
-using System;
|
|
|
+using System.Collections.Concurrent;
|
|
|
|
|
|
namespace Ryujinx.Graphics.Memory
|
|
|
{
|
|
|
class NvGpuVmmCache
|
|
|
{
|
|
|
- private struct CachedResource
|
|
|
- {
|
|
|
- public long Key;
|
|
|
- public int Mask;
|
|
|
+ private const int PageBits = MemoryManager.PageBits;
|
|
|
|
|
|
- public CachedResource(long Key, int Mask)
|
|
|
- {
|
|
|
- this.Key = Key;
|
|
|
- this.Mask = Mask;
|
|
|
- }
|
|
|
+ private const long PageSize = MemoryManager.PageSize;
|
|
|
+ private const long PageMask = MemoryManager.PageMask;
|
|
|
|
|
|
- public override int GetHashCode()
|
|
|
- {
|
|
|
- return (int)(Key * 23 + Mask);
|
|
|
- }
|
|
|
+ private ConcurrentDictionary<long, int>[] CachedPages;
|
|
|
|
|
|
- public override bool Equals(object obj)
|
|
|
- {
|
|
|
- return obj is CachedResource Cached && Equals(Cached);
|
|
|
- }
|
|
|
+ private MemoryManager _memory;
|
|
|
|
|
|
- public bool Equals(CachedResource other)
|
|
|
- {
|
|
|
- return Key == other.Key && Mask == other.Mask;
|
|
|
- }
|
|
|
- }
|
|
|
+ public NvGpuVmmCache(MemoryManager memory)
|
|
|
+ {
|
|
|
+ _memory = memory;
|
|
|
|
|
|
- private ValueRangeSet<CachedResource> CachedRanges;
|
|
|
+ _memory.ObservedAccess += MemoryAccessHandler;
|
|
|
|
|
|
- public NvGpuVmmCache()
|
|
|
- {
|
|
|
- CachedRanges = new ValueRangeSet<CachedResource>();
|
|
|
+ CachedPages = new ConcurrentDictionary<long, int>[1 << 20];
|
|
|
}
|
|
|
|
|
|
- public bool IsRegionModified(MemoryManager Memory, NvGpuBufferType BufferType, long Start, long Size)
|
|
|
+ private void MemoryAccessHandler(object sender, MemoryAccessEventArgs e)
|
|
|
{
|
|
|
- (bool[] Modified, long ModifiedCount) = Memory.IsRegionModified(Start, Size);
|
|
|
+ long pa = _memory.GetPhysicalAddress(e.Position);
|
|
|
|
|
|
- //Remove all modified ranges.
|
|
|
- int Index = 0;
|
|
|
-
|
|
|
- long Position = Start & ~NvGpuVmm.PageMask;
|
|
|
-
|
|
|
- while (ModifiedCount > 0)
|
|
|
- {
|
|
|
- if (Modified[Index++])
|
|
|
- {
|
|
|
- CachedRanges.Remove(new ValueRange<CachedResource>(Position, Position + NvGpuVmm.PageSize));
|
|
|
-
|
|
|
- ModifiedCount--;
|
|
|
- }
|
|
|
-
|
|
|
- Position += NvGpuVmm.PageSize;
|
|
|
- }
|
|
|
-
|
|
|
- //Mask has the bit set for the current resource type.
|
|
|
- //If the region is not yet present on the list, then a new ValueRange
|
|
|
- //is directly added with the current resource type as the only bit set.
|
|
|
- //Otherwise, it just sets the bit for this new resource type on the current mask.
|
|
|
- //The physical address of the resource is used as key, those keys are used to keep
|
|
|
- //track of resources that are already on the cache. A resource may be inside another
|
|
|
- //resource, and in this case we should return true if the "sub-resource" was not
|
|
|
- //yet cached.
|
|
|
- int Mask = 1 << (int)BufferType;
|
|
|
+ CachedPages[pa >> PageBits]?.Clear();
|
|
|
+ }
|
|
|
|
|
|
- CachedResource NewCachedValue = new CachedResource(Start, Mask);
|
|
|
+ public bool IsRegionModified(long position, long size, NvGpuBufferType bufferType)
|
|
|
+ {
|
|
|
+ long pa = _memory.GetPhysicalAddress(position);
|
|
|
|
|
|
- ValueRange<CachedResource> NewCached = new ValueRange<CachedResource>(Start, Start + Size);
|
|
|
+ long addr = pa;
|
|
|
|
|
|
- ValueRange<CachedResource>[] Ranges = CachedRanges.GetAllIntersections(NewCached);
|
|
|
+ long endAddr = (addr + size + PageMask) & ~PageMask;
|
|
|
|
|
|
- bool IsKeyCached = Ranges.Length > 0 && Ranges[0].Value.Key == Start;
|
|
|
+ int newBuffMask = 1 << (int)bufferType;
|
|
|
|
|
|
- long LastEnd = NewCached.Start;
|
|
|
+ _memory.StartObservingRegion(position, size);
|
|
|
|
|
|
- long Coverage = 0;
|
|
|
+ long cachedPagesCount = 0;
|
|
|
|
|
|
- for (Index = 0; Index < Ranges.Length; Index++)
|
|
|
+ while (addr < endAddr)
|
|
|
{
|
|
|
- ValueRange<CachedResource> Current = Ranges[Index];
|
|
|
-
|
|
|
- CachedResource Cached = Current.Value;
|
|
|
+ long page = addr >> PageBits;
|
|
|
|
|
|
- long RgStart = Math.Max(Current.Start, NewCached.Start);
|
|
|
- long RgEnd = Math.Min(Current.End, NewCached.End);
|
|
|
+ ConcurrentDictionary<long, int> dictionary = CachedPages[page];
|
|
|
|
|
|
- if ((Cached.Mask & Mask) != 0)
|
|
|
+ if (dictionary == null)
|
|
|
{
|
|
|
- Coverage += RgEnd - RgStart;
|
|
|
+ dictionary = new ConcurrentDictionary<long, int>();
|
|
|
+
|
|
|
+ CachedPages[page] = dictionary;
|
|
|
}
|
|
|
|
|
|
- //Highest key value has priority, this prevents larger resources
|
|
|
- //for completely invalidating smaller ones on the cache. For example,
|
|
|
- //consider that a resource in the range [100, 200) was added, and then
|
|
|
- //another one in the range [50, 200). We prevent the new resource from
|
|
|
- //completely replacing the old one by spliting it like this:
|
|
|
- //New resource key is added at [50, 100), old key is still present at [100, 200).
|
|
|
- if (Cached.Key < Start)
|
|
|
+ if (dictionary.TryGetValue(pa, out int currBuffMask))
|
|
|
{
|
|
|
- Cached.Key = Start;
|
|
|
+ if ((currBuffMask & newBuffMask) != 0)
|
|
|
+ {
|
|
|
+ cachedPagesCount++;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ {
|
|
|
+ dictionary[pa] |= newBuffMask;
|
|
|
+ }
|
|
|
}
|
|
|
-
|
|
|
- Cached.Mask |= Mask;
|
|
|
-
|
|
|
- CachedRanges.Add(new ValueRange<CachedResource>(RgStart, RgEnd, Cached));
|
|
|
-
|
|
|
- if (RgStart > LastEnd)
|
|
|
+ else
|
|
|
{
|
|
|
- CachedRanges.Add(new ValueRange<CachedResource>(LastEnd, RgStart, NewCachedValue));
|
|
|
+ dictionary[pa] = newBuffMask;
|
|
|
}
|
|
|
|
|
|
- LastEnd = RgEnd;
|
|
|
- }
|
|
|
-
|
|
|
- if (LastEnd < NewCached.End)
|
|
|
- {
|
|
|
- CachedRanges.Add(new ValueRange<CachedResource>(LastEnd, NewCached.End, NewCachedValue));
|
|
|
+ addr += PageSize;
|
|
|
}
|
|
|
|
|
|
- return !IsKeyCached || Coverage != Size;
|
|
|
+ return cachedPagesCount != (endAddr - pa + PageMask) >> PageBits;
|
|
|
}
|
|
|
}
|
|
|
}
|