diff --git a/ARMeilleure/Memory/IMemoryManager.cs b/ARMeilleure/Memory/IMemoryManager.cs index 0a25eb972..c4ea70d17 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 271689699..babc7d42b 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 ac7791b45..73edec47e 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 2c11bab81..85ab763e4 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 705cedeb9..c37d23a51 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 78c1b2404..0ed8bfc58 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 dd122288d..d2a287493 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 944e4c02a..665271c61 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 e3e8d5ba7..9649841f2 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 d4f228e9b..52637c20f 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 a06a7ccf4..f54ce1d70 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 aed6cb9ce..5a84bd847 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 cc6867a64..1aa09b90c 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 5b5c5ab01..66cd9d4d1 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 af69e6938..76125e31a 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 8a9c67676..bc07bfad1 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 2dc1edd24..3968cb96e 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 0ec41a8f3..d292fab07 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 938c9d1d8..05e157b66 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 057565031..c607464d2 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 8f0612a10..d273ace32 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 050b5c059..0195644d4 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 2448ba036..3056bb2e1 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 ffac439f3..a3c3990ea 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 ec802cb36..9d99d90e8 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 ed3d7e381..aa7f9a6c7 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 638e7290f..cce7ccb4c 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 000000000..038f95956 --- /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 40deba983..5ecd53a2b 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 eabbd7231..47fe72e5b 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 40f563512..57a0344ac 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. ///