diff --git a/ARMeilleure/Memory/IMemoryManager.cs b/ARMeilleure/Memory/IMemoryManager.cs
index 0a25eb9727..c4ea70d17c 100644
--- a/ARMeilleure/Memory/IMemoryManager.cs
+++ b/ARMeilleure/Memory/IMemoryManager.cs
@@ -70,6 +70,7 @@ namespace ARMeilleure.Memory
/// Virtual address of the region
/// Size of the region
/// True if the region was written, false if read
- void SignalMemoryTracking(ulong va, ulong size, bool write);
+ /// True if the access is precise, false otherwise
+ void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
}
}
\ No newline at end of file
diff --git a/ARMeilleure/Signal/NativeSignalHandler.cs b/ARMeilleure/Signal/NativeSignalHandler.cs
index 2716896994..babc7d42be 100644
--- a/ARMeilleure/Signal/NativeSignalHandler.cs
+++ b/ARMeilleure/Signal/NativeSignalHandler.cs
@@ -202,7 +202,7 @@ namespace ARMeilleure.Signal
// Call the tracking action, with the pointer's relative offset to the base address.
Operand trackingActionPtr = context.Load(OperandType.I64, Const((ulong)signalStructPtr + rangeBaseOffset + 20));
- context.Call(trackingActionPtr, OperandType.I32, offset, Const(PageSize), isWrite);
+ context.Call(trackingActionPtr, OperandType.I32, offset, Const(PageSize), isWrite, Const(0));
context.Branch(endLabel);
diff --git a/Ryujinx.Cpu/MemoryEhMeilleure.cs b/Ryujinx.Cpu/MemoryEhMeilleure.cs
index ac7791b451..73edec47e5 100644
--- a/Ryujinx.Cpu/MemoryEhMeilleure.cs
+++ b/Ryujinx.Cpu/MemoryEhMeilleure.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Cpu
{
class MemoryEhMeilleure : IDisposable
{
- private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write);
+ private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write, bool precise = false);
private readonly MemoryBlock _addressSpace;
private readonly MemoryTracking _tracking;
diff --git a/Ryujinx.Cpu/MemoryManager.cs b/Ryujinx.Cpu/MemoryManager.cs
index 2c11bab810..85ab763e49 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -567,10 +567,16 @@ namespace Ryujinx.Cpu
}
///
- public void SignalMemoryTracking(ulong va, ulong size, bool write)
+ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
{
AssertValidAddressAndSize(va, size);
+ if (precise)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write, precise: true);
+ return;
+ }
+
// We emulate guard pages for software memory access. This makes for an easy transition to
// tracking using host guard pages in future, but also supporting platforms where this is not possible.
diff --git a/Ryujinx.Cpu/MemoryManagerHostMapped.cs b/Ryujinx.Cpu/MemoryManagerHostMapped.cs
index 705cedeb91..c37d23a51e 100644
--- a/Ryujinx.Cpu/MemoryManagerHostMapped.cs
+++ b/Ryujinx.Cpu/MemoryManagerHostMapped.cs
@@ -405,10 +405,16 @@ namespace Ryujinx.Cpu
///
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
///
- public void SignalMemoryTracking(ulong va, ulong size, bool write)
+ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
{
AssertValidAddressAndSize(va, size);
+ if (precise)
+ {
+ Tracking.VirtualMemoryEvent(va, size, write, precise: true);
+ return;
+ }
+
// Software table, used for managed memory tracking.
int pages = GetPagesCount(va, size, out _);
diff --git a/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs
index 78c1b24041..0ed8bfc588 100644
--- a/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs
@@ -22,6 +22,7 @@ namespace Ryujinx.Cpu.Tracking
public void QueryModified(ulong address, ulong size, Action modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
public void RegisterAction(ulong address, ulong size, RegionSignal action) => _impl.RegisterAction(address, size, action);
+ public void RegisterPreciseAction(ulong address, ulong size, PreciseRegionSignal action) => _impl.RegisterPreciseAction(address, size, action);
public void SignalWrite() => _impl.SignalWrite();
}
}
diff --git a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
index dd122288d7..d2a287493e 100644
--- a/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuRegionHandle.cs
@@ -23,6 +23,7 @@ namespace Ryujinx.Cpu.Tracking
public void ForceDirty() => _impl.ForceDirty();
public IRegionHandle GetHandle() => _impl;
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
+ public void RegisterPreciseAction(PreciseRegionSignal action) => _impl.RegisterPreciseAction(action);
public void RegisterDirtyEvent(Action action) => _impl.RegisterDirtyEvent(action);
public void Reprotect(bool asDirty = false) => _impl.Reprotect(asDirty);
diff --git a/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs b/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs
index 944e4c02af..665271c616 100644
--- a/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs
+++ b/Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs
@@ -17,6 +17,7 @@ namespace Ryujinx.Cpu.Tracking
public void Dispose() => _impl.Dispose();
public void ForceDirty(ulong address, ulong size) => _impl.ForceDirty(address, size);
public void RegisterAction(RegionSignal action) => _impl.RegisterAction(action);
+ public void RegisterPreciseAction(PreciseRegionSignal action) => _impl.RegisterPreciseAction(action);
public void QueryModified(Action modifiedAction) => _impl.QueryModified(modifiedAction);
public void QueryModified(ulong address, ulong size, Action modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action modifiedAction, int sequenceNumber) => _impl.QueryModified(address, size, modifiedAction, sequenceNumber);
diff --git a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
index e3e8d5ba7a..9649841f27 100644
--- a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
@@ -171,7 +171,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
if (_isLinear && _lineCount == 1)
{
- memoryManager.Physical.CacheResourceWrite(memoryManager, _dstGpuVa, data);
+ memoryManager.WriteTrackedResource(_dstGpuVa, data);
+ _context.AdvanceSequence();
}
else
{
diff --git a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
index d4f228e9b9..52637c20fb 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs
@@ -211,6 +211,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.Threed
{
uint syncpointId = (uint)argument & 0xFFFF;
+ _context.AdvanceSequence();
_context.CreateHostSyncIfNeeded();
_context.Renderer.UpdateCounters(); // Poll the query counters, the game may want an updated result.
_context.Synchronization.IncrementSyncpoint(syncpointId);
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
index a06a7ccf4c..f54ce1d705 100644
--- a/Ryujinx.Graphics.Gpu/Image/Pool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -15,6 +15,7 @@ namespace Ryujinx.Graphics.Gpu.Image
protected GpuContext Context;
protected PhysicalMemory PhysicalMemory;
+ protected int SequenceNumber;
protected T1[] Items;
protected T2[] DescriptorCache;
@@ -64,6 +65,7 @@ namespace Ryujinx.Graphics.Gpu.Image
Size = size;
_memoryTracking = physicalMemory.BeginGranularTracking(address, size);
+ _memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
_modifiedDelegate = RegionModified;
}
@@ -116,6 +118,23 @@ namespace Ryujinx.Graphics.Gpu.Image
InvalidateRangeImpl(mAddress, mSize);
}
+ ///
+ /// An action to be performed when a precise memory access occurs to this resource.
+ /// Makes sure that the dirty flags are checked.
+ ///
+ /// Address of the memory action
+ /// Size in bytes
+ /// True if the access was a write, false otherwise
+ private bool PreciseAction(ulong address, ulong size, bool write)
+ {
+ if (write && Context.SequenceNumber == SequenceNumber)
+ {
+ SequenceNumber--;
+ }
+
+ return false;
+ }
+
protected abstract void InvalidateRangeImpl(ulong address, ulong size);
protected abstract void Delete(T1 item);
diff --git a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
index aed6cb9cec..5a84bd8475 100644
--- a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
@@ -7,8 +7,6 @@ namespace Ryujinx.Graphics.Gpu.Image
///
class SamplerPool : Pool
{
- private int _sequenceNumber;
-
///
/// Constructs a new instance of the sampler pool.
///
@@ -30,9 +28,9 @@ namespace Ryujinx.Graphics.Gpu.Image
return null;
}
- if (_sequenceNumber != Context.SequenceNumber)
+ if (SequenceNumber != Context.SequenceNumber)
{
- _sequenceNumber = Context.SequenceNumber;
+ SequenceNumber = Context.SequenceNumber;
SynchronizeMemory();
}
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index cc6867a645..1aa09b90c6 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -99,18 +99,6 @@ namespace Ryujinx.Graphics.Gpu.Image
return TextureScaleMode.Blacklisted;
}
- ///
- /// Determines if any texture exists within the target memory range.
- ///
- /// The GPU memory manager
- /// GPU virtual address to search for textures
- /// The size of the range
- /// True if any texture exists in the range, false otherwise
- public bool IsTextureInRange(MemoryManager memoryManager, ulong gpuVa, ulong size)
- {
- return _textures.FindOverlaps(memoryManager.GetPhysicalRegions(gpuVa, size), ref _textureOverlaps) != 0;
- }
-
///
/// Determines if a given texture is "safe" for upscaling from its info.
/// Note that this is different from being compatible - this elilinates targets that would have detrimental effects when scaled.
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 5b5c5ab014..66cd9d4d13 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -12,7 +12,6 @@ namespace Ryujinx.Graphics.Gpu.Image
///
class TexturePool : Pool
{
- private int _sequenceNumber;
private readonly GpuChannel _channel;
private readonly ConcurrentQueue _dereferenceQueue = new ConcurrentQueue();
@@ -45,9 +44,9 @@ namespace Ryujinx.Graphics.Gpu.Image
return null;
}
- if (_sequenceNumber != Context.SequenceNumber)
+ if (SequenceNumber != Context.SequenceNumber)
{
- _sequenceNumber = Context.SequenceNumber;
+ SequenceNumber = Context.SequenceNumber;
SynchronizeMemory();
}
diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index af69e6938a..76125e31a0 100644
--- a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -1,3 +1,4 @@
+using Ryujinx.Common.Logging;
using Ryujinx.Cpu.Tracking;
using Ryujinx.Graphics.GAL;
using Ryujinx.Memory.Range;
@@ -104,6 +105,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
if (_useGranular)
{
_memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, baseHandles);
+
+ _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
}
else
{
@@ -123,6 +126,8 @@ namespace Ryujinx.Graphics.Gpu.Memory
handle.Dispose();
}
}
+
+ _memoryTracking.RegisterPreciseAction(PreciseAction);
}
_externalFlushDelegate = new RegionSignal(ExternalFlush);
@@ -452,6 +457,38 @@ namespace Ryujinx.Graphics.Gpu.Memory
}, true);
}
+ ///
+ /// An action to be performed when a precise memory access occurs to this resource.
+ /// For buffers, this skips flush-on-write by punching holes directly into the modified range list.
+ ///
+ /// Address of the memory action
+ /// Size in bytes
+ /// True if the access was a write, false otherwise
+ private bool PreciseAction(ulong address, ulong size, bool write)
+ {
+ if (!write)
+ {
+ // We only want to skip flush-on-write.
+ return false;
+ }
+
+ if (address < Address)
+ {
+ address = Address;
+ }
+
+ ulong maxSize = Address + Size - address;
+
+ if (size > maxSize)
+ {
+ size = maxSize;
+ }
+
+ ForceDirty(address, size);
+
+ return true;
+ }
+
///
/// Called when part of the memory for this buffer has been unmapped.
/// Calls are from non-GPU threads.
diff --git a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
index 8a9c676769..bc07bfad10 100644
--- a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
@@ -4,6 +4,9 @@ using System;
namespace Ryujinx.Graphics.Gpu.Memory
{
+ ///
+ /// A tracking handle for a region of GPU VA, represented by one or more tracking handles in CPU VA.
+ ///
class GpuRegionHandle : IRegionHandle
{
private readonly CpuRegionHandle[] _cpuRegionHandles;
@@ -28,11 +31,18 @@ namespace Ryujinx.Graphics.Gpu.Memory
public ulong Size => throw new NotSupportedException();
public ulong EndAddress => throw new NotSupportedException();
+ ///
+ /// Create a new GpuRegionHandle, made up of mulitple CpuRegionHandles.
+ ///
+ /// The CpuRegionHandles that make up this handle
public GpuRegionHandle(CpuRegionHandle[] cpuRegionHandles)
{
_cpuRegionHandles = cpuRegionHandles;
}
+ ///
+ /// Dispose the child handles.
+ ///
public void Dispose()
{
foreach (var regionHandle in _cpuRegionHandles)
@@ -41,6 +51,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Register an action to perform when the tracked region is read or written.
+ /// The action is automatically removed after it runs.
+ ///
+ /// Action to call on read or write
public void RegisterAction(RegionSignal action)
{
foreach (var regionHandle in _cpuRegionHandles)
@@ -49,6 +64,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Register an action to perform when a precise access occurs (one with exact address and size).
+ /// If the action returns true, read/write tracking are skipped.
+ ///
+ /// Action to call on read or write
+ public void RegisterPreciseAction(PreciseRegionSignal action)
+ {
+ foreach (var regionHandle in _cpuRegionHandles)
+ {
+ regionHandle.RegisterPreciseAction(action);
+ }
+ }
+
+ ///
+ /// Consume the dirty flag for the handles, and reprotect so it can be set on the next write.
+ ///
public void Reprotect(bool asDirty = false)
{
foreach (var regionHandle in _cpuRegionHandles)
@@ -57,6 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ ///
+ /// Force the handles to be dirty, without reprotecting.
+ ///
public void ForceDirty()
{
foreach (var regionHandle in _cpuRegionHandles)
diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index 2dc1edd24a..3968cb96ed 100644
--- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -194,6 +194,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
WriteImpl(va, data, Physical.Write);
}
+ ///
+ /// Writes data to GPU mapped memory, destined for a tracked resource.
+ ///
+ /// GPU virtual address to write the data into
+ /// The data to be written
+ public void WriteTrackedResource(ulong va, ReadOnlySpan data)
+ {
+ WriteImpl(va, data, Physical.WriteTrackedResource);
+ }
+
///
/// Writes data to GPU mapped memory without write tracking.
///
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index 0ec41a8f33..d292fab072 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -80,28 +80,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
- ///
- /// Write data to memory that is destined for a resource in a cache.
- /// This avoids triggering write tracking when possible, which can avoid flushes and incrementing sequence number.
- ///
- /// The GPU memory manager
- /// GPU virtual address to write the data into
- /// The data to be written
- public void CacheResourceWrite(MemoryManager memoryManager, ulong gpuVa, ReadOnlySpan data)
- {
- if (TextureCache.IsTextureInRange(memoryManager, gpuVa, (ulong)data.Length))
- {
- // No fast path yet - copy the data back and trigger write tracking.
- memoryManager.Write(gpuVa, data);
- _context.AdvanceSequence();
- }
- else
- {
- BufferCache.ForceDirty(memoryManager, gpuVa, (ulong)data.Length);
- memoryManager.WriteUntracked(gpuVa, data);
- }
- }
-
///
/// Gets a span of data from the application process.
///
@@ -179,6 +157,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
return _cpuMemory.ReadTracked(address);
}
+ ///
+ /// Writes data to the application process, triggering a precise memory tracking event.
+ ///
+ /// Address to write into
+ /// Data to be written
+ public void WriteTrackedResource(ulong address, ReadOnlySpan data)
+ {
+ _cpuMemory.SignalMemoryTracking(address, (ulong)data.Length, true, precise: true);
+ _cpuMemory.WriteUntracked(address, data);
+ }
+
///
/// Writes data to the application process.
///
diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
index 938c9d1d87..05e157b66b 100644
--- a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
+++ b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
@@ -79,7 +79,7 @@ namespace Ryujinx.Memory.Tests
throw new NotImplementedException();
}
- public void SignalMemoryTracking(ulong va, ulong size, bool write)
+ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
{
throw new NotImplementedException();
}
diff --git a/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs b/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
index 057565031d..c607464d21 100644
--- a/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
+++ b/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
@@ -399,5 +399,41 @@ namespace Ryujinx.Memory.Tests
Assert.AreEqual(singlePages[1], combinedHandles.ElementAt(9));
Assert.AreEqual(singlePages[2], combinedHandles.ElementAt(10));
}
+
+ [Test]
+ public void PreciseAction()
+ {
+ bool actionTriggered = false;
+
+ MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize);
+ PreparePages(granular, 3, PageSize * 3);
+
+ // Add a precise action to the second and third handle in the multiregion.
+ granular.RegisterPreciseAction(PageSize * 4, PageSize * 2, (_, _, _) => { actionTriggered = true; return true; });
+
+ // Precise write to first handle in the multiregion.
+ _tracking.VirtualMemoryEvent(PageSize * 3, PageSize, true, precise: true);
+ Assert.IsFalse(actionTriggered); // Action not triggered.
+
+ bool firstPageModified = false;
+ granular.QueryModified(PageSize * 3, PageSize, (_, _) => { firstPageModified = true; });
+ Assert.IsTrue(firstPageModified); // First page is modified.
+
+ // Precise write to all handles in the multiregion.
+ _tracking.VirtualMemoryEvent(PageSize * 3, PageSize * 3, true, precise: true);
+
+ bool[] pagesModified = new bool[3];
+
+ for (int i = 3; i < 6; i++)
+ {
+ int index = i - 3;
+ granular.QueryModified(PageSize * (ulong)i, PageSize, (_, _) => { pagesModified[index] = true; });
+ }
+
+ Assert.IsTrue(actionTriggered); // Action triggered.
+
+ // Precise writes are ignored on two later handles due to the action returning true.
+ Assert.AreEqual(pagesModified, new bool[] { true, false, false });
+ }
}
}
diff --git a/Ryujinx.Memory.Tests/TrackingTests.cs b/Ryujinx.Memory.Tests/TrackingTests.cs
index 8f0612a10c..d273ace32c 100644
--- a/Ryujinx.Memory.Tests/TrackingTests.cs
+++ b/Ryujinx.Memory.Tests/TrackingTests.cs
@@ -449,5 +449,61 @@ namespace Ryujinx.Memory.Tests
handle.Dispose();
}
+
+ [Test]
+ public void PreciseAction()
+ {
+ RegionHandle handle = _tracking.BeginTracking(0, PageSize);
+
+ (ulong address, ulong size, bool write)? preciseTriggered = null;
+ handle.RegisterPreciseAction((address, size, write) =>
+ {
+ preciseTriggered = (address, size, write);
+
+ return true;
+ });
+
+ (ulong address, ulong size)? readTrackingTriggered = null;
+ handle.RegisterAction((address, size) =>
+ {
+ readTrackingTriggered = (address, size);
+ });
+
+ handle.Reprotect();
+
+ _tracking.VirtualMemoryEvent(0, 4, false, precise: true);
+
+ Assert.IsNull(readTrackingTriggered); // Hasn't been triggered - precise action returned true.
+ Assert.AreEqual(preciseTriggered, (0UL, 4UL, false)); // Precise action was triggered.
+
+ _tracking.VirtualMemoryEvent(0, 4, true, precise: true);
+
+ Assert.IsNull(readTrackingTriggered); // Still hasn't been triggered.
+ bool dirtyAfterPreciseActionTrue = handle.Dirty;
+ Assert.False(dirtyAfterPreciseActionTrue); // Not dirtied - precise action returned true.
+ Assert.AreEqual(preciseTriggered, (0UL, 4UL, true)); // Precise action was triggered.
+
+ // Handle is now dirty.
+ handle.Reprotect(true);
+ preciseTriggered = null;
+
+ _tracking.VirtualMemoryEvent(4, 4, true, precise: true);
+ Assert.AreEqual(preciseTriggered, (4UL, 4UL, true)); // Precise action was triggered even though handle was dirty.
+
+ handle.Reprotect();
+ handle.RegisterPreciseAction((address, size, write) =>
+ {
+ preciseTriggered = (address, size, write);
+
+ return false; // Now, we return false, which indicates that the regular read/write behaviours should trigger.
+ });
+
+ _tracking.VirtualMemoryEvent(8, 4, true, precise: true);
+
+ Assert.AreEqual(readTrackingTriggered, (8UL, 4UL)); // Read action triggered, as precise action returned false.
+ bool dirtyAfterPreciseActionFalse = handle.Dirty;
+ Assert.True(dirtyAfterPreciseActionFalse); // Dirtied, as precise action returned false.
+ Assert.AreEqual(preciseTriggered, (8UL, 4UL, true)); // Precise action was triggered.
+ }
}
}
diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs
index 050b5c0598..0195644d47 100644
--- a/Ryujinx.Memory/AddressSpaceManager.cs
+++ b/Ryujinx.Memory/AddressSpaceManager.cs
@@ -481,7 +481,7 @@ namespace Ryujinx.Memory
throw new NotImplementedException();
}
- public void SignalMemoryTracking(ulong va, ulong size, bool write)
+ public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
{
// Only the ARM Memory Manager has tracking for now.
}
diff --git a/Ryujinx.Memory/IVirtualMemoryManager.cs b/Ryujinx.Memory/IVirtualMemoryManager.cs
index 2448ba036f..3056bb2e1b 100644
--- a/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -135,7 +135,8 @@ namespace Ryujinx.Memory
/// Virtual address of the region
/// Size of the region
/// True if the region was written, false if read
- void SignalMemoryTracking(ulong va, ulong size, bool write);
+ /// True if the access is precise, false otherwise
+ void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
///
/// Reprotect a region of virtual memory for tracking.
diff --git a/Ryujinx.Memory/Tracking/AbstractRegion.cs b/Ryujinx.Memory/Tracking/AbstractRegion.cs
index ffac439f30..a3c3990ea2 100644
--- a/Ryujinx.Memory/Tracking/AbstractRegion.cs
+++ b/Ryujinx.Memory/Tracking/AbstractRegion.cs
@@ -52,6 +52,14 @@ namespace Ryujinx.Memory.Tracking
/// Whether the region was written to or read
public abstract void Signal(ulong address, ulong size, bool write);
+ ///
+ /// Signals to the handles that a precise memory event has occurred. Assumes that the tracking lock has been obtained.
+ ///
+ /// Address accessed
+ /// Size of the region affected in bytes
+ /// Whether the region was written to or read
+ public abstract void SignalPrecise(ulong address, ulong size, bool write);
+
///
/// Split this region into two, around the specified address.
/// This region is updated to end at the split address, and a new region is created to represent past that point.
diff --git a/Ryujinx.Memory/Tracking/IRegionHandle.cs b/Ryujinx.Memory/Tracking/IRegionHandle.cs
index ec802cb365..9d99d90e84 100644
--- a/Ryujinx.Memory/Tracking/IRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/IRegionHandle.cs
@@ -13,5 +13,6 @@ namespace Ryujinx.Memory.Tracking
void ForceDirty();
void Reprotect(bool asDirty = false);
void RegisterAction(RegionSignal action);
+ void RegisterPreciseAction(PreciseRegionSignal action);
}
}
diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs
index ed3d7e381e..aa7f9a6c76 100644
--- a/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -190,12 +190,15 @@ namespace Ryujinx.Memory.Tracking
///
/// Signal that a virtual memory event happened at the given location.
+ /// This can be flagged as a precise event, which will avoid reprotection and call special handlers if possible.
+ /// A precise event has an exact address and size, rather than triggering on page granularity.
///
/// Virtual address accessed
/// Size of the region affected in bytes
/// Whether the region was written to or read
+ /// True if the access is precise, false otherwise
/// True if the event triggered any tracking regions, false otherwise
- public bool VirtualMemoryEvent(ulong address, ulong size, bool write)
+ public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise = false)
{
// Look up the virtual region using the region list.
// Signal up the chain to relevant handles.
@@ -206,7 +209,7 @@ namespace Ryujinx.Memory.Tracking
int count = _virtualRegions.FindOverlapsNonOverlapping(address, size, ref overlaps);
- if (count == 0)
+ if (count == 0 && !precise)
{
if (!_memoryManager.IsMapped(address))
{
@@ -224,7 +227,15 @@ namespace Ryujinx.Memory.Tracking
for (int i = 0; i < count; i++)
{
VirtualRegion region = overlaps[i];
- region.Signal(address, size, write);
+
+ if (precise)
+ {
+ region.SignalPrecise(address, size, write);
+ }
+ else
+ {
+ region.Signal(address, size, write);
+ }
}
}
diff --git a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
index 638e7290f9..cce7ccb4ce 100644
--- a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
@@ -206,6 +206,17 @@ namespace Ryujinx.Memory.Tracking
}
}
+ public void RegisterPreciseAction(ulong address, ulong size, PreciseRegionSignal action)
+ {
+ int startHandle = (int)((address - Address) / Granularity);
+ int lastHandle = (int)((address + (size - 1) - Address) / Granularity);
+
+ for (int i = startHandle; i <= lastHandle; i++)
+ {
+ _handles[i].RegisterPreciseAction(action);
+ }
+ }
+
public void Dispose()
{
foreach (var handle in _handles)
diff --git a/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs b/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs
new file mode 100644
index 0000000000..038f959561
--- /dev/null
+++ b/Ryujinx.Memory/Tracking/PreciseRegionSignal.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Memory.Tracking
+{
+ public delegate bool PreciseRegionSignal(ulong address, ulong size, bool write);
+}
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
index 40deba9838..5ecd53a2b6 100644
--- a/Ryujinx.Memory/Tracking/RegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -37,6 +37,7 @@ namespace Ryujinx.Memory.Tracking
private object _preActionLock = new object();
private RegionSignal _preAction; // Action to perform before a read or write. This will block the memory access.
+ private PreciseRegionSignal _preciseAction; // Action to perform on a precise read or write.
private readonly List _regions;
private readonly MemoryTracking _tracking;
private bool _disposed;
@@ -113,7 +114,10 @@ namespace Ryujinx.Memory.Tracking
///
/// Signal that a memory action occurred within this handle's virtual regions.
///
+ /// Address accessed
+ /// Size of the region affected in bytes
/// Whether the region was written to or read
+ /// Reference to the handles being iterated, in case the list needs to be copied
internal void Signal(ulong address, ulong size, bool write, ref IList handleIterable)
{
// If this handle was already unmapped (even if just partially),
@@ -163,6 +167,27 @@ namespace Ryujinx.Memory.Tracking
}
}
+ ///
+ /// Signal that a precise memory action occurred within this handle's virtual regions.
+ /// If there is no precise action, or the action returns false, the normal signal handler will be called.
+ ///
+ /// Address accessed
+ /// Size of the region affected in bytes
+ /// Whether the region was written to or read
+ /// Reference to the handles being iterated, in case the list needs to be copied
+ /// True if a precise action was performed and returned true, false otherwise
+ internal bool SignalPrecise(ulong address, ulong size, bool write, ref IList handleIterable)
+ {
+ if (!Unmapped && _preciseAction != null && _preciseAction(address, size, write))
+ {
+ return true;
+ }
+
+ Signal(address, size, write, ref handleIterable);
+
+ return false;
+ }
+
///
/// Force this handle to be dirty, without reprotecting.
///
@@ -243,6 +268,16 @@ namespace Ryujinx.Memory.Tracking
}
}
+ ///
+ /// Register an action to perform when a precise access occurs (one with exact address and size).
+ /// If the action returns true, read/write tracking are skipped.
+ ///
+ /// Action to call on read or write
+ public void RegisterPreciseAction(PreciseRegionSignal action)
+ {
+ _preciseAction = action;
+ }
+
///
/// Register an action to perform when the region is written to.
/// This action will not be removed when it is called - it is called each time the dirty flag is set.
diff --git a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
index eabbd7231e..47fe72e5bc 100644
--- a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
@@ -63,6 +63,17 @@ namespace Ryujinx.Memory.Tracking
}
}
+ public void RegisterPreciseAction(PreciseRegionSignal action)
+ {
+ foreach (var handle in _handles)
+ {
+ if (handle != null)
+ {
+ handle?.RegisterPreciseAction((address, size, write) => action(handle.Address, handle.Size, write));
+ }
+ }
+ }
+
public void QueryModified(Action modifiedAction)
{
if (!Dirty)
diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs
index 40f5635129..57a0344ac8 100644
--- a/Ryujinx.Memory/Tracking/VirtualRegion.cs
+++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs
@@ -31,6 +31,25 @@ namespace Ryujinx.Memory.Tracking
UpdateProtection();
}
+ public override void SignalPrecise(ulong address, ulong size, bool write)
+ {
+ IList handles = Handles;
+
+ bool allPrecise = true;
+
+ for (int i = 0; i < handles.Count; i++)
+ {
+ allPrecise &= handles[i].SignalPrecise(address, size, write, ref handles);
+ }
+
+ // Only update protection if a regular signal handler was called.
+ // This allows precise actions to skip reprotection costs if they want (they can still do it manually).
+ if (!allPrecise)
+ {
+ UpdateProtection();
+ }
+ }
+
///
/// Signal that this region has been mapped or unmapped.
///