diff --git a/Ryujinx.Memory/Tracking/BitMap.cs b/Ryujinx.Memory/Tracking/BitMap.cs
new file mode 100644
index 000000000..d3f15994f
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/BitMap.cs
@@ -0,0 +1,199 @@
+using System.Runtime.CompilerServices;
+
+namespace Ryujinx.Memory.Tracking
+{
+ ///
+ /// A bitmap that can check or set large ranges of true/false values at once.
+ ///
+ struct BitMap
+ {
+ public const int IntSize = 64;
+
+ private const int IntShift = 6;
+ private const int IntMask = IntSize - 1;
+
+ ///
+ /// Masks representing the bitmap. Least significant bit first, 64-bits per mask.
+ ///
+ public readonly long[] Masks;
+
+ ///
+ /// Create a new bitmap.
+ ///
+ /// The number of bits to reserve
+ public BitMap(int count)
+ {
+ Masks = new long[(count + IntMask) / IntSize];
+ }
+
+ ///
+ /// Check if any bit in the bitmap is set.
+ ///
+ /// True if any bits are set, false otherwise
+ public bool AnySet()
+ {
+ for (int i = 0; i < Masks.Length; i++)
+ {
+ if (Masks[i] != 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Check if a bit in the bitmap is set.
+ ///
+ /// The bit index to check
+ /// True if the bit is set, false otherwise
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ public bool IsSet(int bit)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ return (Masks[wordIndex] & wordMask) != 0;
+ }
+
+ ///
+ /// Check if any bit in a range of bits in the bitmap are set. (inclusive)
+ ///
+ /// The first bit index to check
+ /// The last bit index to check
+ /// True if a bit is set, false otherwise
+ public bool IsSet(int start, int end)
+ {
+ if (start == end)
+ {
+ return IsSet(start);
+ }
+
+ int startIndex = start >> IntShift;
+ int startBit = start & IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = end >> IntShift;
+ int endBit = end & IntMask;
+ long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
+
+ if (startIndex == endIndex)
+ {
+ return (Masks[startIndex] & startMask & endMask) != 0;
+ }
+
+ if ((Masks[startIndex] & startMask) != 0)
+ {
+ return true;
+ }
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ if (Masks[i] != 0)
+ {
+ return true;
+ }
+ }
+
+ if ((Masks[endIndex] & endMask) != 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Set a bit at a specific index to 1.
+ ///
+ /// The bit index to set
+ /// True if the bit is set, false if it was already set
+ public bool Set(int bit)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ if ((Masks[wordIndex] & wordMask) != 0)
+ {
+ return false;
+ }
+
+ Masks[wordIndex] |= wordMask;
+
+ return true;
+ }
+
+ ///
+ /// Set a range of bits in the bitmap to 1.
+ ///
+ /// The first bit index to set
+ /// The last bit index to set
+ public void SetRange(int start, int end)
+ {
+ if (start == end)
+ {
+ Set(start);
+ return;
+ }
+
+ int startIndex = start >> IntShift;
+ int startBit = start & IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = end >> IntShift;
+ int endBit = end & IntMask;
+ long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
+
+ if (startIndex == endIndex)
+ {
+ Masks[startIndex] |= startMask & endMask;
+ }
+ else
+ {
+ Masks[startIndex] |= startMask;
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ Masks[i] |= -1;
+ }
+
+ Masks[endIndex] |= endMask;
+ }
+ }
+
+ ///
+ /// Clear a bit at a specific index to 0.
+ ///
+ /// The bit index to clear
+ /// True if the bit was set, false if it was not
+ public bool Clear(int bit)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ bool wasSet = (Masks[wordIndex] & wordMask) != 0;
+
+ Masks[wordIndex] &= ~wordMask;
+
+ return wasSet;
+ }
+
+ ///
+ /// Clear the bitmap entirely, setting all bits to 0.
+ ///
+ public void Clear()
+ {
+ for (int i = 0; i < Masks.Length; i++)
+ {
+ Masks[i] = 0;
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs b/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs
new file mode 100644
index 000000000..2e007bb56
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/ConcurrentBitmap.cs
@@ -0,0 +1,161 @@
+using System;
+using System.Threading;
+
+namespace Ryujinx.Memory.Tracking
+{
+ ///
+ /// A bitmap that can be safely modified from multiple threads.
+ ///
+ internal class ConcurrentBitmap
+ {
+ public const int IntSize = 64;
+
+ public const int IntShift = 6;
+ public const int IntMask = IntSize - 1;
+
+ ///
+ /// Masks representing the bitmap. Least significant bit first, 64-bits per mask.
+ ///
+ public readonly long[] Masks;
+
+ ///
+ /// Create a new multithreaded bitmap.
+ ///
+ /// The number of bits to reserve
+ /// Whether the bits should be initially set or not
+ public ConcurrentBitmap(int count, bool set)
+ {
+ Masks = new long[(count + IntMask) / IntSize];
+
+ if (set)
+ {
+ Array.Fill(Masks, -1L);
+ }
+ }
+
+ ///
+ /// Check if any bit in the bitmap is set.
+ ///
+ /// True if any bits are set, false otherwise
+ public bool AnySet()
+ {
+ for (int i = 0; i < Masks.Length; i++)
+ {
+ if (Volatile.Read(ref Masks[i]) != 0)
+ {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ ///
+ /// Check if a bit in the bitmap is set.
+ ///
+ /// The bit index to check
+ /// True if the bit is set, false otherwise
+ public bool IsSet(int bit)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ return (Volatile.Read(ref Masks[wordIndex]) & wordMask) != 0;
+ }
+
+ ///
+ /// Check if any bit in a range of bits in the bitmap are set. (inclusive)
+ ///
+ /// The first bit index to check
+ /// The last bit index to check
+ /// True if a bit is set, false otherwise
+ public bool IsSet(int start, int end)
+ {
+ if (start == end)
+ {
+ return IsSet(start);
+ }
+
+ int startIndex = start >> IntShift;
+ int startBit = start & IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = end >> IntShift;
+ int endBit = end & IntMask;
+ long endMask = (long)(ulong.MaxValue >> (IntMask - endBit));
+
+ long startValue = Volatile.Read(ref Masks[startIndex]);
+
+ if (startIndex == endIndex)
+ {
+ return (startValue & startMask & endMask) != 0;
+ }
+
+ if ((startValue & startMask) != 0)
+ {
+ return true;
+ }
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ if (Volatile.Read(ref Masks[i]) != 0)
+ {
+ return true;
+ }
+ }
+
+ long endValue = Volatile.Read(ref Masks[endIndex]);
+
+ if ((endValue & endMask) != 0)
+ {
+ return true;
+ }
+
+ return false;
+ }
+
+ ///
+ /// Set a bit at a specific index to either true or false.
+ ///
+ /// The bit index to set
+ /// Whether the bit should be set or not
+ public void Set(int bit, bool value)
+ {
+ int wordIndex = bit >> IntShift;
+ int wordBit = bit & IntMask;
+
+ long wordMask = 1L << wordBit;
+
+ long existing;
+ long newValue;
+
+ do
+ {
+ existing = Volatile.Read(ref Masks[wordIndex]);
+
+ if (value)
+ {
+ newValue = existing | wordMask;
+ }
+ else
+ {
+ newValue = existing & ~wordMask;
+ }
+ }
+ while (Interlocked.CompareExchange(ref Masks[wordIndex], newValue, existing) != existing);
+ }
+
+ ///
+ /// Clear the bitmap entirely, setting all bits to 0.
+ ///
+ public void Clear()
+ {
+ for (int i = 0; i < Masks.Length; i++)
+ {
+ Volatile.Write(ref Masks[i], 0);
+ }
+ }
+ }
+}
diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs
index f2ac17ff9..9aa7c7ff3 100644
--- a/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -176,6 +176,26 @@ namespace Ryujinx.Memory.Tracking
}
}
+ ///
+ /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
+ ///
+ /// CPU virtual address of the region
+ /// Size of the region
+ /// The bitmap owning the dirty flag for this handle
+ /// The bit of this handle within the dirty flag
+ /// The memory tracking handle
+ internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
+ {
+ (address, size) = PageAlign(address, size);
+
+ lock (TrackingLock)
+ {
+ RegionHandle handle = new RegionHandle(this, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
+
+ return handle;
+ }
+ }
+
///
/// Signal that a virtual memory event happened at the given location (one byte).
///
diff --git a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
index cce7ccb4c..45138ff35 100644
--- a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
@@ -1,11 +1,15 @@
using System;
using System.Collections.Generic;
+using System.Numerics;
+using System.Runtime.CompilerServices;
+using System.Threading;
namespace Ryujinx.Memory.Tracking
{
///
/// A region handle that tracks a large region using many smaller handles, to provide
- /// granular tracking that can be used to track partial updates.
+ /// granular tracking that can be used to track partial updates. Backed by a bitmap
+ /// to improve performance when scanning large regions.
///
public class MultiRegionHandle : IMultiRegionHandle
{
@@ -17,6 +21,12 @@ namespace Ryujinx.Memory.Tracking
private readonly ulong Granularity;
private readonly ulong Size;
+ private ConcurrentBitmap _dirtyBitmap;
+
+ private int _sequenceNumber;
+ private BitMap _sequenceNumberBitmap;
+ private int _uncheckedHandles;
+
public bool Dirty { get; private set; } = true;
internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable handles, ulong granularity)
@@ -24,6 +34,9 @@ namespace Ryujinx.Memory.Tracking
_handles = new RegionHandle[size / granularity];
Granularity = granularity;
+ _dirtyBitmap = new ConcurrentBitmap(_handles.Length, true);
+ _sequenceNumberBitmap = new BitMap(_handles.Length);
+
int i = 0;
if (handles != null)
@@ -40,37 +53,43 @@ namespace Ryujinx.Memory.Tracking
// Fill any gap left before this handle.
while (i < startIndex)
{
- RegionHandle fillHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
+ RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
fillHandle.Parent = this;
_handles[i++] = fillHandle;
}
- if (handle.Size == granularity)
+ lock (tracking.TrackingLock)
{
- handle.Parent = this;
- _handles[i++] = handle;
- }
- else
- {
- int endIndex = (int)((handle.EndAddress - address) / granularity);
-
- while (i < endIndex)
+ if (handle is RegionHandle bitHandle && handle.Size == granularity)
{
- RegionHandle splitHandle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
- splitHandle.Parent = this;
+ handle.Parent = this;
- splitHandle.Reprotect(handle.Dirty);
+ bitHandle.ReplaceBitmap(_dirtyBitmap, i);
- RegionSignal signal = handle.PreAction;
- if (signal != null)
+ _handles[i++] = bitHandle;
+ }
+ else
+ {
+ int endIndex = (int)((handle.EndAddress - address) / granularity);
+
+ while (i < endIndex)
{
- splitHandle.RegisterAction(signal);
+ RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
+ splitHandle.Parent = this;
+
+ splitHandle.Reprotect(handle.Dirty);
+
+ RegionSignal signal = handle.PreAction;
+ if (signal != null)
+ {
+ splitHandle.RegisterAction(signal);
+ }
+
+ _handles[i++] = splitHandle;
}
- _handles[i++] = splitHandle;
+ handle.Dispose();
}
-
- handle.Dispose();
}
}
}
@@ -78,15 +97,27 @@ namespace Ryujinx.Memory.Tracking
// Fill any remaining space with new handles.
while (i < _handles.Length)
{
- RegionHandle handle = tracking.BeginTracking(address + (ulong)i * granularity, granularity);
+ RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
handle.Parent = this;
_handles[i++] = handle;
}
+ _uncheckedHandles = _handles.Length;
+
Address = address;
Size = size;
}
+ public void SignalWrite()
+ {
+ Dirty = true;
+ }
+
+ public IEnumerable GetHandles()
+ {
+ return _handles;
+ }
+
public void ForceDirty(ulong address, ulong size)
{
Dirty = true;
@@ -96,21 +127,15 @@ namespace Ryujinx.Memory.Tracking
for (int i = startHandle; i <= lastHandle; i++)
{
- _handles[i].SequenceNumber--;
+ if (_sequenceNumberBitmap.Clear(i))
+ {
+ _uncheckedHandles++;
+ }
+
_handles[i].ForceDirty();
}
}
- public IEnumerable GetHandles()
- {
- return _handles;
- }
-
- public void SignalWrite()
- {
- Dirty = true;
- }
-
public void QueryModified(Action modifiedAction)
{
if (!Dirty)
@@ -123,33 +148,95 @@ namespace Ryujinx.Memory.Tracking
QueryModified(Address, Size, modifiedAction);
}
- public void QueryModified(ulong address, ulong size, Action modifiedAction)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ParseDirtyBits(long dirtyBits, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action modifiedAction)
{
- int startHandle = (int)((address - Address) / Granularity);
- int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
-
- ulong rgStart = _handles[startHandle].Address;
- ulong rgSize = 0;
-
- for (int i = startHandle; i <= lastHandle; i++)
+ while (dirtyBits != 0)
{
- RegionHandle handle = _handles[i];
+ int bit = BitOperations.TrailingZeroCount(dirtyBits);
+
+ dirtyBits &= ~(1L << bit);
+
+ int handleIndex = baseBit + bit;
+
+ RegionHandle handle = _handles[handleIndex];
+
+ if (handleIndex != prevHandle + 1)
+ {
+ // Submit handles scanned until the gap as dirty
+ if (rgSize != 0)
+ {
+ modifiedAction(rgStart, rgSize);
+ rgSize = 0;
+ }
+ rgStart = handle.Address;
+ }
if (handle.Dirty)
{
rgSize += handle.Size;
handle.Reprotect();
}
- else
+
+ prevHandle = handleIndex;
+ }
+
+ baseBit += ConcurrentBitmap.IntSize;
+ }
+
+ public void QueryModified(ulong address, ulong size, Action modifiedAction)
+ {
+ int startHandle = (int)((address - Address) / Granularity);
+ int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+
+ ulong rgStart = _handles[startHandle].Address;
+
+ if (startHandle == lastHandle)
+ {
+ RegionHandle handle = _handles[startHandle];
+
+ if (handle.Dirty)
{
- // Submit the region scanned so far as dirty
- if (rgSize != 0)
- {
- modifiedAction(rgStart, rgSize);
- rgSize = 0;
- }
- rgStart = handle.EndAddress;
+ handle.Reprotect();
+ modifiedAction(rgStart, handle.Size);
}
+
+ return;
+ }
+
+ ulong rgSize = 0;
+
+ long[] masks = _dirtyBitmap.Masks;
+
+ int startIndex = startHandle >> ConcurrentBitmap.IntShift;
+ int startBit = startHandle & ConcurrentBitmap.IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
+ int endBit = lastHandle & ConcurrentBitmap.IntMask;
+ long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
+
+ long startValue = Volatile.Read(ref masks[startIndex]);
+
+ int baseBit = startIndex << ConcurrentBitmap.IntShift;
+ int prevHandle = startHandle - 1;
+
+ if (startIndex == endIndex)
+ {
+ ParseDirtyBits(startValue & startMask & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
+ }
+ else
+ {
+ ParseDirtyBits(startValue & startMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ ParseDirtyBits(Volatile.Read(ref masks[i]), ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
+ }
+
+ long endValue = Volatile.Read(ref masks[endIndex]);
+
+ ParseDirtyBits(endValue & endMask, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
if (rgSize != 0)
@@ -158,35 +245,120 @@ namespace Ryujinx.Memory.Tracking
}
}
- public void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber)
+ [MethodImpl(MethodImplOptions.AggressiveInlining)]
+ private void ParseDirtyBits(long dirtyBits, long mask, int index, long[] seqMasks, ref int baseBit, ref int prevHandle, ref ulong rgStart, ref ulong rgSize, Action modifiedAction)
{
- int startHandle = (int)((address - Address) / Granularity);
- int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+ long seqMask = mask & ~seqMasks[index];
+ dirtyBits &= seqMask;
- ulong rgStart = _handles[startHandle].Address;
- ulong rgSize = 0;
-
- for (int i = startHandle; i <= lastHandle; i++)
+ while (dirtyBits != 0)
{
- RegionHandle handle = _handles[i];
+ int bit = BitOperations.TrailingZeroCount(dirtyBits);
- if (sequenceNumber != handle.SequenceNumber && handle.DirtyOrVolatile())
+ dirtyBits &= ~(1L << bit);
+
+ int handleIndex = baseBit + bit;
+
+ RegionHandle handle = _handles[handleIndex];
+
+ if (handleIndex != prevHandle + 1)
{
- rgSize += handle.Size;
- handle.Reprotect();
- }
- else
- {
- // Submit the region scanned so far as dirty
+ // Submit handles scanned until the gap as dirty
if (rgSize != 0)
{
modifiedAction(rgStart, rgSize);
rgSize = 0;
}
- rgStart = handle.EndAddress;
+ rgStart = handle.Address;
}
- handle.SequenceNumber = sequenceNumber;
+ rgSize += handle.Size;
+ handle.Reprotect();
+
+ prevHandle = handleIndex;
+ }
+
+ seqMasks[index] |= mask;
+ _uncheckedHandles -= BitOperations.PopCount((ulong)seqMask);
+
+ baseBit += ConcurrentBitmap.IntSize;
+ }
+
+ public void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber)
+ {
+ int startHandle = (int)((address - Address) / Granularity);
+ int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+
+ ulong rgStart = Address + (ulong)startHandle * Granularity;
+
+ if (sequenceNumber != _sequenceNumber)
+ {
+ if (_uncheckedHandles != _handles.Length)
+ {
+ _sequenceNumberBitmap.Clear();
+ _uncheckedHandles = _handles.Length;
+ }
+
+ _sequenceNumber = sequenceNumber;
+ }
+
+ if (startHandle == lastHandle)
+ {
+ var handle = _handles[startHandle];
+ if (_sequenceNumberBitmap.Set(startHandle))
+ {
+ _uncheckedHandles--;
+
+ if (handle.DirtyOrVolatile())
+ {
+ handle.Reprotect();
+
+ modifiedAction(rgStart, handle.Size);
+ }
+ }
+
+ return;
+ }
+
+ if (_uncheckedHandles == 0)
+ {
+ return;
+ }
+
+ ulong rgSize = 0;
+
+ long[] seqMasks = _sequenceNumberBitmap.Masks;
+ long[] masks = _dirtyBitmap.Masks;
+
+ int startIndex = startHandle >> ConcurrentBitmap.IntShift;
+ int startBit = startHandle & ConcurrentBitmap.IntMask;
+ long startMask = -1L << startBit;
+
+ int endIndex = lastHandle >> ConcurrentBitmap.IntShift;
+ int endBit = lastHandle & ConcurrentBitmap.IntMask;
+ long endMask = (long)(ulong.MaxValue >> (ConcurrentBitmap.IntMask - endBit));
+
+ long startValue = Volatile.Read(ref masks[startIndex]);
+
+ int baseBit = startIndex << ConcurrentBitmap.IntShift;
+ int prevHandle = startHandle - 1;
+
+ if (startIndex == endIndex)
+ {
+ ParseDirtyBits(startValue, startMask & endMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
+ }
+ else
+ {
+ ParseDirtyBits(startValue, startMask, startIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
+
+ for (int i = startIndex + 1; i < endIndex; i++)
+ {
+ ParseDirtyBits(Volatile.Read(ref masks[i]), -1L, i, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
+ }
+
+ long endValue = Volatile.Read(ref masks[endIndex]);
+
+ ParseDirtyBits(endValue, endMask, endIndex, seqMasks, ref baseBit, ref prevHandle, ref rgStart, ref rgSize, modifiedAction);
}
if (rgSize != 0)
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
index 14c6de2c9..363bedef0 100644
--- a/Ryujinx.Memory/Tracking/RegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -1,5 +1,4 @@
-using Ryujinx.Memory.Range;
-using System;
+using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
@@ -10,7 +9,7 @@ namespace Ryujinx.Memory.Tracking
/// A tracking handle for a given region of virtual memory. The Dirty flag is updated whenever any changes are made,
/// and an action can be performed when the region is read to or written from.
///
- public class RegionHandle : IRegionHandle, IRange
+ public class RegionHandle : IRegionHandle
{
///
/// If more than this number of checks have been performed on a dirty flag since its last reprotect,
@@ -23,7 +22,20 @@ namespace Ryujinx.Memory.Tracking
///
private static int VolatileThreshold = 5;
- public bool Dirty { get; private set; }
+ public bool Dirty
+ {
+ get
+ {
+ return Bitmap.IsSet(DirtyBit);
+ }
+ protected set
+ {
+ Bitmap.Set(DirtyBit, value);
+ }
+ }
+
+ internal int SequenceNumber { get; set; }
+
public bool Unmapped { get; private set; }
public ulong Address { get; }
@@ -31,7 +43,6 @@ namespace Ryujinx.Memory.Tracking
public ulong EndAddress { get; }
internal IMultiRegionHandle Parent { get; set; }
- internal int SequenceNumber { get; set; }
private event Action _onDirty;
@@ -68,17 +79,26 @@ namespace Ryujinx.Memory.Tracking
internal RegionSignal PreAction => _preAction;
+ internal ConcurrentBitmap Bitmap;
+ internal int DirtyBit;
+
///
- /// Create a new region handle. The handle is registered with the given tracking object,
+ /// Create a new bitmap backed region handle. The handle is registered with the given tracking object,
/// and will be notified of any changes to the specified region.
///
/// Tracking object for the target memory block
/// Virtual address of the region to track
/// Size of the region to track
+ /// The bitmap the dirty flag for this handle is stored in
+ /// The bit index representing the dirty flag for this handle
/// True if the region handle starts mapped
- internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
+ internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ConcurrentBitmap bitmap, int bit, bool mapped = true)
{
+ Bitmap = bitmap;
+ DirtyBit = bit;
+
Dirty = mapped;
+
Unmapped = !mapped;
Address = address;
Size = size;
@@ -92,6 +112,54 @@ namespace Ryujinx.Memory.Tracking
}
}
+ ///
+ /// Create a new region handle. The handle is registered with the given tracking object,
+ /// and will be notified of any changes to the specified region.
+ ///
+ /// Tracking object for the target memory block
+ /// Virtual address of the region to track
+ /// Size of the region to track
+ /// True if the region handle starts mapped
+ internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, bool mapped = true)
+ {
+ Bitmap = new ConcurrentBitmap(1, mapped);
+
+ Unmapped = !mapped;
+ Address = address;
+ Size = size;
+ EndAddress = address + size;
+
+ _tracking = tracking;
+ _regions = tracking.GetVirtualRegionsForHandle(address, size);
+ foreach (var region in _regions)
+ {
+ region.Handles.Add(this);
+ }
+ }
+
+ ///
+ /// Replace the bitmap and bit index used to track dirty state.
+ ///
+ ///
+ /// The tracking lock should be held when this is called, to ensure neither bitmap is modified.
+ ///
+ /// The bitmap the dirty flag for this handle is stored in
+ /// The bit index representing the dirty flag for this handle
+ internal void ReplaceBitmap(ConcurrentBitmap bitmap, int bit)
+ {
+ // Assumes the tracking lock is held, so nothing else can signal right now.
+
+ var oldBitmap = Bitmap;
+ var oldBit = DirtyBit;
+
+ bitmap.Set(bit, Dirty);
+
+ Bitmap = bitmap;
+ DirtyBit = bit;
+
+ Dirty |= oldBitmap.IsSet(oldBit);
+ }
+
///
/// Clear the volatile state of this handle.
///
@@ -108,7 +176,7 @@ namespace Ryujinx.Memory.Tracking
public bool DirtyOrVolatile()
{
_checkCount++;
- return Dirty || _volatile;
+ return _volatile || Dirty;
}
///
@@ -195,6 +263,8 @@ namespace Ryujinx.Memory.Tracking
///
public void ForceDirty()
{
+ _checkCount++;
+
Dirty = true;
}