aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--ARMeilleure/Memory/IMemoryManager.cs3
-rw-r--r--ARMeilleure/Signal/NativeSignalHandler.cs2
-rw-r--r--Ryujinx.Cpu/MemoryEhMeilleure.cs2
-rw-r--r--Ryujinx.Cpu/MemoryManager.cs8
-rw-r--r--Ryujinx.Cpu/MemoryManagerHostMapped.cs8
-rw-r--r--Ryujinx.Cpu/Tracking/CpuMultiRegionHandle.cs1
-rw-r--r--Ryujinx.Cpu/Tracking/CpuRegionHandle.cs1
-rw-r--r--Ryujinx.Cpu/Tracking/CpuSmartMultiRegionHandle.cs1
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs3
-rw-r--r--Ryujinx.Graphics.Gpu/Engine/Threed/ThreedClass.cs1
-rw-r--r--Ryujinx.Graphics.Gpu/Image/Pool.cs19
-rw-r--r--Ryujinx.Graphics.Gpu/Image/SamplerPool.cs6
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TextureCache.cs12
-rw-r--r--Ryujinx.Graphics.Gpu/Image/TexturePool.cs5
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/Buffer.cs37
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs34
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs10
-rw-r--r--Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs33
-rw-r--r--Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs2
-rw-r--r--Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs36
-rw-r--r--Ryujinx.Memory.Tests/TrackingTests.cs56
-rw-r--r--Ryujinx.Memory/AddressSpaceManager.cs2
-rw-r--r--Ryujinx.Memory/IVirtualMemoryManager.cs3
-rw-r--r--Ryujinx.Memory/Tracking/AbstractRegion.cs8
-rw-r--r--Ryujinx.Memory/Tracking/IRegionHandle.cs1
-rw-r--r--Ryujinx.Memory/Tracking/MemoryTracking.cs17
-rw-r--r--Ryujinx.Memory/Tracking/MultiRegionHandle.cs11
-rw-r--r--Ryujinx.Memory/Tracking/PreciseRegionSignal.cs4
-rw-r--r--Ryujinx.Memory/Tracking/RegionHandle.cs35
-rw-r--r--Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs11
-rw-r--r--Ryujinx.Memory/Tracking/VirtualRegion.cs19
31 files changed, 338 insertions, 53 deletions
diff --git a/ARMeilleure/Memory/IMemoryManager.cs b/ARMeilleure/Memory/IMemoryManager.cs
index 0a25eb97..c4ea70d1 100644
--- a/ARMeilleure/Memory/IMemoryManager.cs
+++ b/ARMeilleure/Memory/IMemoryManager.cs
@@ -70,6 +70,7 @@ namespace ARMeilleure.Memory
/// <param name="va">Virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="write">True if the region was written, false if read</param>
- void SignalMemoryTracking(ulong va, ulong size, bool write);
+ /// <param name="precise">True if the access is precise, false otherwise</param>
+ 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 27168969..babc7d42 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 ac7791b4..73edec47 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 2c11bab8..85ab763e 100644
--- a/Ryujinx.Cpu/MemoryManager.cs
+++ b/Ryujinx.Cpu/MemoryManager.cs
@@ -567,10 +567,16 @@ namespace Ryujinx.Cpu
}
/// <inheritdoc/>
- 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 705cedeb..c37d23a5 100644
--- a/Ryujinx.Cpu/MemoryManagerHostMapped.cs
+++ b/Ryujinx.Cpu/MemoryManagerHostMapped.cs
@@ -405,10 +405,16 @@ namespace Ryujinx.Cpu
/// <remarks>
/// This function also validates that the given range is both valid and mapped, and will throw if it is not.
/// </remarks>
- 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 78c1b240..0ed8bfc5 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<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> 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 dd122288..d2a28749 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 944e4c02..665271c6 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<ulong, ulong> modifiedAction) => _impl.QueryModified(modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> modifiedAction) => _impl.QueryModified(address, size, modifiedAction);
public void QueryModified(ulong address, ulong size, Action<ulong, ulong> 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 e3e8d5ba..9649841f 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 d4f228e9..52637c20 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 a06a7ccf..f54ce1d7 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);
}
+ /// <summary>
+ /// An action to be performed when a precise memory access occurs to this resource.
+ /// Makes sure that the dirty flags are checked.
+ /// </summary>
+ /// <param name="address">Address of the memory action</param>
+ /// <param name="size">Size in bytes</param>
+ /// <param name="write">True if the access was a write, false otherwise</param>
+ 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 aed6cb9c..5a84bd84 100644
--- a/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/SamplerPool.cs
@@ -7,8 +7,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
class SamplerPool : Pool<Sampler, SamplerDescriptor>
{
- private int _sequenceNumber;
-
/// <summary>
/// Constructs a new instance of the sampler pool.
/// </summary>
@@ -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 cc6867a6..1aa09b90 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -100,18 +100,6 @@ namespace Ryujinx.Graphics.Gpu.Image
}
/// <summary>
- /// Determines if any texture exists within the target memory range.
- /// </summary>
- /// <param name="memoryManager">The GPU memory manager</param>
- /// <param name="gpuVa">GPU virtual address to search for textures</param>
- /// <param name="size">The size of the range</param>
- /// <returns>True if any texture exists in the range, false otherwise</returns>
- public bool IsTextureInRange(MemoryManager memoryManager, ulong gpuVa, ulong size)
- {
- return _textures.FindOverlaps(memoryManager.GetPhysicalRegions(gpuVa, size), ref _textureOverlaps) != 0;
- }
-
- /// <summary>
/// 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.
/// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
index 5b5c5ab0..66cd9d4d 100644
--- a/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TexturePool.cs
@@ -12,7 +12,6 @@ namespace Ryujinx.Graphics.Gpu.Image
/// </summary>
class TexturePool : Pool<Texture, TextureDescriptor>
{
- private int _sequenceNumber;
private readonly GpuChannel _channel;
private readonly ConcurrentQueue<Texture> _dereferenceQueue = new ConcurrentQueue<Texture>();
@@ -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 af69e693..76125e31 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);
@@ -453,6 +458,38 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="address">Address of the memory action</param>
+ /// <param name="size">Size in bytes</param>
+ /// <param name="write">True if the access was a write, false otherwise</param>
+ 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;
+ }
+
+ /// <summary>
/// Called when part of the memory for this buffer has been unmapped.
/// Calls are from non-GPU threads.
/// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs b/Ryujinx.Graphics.Gpu/Memory/GpuRegionHandle.cs
index 8a9c6767..bc07bfad 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
{
+ /// <summary>
+ /// A tracking handle for a region of GPU VA, represented by one or more tracking handles in CPU VA.
+ /// </summary>
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();
+ /// <summary>
+ /// Create a new GpuRegionHandle, made up of mulitple CpuRegionHandles.
+ /// </summary>
+ /// <param name="cpuRegionHandles">The CpuRegionHandles that make up this handle</param>
public GpuRegionHandle(CpuRegionHandle[] cpuRegionHandles)
{
_cpuRegionHandles = cpuRegionHandles;
}
+ /// <summary>
+ /// Dispose the child handles.
+ /// </summary>
public void Dispose()
{
foreach (var regionHandle in _cpuRegionHandles)
@@ -41,6 +51,11 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ /// <summary>
+ /// Register an action to perform when the tracked region is read or written.
+ /// The action is automatically removed after it runs.
+ /// </summary>
+ /// <param name="action">Action to call on read or write</param>
public void RegisterAction(RegionSignal action)
{
foreach (var regionHandle in _cpuRegionHandles)
@@ -49,6 +64,22 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ /// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="action">Action to call on read or write</param>
+ public void RegisterPreciseAction(PreciseRegionSignal action)
+ {
+ foreach (var regionHandle in _cpuRegionHandles)
+ {
+ regionHandle.RegisterPreciseAction(action);
+ }
+ }
+
+ /// <summary>
+ /// Consume the dirty flag for the handles, and reprotect so it can be set on the next write.
+ /// </summary>
public void Reprotect(bool asDirty = false)
{
foreach (var regionHandle in _cpuRegionHandles)
@@ -57,6 +88,9 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
}
+ /// <summary>
+ /// Force the handles to be dirty, without reprotecting.
+ /// </summary>
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 2dc1edd2..3968cb96 100644
--- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -195,6 +195,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Writes data to GPU mapped memory, destined for a tracked resource.
+ /// </summary>
+ /// <param name="va">GPU virtual address to write the data into</param>
+ /// <param name="data">The data to be written</param>
+ public void WriteTrackedResource(ulong va, ReadOnlySpan<byte> data)
+ {
+ WriteImpl(va, data, Physical.WriteTrackedResource);
+ }
+
+ /// <summary>
/// Writes data to GPU mapped memory without write tracking.
/// </summary>
/// <param name="va">GPU virtual address to write the data into</param>
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index 0ec41a8f..d292fab0 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -81,28 +81,6 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
- /// 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.
- /// </summary>
- /// <param name="memoryManager">The GPU memory manager</param>
- /// <param name="gpuVa">GPU virtual address to write the data into</param>
- /// <param name="data">The data to be written</param>
- public void CacheResourceWrite(MemoryManager memoryManager, ulong gpuVa, ReadOnlySpan<byte> 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);
- }
- }
-
- /// <summary>
/// Gets a span of data from the application process.
/// </summary>
/// <param name="address">Start address of the range</param>
@@ -180,6 +158,17 @@ namespace Ryujinx.Graphics.Gpu.Memory
}
/// <summary>
+ /// Writes data to the application process, triggering a precise memory tracking event.
+ /// </summary>
+ /// <param name="address">Address to write into</param>
+ /// <param name="data">Data to be written</param>
+ public void WriteTrackedResource(ulong address, ReadOnlySpan<byte> data)
+ {
+ _cpuMemory.SignalMemoryTracking(address, (ulong)data.Length, true, precise: true);
+ _cpuMemory.WriteUntracked(address, data);
+ }
+
+ /// <summary>
/// Writes data to the application process.
/// </summary>
/// <param name="address">Address to write into</param>
diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
index 938c9d1d..05e157b6 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 05756503..c607464d 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 8f0612a1..d273ace3 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 050b5c05..0195644d 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 2448ba03..3056bb2e 100644
--- a/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -135,7 +135,8 @@ namespace Ryujinx.Memory
/// <param name="va">Virtual address of the region</param>
/// <param name="size">Size of the region</param>
/// <param name="write">True if the region was written, false if read</param>
- void SignalMemoryTracking(ulong va, ulong size, bool write);
+ /// <param name="precise">True if the access is precise, false otherwise</param>
+ void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
/// <summary>
/// Reprotect a region of virtual memory for tracking.
diff --git a/Ryujinx.Memory/Tracking/AbstractRegion.cs b/Ryujinx.Memory/Tracking/AbstractRegion.cs
index ffac439f..a3c3990e 100644
--- a/Ryujinx.Memory/Tracking/AbstractRegion.cs
+++ b/Ryujinx.Memory/Tracking/AbstractRegion.cs
@@ -53,6 +53,14 @@ namespace Ryujinx.Memory.Tracking
public abstract void Signal(ulong address, ulong size, bool write);
/// <summary>
+ /// Signals to the handles that a precise memory event has occurred. Assumes that the tracking lock has been obtained.
+ /// </summary>
+ /// <param name="address">Address accessed</param>
+ /// <param name="size">Size of the region affected in bytes</param>
+ /// <param name="write">Whether the region was written to or read</param>
+ public abstract void SignalPrecise(ulong address, ulong size, bool write);
+
+ /// <summary>
/// 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.
/// </summary>
diff --git a/Ryujinx.Memory/Tracking/IRegionHandle.cs b/Ryujinx.Memory/Tracking/IRegionHandle.cs
index ec802cb3..9d99d90e 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 ed3d7e38..aa7f9a6c 100644
--- a/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -190,12 +190,15 @@ namespace Ryujinx.Memory.Tracking
/// <summary>
/// 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.
/// </summary>
/// <param name="address">Virtual address accessed</param>
/// <param name="size">Size of the region affected in bytes</param>
/// <param name="write">Whether the region was written to or read</param>
+ /// <param name="precise">True if the access is precise, false otherwise</param>
/// <returns>True if the event triggered any tracking regions, false otherwise</returns>
- 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 638e7290..cce7ccb4 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 00000000..038f9595
--- /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 40deba98..5ecd53a2 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<VirtualRegion> _regions;
private readonly MemoryTracking _tracking;
private bool _disposed;
@@ -113,7 +114,10 @@ namespace Ryujinx.Memory.Tracking
/// <summary>
/// Signal that a memory action occurred within this handle's virtual regions.
/// </summary>
+ /// <param name="address">Address accessed</param>
+ /// <param name="size">Size of the region affected in bytes</param>
/// <param name="write">Whether the region was written to or read</param>
+ /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param>
internal void Signal(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable)
{
// If this handle was already unmapped (even if just partially),
@@ -164,6 +168,27 @@ namespace Ryujinx.Memory.Tracking
}
/// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="address">Address accessed</param>
+ /// <param name="size">Size of the region affected in bytes</param>
+ /// <param name="write">Whether the region was written to or read</param>
+ /// <param name="handleIterable">Reference to the handles being iterated, in case the list needs to be copied</param>
+ /// <returns>True if a precise action was performed and returned true, false otherwise</returns>
+ internal bool SignalPrecise(ulong address, ulong size, bool write, ref IList<RegionHandle> handleIterable)
+ {
+ if (!Unmapped && _preciseAction != null && _preciseAction(address, size, write))
+ {
+ return true;
+ }
+
+ Signal(address, size, write, ref handleIterable);
+
+ return false;
+ }
+
+ /// <summary>
/// Force this handle to be dirty, without reprotecting.
/// </summary>
public void ForceDirty()
@@ -244,6 +269,16 @@ namespace Ryujinx.Memory.Tracking
}
/// <summary>
+ /// 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.
+ /// </summary>
+ /// <param name="action">Action to call on read or write</param>
+ public void RegisterPreciseAction(PreciseRegionSignal action)
+ {
+ _preciseAction = action;
+ }
+
+ /// <summary>
/// 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.
/// </summary>
diff --git a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
index eabbd723..47fe72e5 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<ulong, ulong> modifiedAction)
{
if (!Dirty)
diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs
index 40f56351..57a0344a 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<RegionHandle> 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();
+ }
+ }
+
/// <summary>
/// Signal that this region has been mapped or unmapped.
/// </summary>