From efb135b74c9c0ff1de2dfd7d2a431bd23185ca66 Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Thu, 16 Feb 2023 18:28:49 -0300
Subject: [PATCH] Clear CPU side data on GPU buffer clears (#4125)

* Clear CPU side data on GPU buffer clears

* Implement tracked fill operation that can signal other resource types except buffer

* Fix tests, add missing XML doc

* PR feedback
---
 ARMeilleure/Memory/IMemoryManager.cs          |  3 +-
 ARMeilleure/Signal/NativeSignalHandler.cs     |  2 +-
 Ryujinx.Cpu/AppleHv/HvMemoryManager.cs        | 20 +++++-----
 Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs   |  9 +++--
 Ryujinx.Cpu/Jit/MemoryManager.cs              | 18 ++++-----
 Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs    | 20 +++++-----
 Ryujinx.Cpu/MemoryEhMeilleure.cs              |  2 +-
 Ryujinx.Graphics.Gpu/Image/Pool.cs            |  2 +-
 Ryujinx.Graphics.Gpu/Image/TextureGroup.cs    |  2 +-
 Ryujinx.Graphics.Gpu/Memory/Buffer.cs         |  4 +-
 Ryujinx.Graphics.Gpu/Memory/BufferCache.cs    |  2 +-
 Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs | 37 ++++++++++++++----
 Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs   | 13 +++++++
 .../MockVirtualMemoryManager.cs               |  2 +-
 .../MultiRegionTrackingTests.cs               | 16 ++++----
 Ryujinx.Memory.Tests/TrackingTests.cs         | 24 ++++++------
 Ryujinx.Memory/AddressSpaceManager.cs         |  2 +-
 Ryujinx.Memory/IVirtualMemoryManager.cs       |  3 +-
 Ryujinx.Memory/Tracking/AbstractRegion.cs     |  8 ++--
 Ryujinx.Memory/Tracking/MemoryTracking.cs     | 38 +++++++++++--------
 Ryujinx.Memory/Tracking/MultiRegionHandle.cs  | 14 +++++--
 Ryujinx.Memory/Tracking/RegionHandle.cs       | 24 ++++++++++--
 .../Tracking/SmartMultiRegionHandle.cs        | 12 +++---
 Ryujinx.Memory/Tracking/VirtualRegion.cs      | 16 ++++++--
 Ryujinx.Tests/Memory/MockMemoryManager.cs     |  2 +-
 25 files changed, 188 insertions(+), 107 deletions(-)
 create mode 100644 Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs

diff --git a/ARMeilleure/Memory/IMemoryManager.cs b/ARMeilleure/Memory/IMemoryManager.cs
index c4ea70d17..5eb1fadd6 100644
--- a/ARMeilleure/Memory/IMemoryManager.cs
+++ b/ARMeilleure/Memory/IMemoryManager.cs
@@ -71,6 +71,7 @@ namespace ARMeilleure.Memory
         /// <param name="size">Size of the region</param>
         /// <param name="write">True if the region was written, false if read</param>
         /// <param name="precise">True if the access is precise, false otherwise</param>
-        void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
+        /// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
+        void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
     }
 }
\ No newline at end of file
diff --git a/ARMeilleure/Signal/NativeSignalHandler.cs b/ARMeilleure/Signal/NativeSignalHandler.cs
index 77eabe267..cddeb8174 100644
--- a/ARMeilleure/Signal/NativeSignalHandler.cs
+++ b/ARMeilleure/Signal/NativeSignalHandler.cs
@@ -222,7 +222,7 @@ namespace ARMeilleure.Signal
 
                 // Tracking action should be non-null to call it, otherwise assume false return.
                 context.BranchIfFalse(skipActionLabel, trackingActionPtr);
-                Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite, Const(0));
+                Operand result = context.Call(trackingActionPtr, OperandType.I32, offset, Const(_pageSize), isWrite);
                 context.Copy(inRegionLocal, result);
 
                 context.MarkLabel(skipActionLabel);
diff --git a/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs b/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
index 222dcae1b..437e02aea 100644
--- a/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
+++ b/Ryujinx.Cpu/AppleHv/HvMemoryManager.cs
@@ -634,13 +634,13 @@ namespace Ryujinx.Cpu.AppleHv
         /// <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, bool precise = false)
+        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
         {
             AssertValidAddressAndSize(va, size);
 
             if (precise)
             {
-                Tracking.VirtualMemoryEvent(va, size, write, precise: true);
+                Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
                 return;
             }
 
@@ -663,7 +663,7 @@ namespace Ryujinx.Cpu.AppleHv
 
                 if (state >= tag)
                 {
-                    Tracking.VirtualMemoryEvent(va, size, write);
+                    Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
                     return;
                 }
                 else if (state == 0)
@@ -706,7 +706,7 @@ namespace Ryujinx.Cpu.AppleHv
                         // Only trigger tracking from reads if both bits are set on any page.
                         if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
                         {
-                            Tracking.VirtualMemoryEvent(va, size, write);
+                            Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
                             break;
                         }
                     }
@@ -822,21 +822,21 @@ namespace Ryujinx.Cpu.AppleHv
         }
 
         /// <inheritdoc/>
-        public CpuRegionHandle BeginTracking(ulong address, ulong size)
+        public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
         {
-            return new CpuRegionHandle(Tracking.BeginTracking(address, size));
+            return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
         }
 
         /// <inheritdoc/>
-        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
+        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
         {
-            return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity));
+            return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
         }
 
         /// <inheritdoc/>
-        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
+        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
         {
-            return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
+            return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
         }
 
         /// <summary>
diff --git a/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs b/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs
index 8004d39bc..92d3c76ca 100644
--- a/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs
+++ b/Ryujinx.Cpu/IVirtualMemoryManagerTracked.cs
@@ -28,8 +28,9 @@ namespace Ryujinx.Cpu
         /// </summary>
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        CpuRegionHandle BeginTracking(ulong address, ulong size);
+        CpuRegionHandle BeginTracking(ulong address, ulong size, int id);
 
         /// <summary>
         /// Obtains a memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
@@ -38,8 +39,9 @@ namespace Ryujinx.Cpu
         /// <param name="size">Size of the region</param>
         /// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
         /// <param name="granularity">Desired granularity of write tracking</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity);
+        CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id);
 
         /// <summary>
         /// Obtains a smart memory tracking handle for the given virtual region, with a specified granularity. This should be disposed when finished with.
@@ -47,7 +49,8 @@ namespace Ryujinx.Cpu
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
         /// <param name="granularity">Desired granularity of write tracking</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity);
+        CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id);
     }
 }
diff --git a/Ryujinx.Cpu/Jit/MemoryManager.cs b/Ryujinx.Cpu/Jit/MemoryManager.cs
index 014d843b5..8542d53e2 100644
--- a/Ryujinx.Cpu/Jit/MemoryManager.cs
+++ b/Ryujinx.Cpu/Jit/MemoryManager.cs
@@ -629,31 +629,31 @@ namespace Ryujinx.Cpu.Jit
         }
 
         /// <inheritdoc/>
-        public CpuRegionHandle BeginTracking(ulong address, ulong size)
+        public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
         {
-            return new CpuRegionHandle(Tracking.BeginTracking(address, size));
+            return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
         }
 
         /// <inheritdoc/>
-        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
+        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
         {
-            return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity));
+            return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
         }
 
         /// <inheritdoc/>
-        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
+        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
         {
-            return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
+            return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
         }
 
         /// <inheritdoc/>
-        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
+        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
         {
             AssertValidAddressAndSize(va, size);
 
             if (precise)
             {
-                Tracking.VirtualMemoryEvent(va, size, write, precise: true);
+                Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
                 return;
             }
 
@@ -676,7 +676,7 @@ namespace Ryujinx.Cpu.Jit
 
                 if ((pte & tag) != 0)
                 {
-                    Tracking.VirtualMemoryEvent(va, size, write);
+                    Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
                     break;
                 }
 
diff --git a/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs b/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
index 856b6b9b0..090740abe 100644
--- a/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
+++ b/Ryujinx.Cpu/Jit/MemoryManagerHostMapped.cs
@@ -518,13 +518,13 @@ namespace Ryujinx.Cpu.Jit
         /// <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, bool precise = false)
+        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
         {
             AssertValidAddressAndSize(va, size);
 
             if (precise)
             {
-                Tracking.VirtualMemoryEvent(va, size, write, precise: true);
+                Tracking.VirtualMemoryEvent(va, size, write, precise: true, exemptId);
                 return;
             }
 
@@ -547,7 +547,7 @@ namespace Ryujinx.Cpu.Jit
 
                 if (state >= tag)
                 {
-                    Tracking.VirtualMemoryEvent(va, size, write);
+                    Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
                     return;
                 }
                 else if (state == 0)
@@ -590,7 +590,7 @@ namespace Ryujinx.Cpu.Jit
                         // Only trigger tracking from reads if both bits are set on any page.
                         if (write || (pte & (pte >> 1) & BlockMappedMask) != 0)
                         {
-                            Tracking.VirtualMemoryEvent(va, size, write);
+                            Tracking.VirtualMemoryEvent(va, size, write, precise: false, exemptId);
                             break;
                         }
                     }
@@ -706,21 +706,21 @@ namespace Ryujinx.Cpu.Jit
         }
 
         /// <inheritdoc/>
-        public CpuRegionHandle BeginTracking(ulong address, ulong size)
+        public CpuRegionHandle BeginTracking(ulong address, ulong size, int id)
         {
-            return new CpuRegionHandle(Tracking.BeginTracking(address, size));
+            return new CpuRegionHandle(Tracking.BeginTracking(address, size, id));
         }
 
         /// <inheritdoc/>
-        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
+        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
         {
-            return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity));
+            return new CpuMultiRegionHandle(Tracking.BeginGranularTracking(address, size, handles, granularity, id));
         }
 
         /// <inheritdoc/>
-        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
+        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
         {
-            return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity));
+            return new CpuSmartMultiRegionHandle(Tracking.BeginSmartGranularTracking(address, size, granularity, id));
         }
 
         /// <summary>
diff --git a/Ryujinx.Cpu/MemoryEhMeilleure.cs b/Ryujinx.Cpu/MemoryEhMeilleure.cs
index 806ef8113..0b434ea74 100644
--- a/Ryujinx.Cpu/MemoryEhMeilleure.cs
+++ b/Ryujinx.Cpu/MemoryEhMeilleure.cs
@@ -8,7 +8,7 @@ namespace Ryujinx.Cpu
 {
     public class MemoryEhMeilleure : IDisposable
     {
-        private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write, bool precise = false);
+        private delegate bool TrackingEventDelegate(ulong address, ulong size, bool write);
 
         private readonly MemoryTracking _tracking;
         private readonly TrackingEventDelegate _trackingEvent;
diff --git a/Ryujinx.Graphics.Gpu/Image/Pool.cs b/Ryujinx.Graphics.Gpu/Image/Pool.cs
index ee4c051f4..3e557c0bd 100644
--- a/Ryujinx.Graphics.Gpu/Image/Pool.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Pool.cs
@@ -69,7 +69,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             Address = address;
             Size    = size;
 
-            _memoryTracking = physicalMemory.BeginGranularTracking(address, size);
+            _memoryTracking = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Pool);
             _memoryTracking.RegisterPreciseAction(address, size, PreciseAction);
             _modifiedDelegate = RegionModified;
         }
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
index 1040b394a..12a640e15 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureGroup.cs
@@ -854,7 +854,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <returns>A CpuRegionHandle covering the given range</returns>
         private CpuRegionHandle GenerateHandle(ulong address, ulong size)
         {
-            return _physicalMemory.BeginTracking(address, size);
+            return _physicalMemory.BeginTracking(address, size, ResourceKind.Texture);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
index a624386ed..3778cd824 100644
--- a/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/Buffer.cs
@@ -105,13 +105,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
 
             if (_useGranular)
             {
-                _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, baseHandles);
+                _memoryTrackingGranular = physicalMemory.BeginGranularTracking(address, size, ResourceKind.Buffer, baseHandles);
 
                 _memoryTrackingGranular.RegisterPreciseAction(address, size, PreciseAction);
             }
             else
             {
-                _memoryTracking = physicalMemory.BeginTracking(address, size);
+                _memoryTracking = physicalMemory.BeginTracking(address, size, ResourceKind.Buffer);
 
                 if (baseHandles != null)
                 {
diff --git a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
index 00f590831..a5a9b75e9 100644
--- a/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/BufferCache.cs
@@ -368,7 +368,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
 
             _context.Renderer.Pipeline.ClearBuffer(buffer.Handle, offset, (int)size, value);
 
-            buffer.SignalModified(address, size);
+            memoryManager.Physical.FillTrackedResource(address, size, value, ResourceKind.Buffer);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index c1fc0c5cd..bd33383e5 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -7,6 +7,7 @@ using Ryujinx.Memory.Range;
 using Ryujinx.Memory.Tracking;
 using System;
 using System.Collections.Generic;
+using System.Runtime.InteropServices;
 using System.Threading;
 
 namespace Ryujinx.Graphics.Gpu.Memory
@@ -295,23 +296,41 @@ namespace Ryujinx.Graphics.Gpu.Memory
             }
         }
 
+        /// <summary>
+        /// Fills the specified memory region with a 32-bit integer value.
+        /// </summary>
+        /// <param name="address">CPU virtual address of the region</param>
+        /// <param name="size">Size of the region</param>
+        /// <param name="value">Value to fill the region with</param>
+        /// <param name="kind">Kind of the resource being filled, which will not be signalled as CPU modified</param>
+        public void FillTrackedResource(ulong address, ulong size, uint value, ResourceKind kind)
+        {
+            _cpuMemory.SignalMemoryTracking(address, size, write: true, precise: true, (int)kind);
+
+            using WritableRegion region = _cpuMemory.GetWritableRegion(address, (int)size);
+
+            MemoryMarshal.Cast<byte, uint>(region.Memory.Span).Fill(value);
+        }
+
         /// <summary>
         /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
         /// </summary>
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
+        /// <param name="kind">Kind of the resource being tracked</param>
         /// <returns>The memory tracking handle</returns>
-        public CpuRegionHandle BeginTracking(ulong address, ulong size)
+        public CpuRegionHandle BeginTracking(ulong address, ulong size, ResourceKind kind)
         {
-            return _cpuMemory.BeginTracking(address, size);
+            return _cpuMemory.BeginTracking(address, size, (int)kind);
         }
 
         /// <summary>
         /// Obtains a memory tracking handle for the given virtual region. This should be disposed when finished with.
         /// </summary>
         /// <param name="range">Ranges of physical memory where the data is located</param>
+        /// <param name="kind">Kind of the resource being tracked</param>
         /// <returns>The memory tracking handle</returns>
-        public GpuRegionHandle BeginTracking(MultiRange range)
+        public GpuRegionHandle BeginTracking(MultiRange range, ResourceKind kind)
         {
             var cpuRegionHandles = new CpuRegionHandle[range.Count];
             int count = 0;
@@ -321,7 +340,7 @@ namespace Ryujinx.Graphics.Gpu.Memory
                 var currentRange = range.GetSubRange(i);
                 if (currentRange.Address != MemoryManager.PteUnmapped)
                 {
-                    cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size);
+                    cpuRegionHandles[count++] = _cpuMemory.BeginTracking(currentRange.Address, currentRange.Size, (int)kind);
                 }
             }
 
@@ -338,12 +357,13 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// </summary>
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
+        /// <param name="kind">Kind of the resource being tracked</param>
         /// <param name="handles">Handles to inherit state from or reuse</param>
         /// <param name="granularity">Desired granularity of write tracking</param>
         /// <returns>The memory tracking handle</returns>
-        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
+        public CpuMultiRegionHandle BeginGranularTracking(ulong address, ulong size, ResourceKind kind, IEnumerable<IRegionHandle> handles = null, ulong granularity = 4096)
         {
-            return _cpuMemory.BeginGranularTracking(address, size, handles, granularity);
+            return _cpuMemory.BeginGranularTracking(address, size, handles, granularity, (int)kind);
         }
 
         /// <summary>
@@ -351,11 +371,12 @@ namespace Ryujinx.Graphics.Gpu.Memory
         /// </summary>
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
+        /// <param name="kind">Kind of the resource being tracked</param>
         /// <param name="granularity">Desired granularity of write tracking</param>
         /// <returns>The memory tracking handle</returns>
-        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity = 4096)
+        public CpuSmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ResourceKind kind, ulong granularity = 4096)
         {
-            return _cpuMemory.BeginSmartGranularTracking(address, size, granularity);
+            return _cpuMemory.BeginSmartGranularTracking(address, size, granularity, (int)kind);
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs b/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs
new file mode 100644
index 000000000..55d697b81
--- /dev/null
+++ b/Ryujinx.Graphics.Gpu/Memory/ResourceKind.cs
@@ -0,0 +1,13 @@
+namespace Ryujinx.Graphics.Gpu.Memory
+{
+    /// <summary>
+    /// Kind of a GPU resource.
+    /// </summary>
+    enum ResourceKind
+    {
+        None,
+        Buffer,
+        Texture,
+        Pool
+    }
+}
diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
index 06eb4729e..6729f4a36 100644
--- a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
+++ b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
@@ -96,7 +96,7 @@ namespace Ryujinx.Memory.Tests
             throw new NotImplementedException();
         }
 
-        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
+        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
         {
             throw new NotImplementedException();
         }
diff --git a/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs b/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
index c607464d2..38cb49216 100644
--- a/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
+++ b/Ryujinx.Memory.Tests/MultiRegionTrackingTests.cs
@@ -34,8 +34,8 @@ namespace Ryujinx.Memory.Tests
         private IMultiRegionHandle GetGranular(bool smart, ulong address, ulong size, ulong granularity)
         {
             return smart ?
-                _tracking.BeginSmartGranularTracking(address, size, granularity) :
-                (IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, null, granularity);
+                _tracking.BeginSmartGranularTracking(address, size, granularity, 0) :
+                (IMultiRegionHandle)_tracking.BeginGranularTracking(address, size, null, granularity, 0);
         }
 
         private void RandomOrder(Random random, List<int> indices, Action<int> action)
@@ -216,7 +216,7 @@ namespace Ryujinx.Memory.Tests
             {
                 int region = regionSizes[i];
                 handle.QueryModified(address, (ulong)(PageSize * region), (address, size) => { });
-                
+
                 // There should be a gap between regions,
                 // So that they don't combine and we can see the full effects.
                 address += (ulong)(PageSize * (region + 1));
@@ -294,7 +294,7 @@ namespace Ryujinx.Memory.Tests
 
             bool[] actionsTriggered = new bool[3];
 
-            MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize);
+            MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize, 0);
             PreparePages(granular, 3, PageSize * 3);
 
             // Write to the second handle in the multiregion.
@@ -307,7 +307,7 @@ namespace Ryujinx.Memory.Tests
 
             for (int i = 0; i < 3; i++)
             {
-                singlePages[i] = _tracking.BeginTracking(PageSize * (8 + (ulong)i), PageSize);
+                singlePages[i] = _tracking.BeginTracking(PageSize * (8 + (ulong)i), PageSize, 0);
                 singlePages[i].Reprotect();
             }
 
@@ -321,7 +321,7 @@ namespace Ryujinx.Memory.Tests
 
             for (int i = 0; i < 3; i++)
             {
-                doublePages[i] = _tracking.BeginTracking(PageSize * (11 + (ulong)i * 2), PageSize * 2);
+                doublePages[i] = _tracking.BeginTracking(PageSize * (11 + (ulong)i * 2), PageSize * 2, 0);
                 doublePages[i].Reprotect();
             }
 
@@ -340,7 +340,7 @@ namespace Ryujinx.Memory.Tests
                 doublePages
             };
 
-            MultiRegionHandle combined = _tracking.BeginGranularTracking(0, PageSize * 18, handleGroups.SelectMany((handles) => handles), PageSize);
+            MultiRegionHandle combined = _tracking.BeginGranularTracking(0, PageSize * 18, handleGroups.SelectMany((handles) => handles), PageSize, 0);
 
             bool[] expectedDirty = new bool[]
             {
@@ -405,7 +405,7 @@ namespace Ryujinx.Memory.Tests
         {
             bool actionTriggered = false;
 
-            MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize);
+            MultiRegionHandle granular = _tracking.BeginGranularTracking(PageSize * 3, PageSize * 3, null, PageSize, 0);
             PreparePages(granular, 3, PageSize * 3);
 
             // Add a precise action to the second and third handle in the multiregion.
diff --git a/Ryujinx.Memory.Tests/TrackingTests.cs b/Ryujinx.Memory.Tests/TrackingTests.cs
index b0c39ab04..eb679804c 100644
--- a/Ryujinx.Memory.Tests/TrackingTests.cs
+++ b/Ryujinx.Memory.Tests/TrackingTests.cs
@@ -44,7 +44,7 @@ namespace Ryujinx.Memory.Tests
         [Test]
         public void SingleRegion()
         {
-            RegionHandle handle = _tracking.BeginTracking(0, PageSize);
+            RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
             (ulong address, ulong size)? readTrackingTriggered = null;
             handle.RegisterAction((address, size) =>
             {
@@ -97,7 +97,7 @@ namespace Ryujinx.Memory.Tests
         [Test]
         public void OverlappingRegions()
         {
-            RegionHandle allHandle = _tracking.BeginTracking(0, PageSize * 16);
+            RegionHandle allHandle = _tracking.BeginTracking(0, PageSize * 16, 0);
             allHandle.Reprotect();
 
             (ulong address, ulong size)? readTrackingTriggeredAll = null;
@@ -116,7 +116,7 @@ namespace Ryujinx.Memory.Tests
 
             for (int i = 0; i < 16; i++)
             {
-                containedHandles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize);
+                containedHandles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize, 0);
                 containedHandles[i].Reprotect();
             }
 
@@ -163,7 +163,7 @@ namespace Ryujinx.Memory.Tests
             ulong alignedEnd = ((address + size + PageSize - 1) / PageSize) * PageSize;
             ulong alignedSize = alignedEnd - alignedStart;
 
-            RegionHandle handle = _tracking.BeginTracking(address, size);
+            RegionHandle handle = _tracking.BeginTracking(address, size, 0);
 
             // Anywhere inside the pages the region is contained on should trigger.
 
@@ -207,7 +207,7 @@ namespace Ryujinx.Memory.Tests
 
             for (int i = 0; i < handles.Length; i++)
             {
-                handles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize);
+                handles[i] = _tracking.BeginTracking((ulong)i * PageSize, PageSize, 0);
                 handles[i].Reprotect();
             }
 
@@ -263,7 +263,7 @@ namespace Ryujinx.Memory.Tests
                     Random random = new Random(randSeed + 512);
                     while (Stopwatch.GetTimestamp() < finishedTime)
                     {
-                        RegionHandle handle = _tracking.BeginTracking((ulong)random.Next(maxAddress), (ulong)random.Next(65536));
+                        RegionHandle handle = _tracking.BeginTracking((ulong)random.Next(maxAddress), (ulong)random.Next(65536), 0);
 
                         handle.Dispose();
 
@@ -295,7 +295,7 @@ namespace Ryujinx.Memory.Tests
             // Read actions should only be triggered once for each registration.
             // The implementation should use an interlocked exchange to make sure other threads can't get the action.
 
-            RegionHandle handle = _tracking.BeginTracking(0, PageSize);
+            RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
 
             int triggeredCount = 0;
             int registeredCount = 0;
@@ -359,7 +359,7 @@ namespace Ryujinx.Memory.Tests
         {
             // Ensure that disposed handles correctly remove their virtual and physical regions.
 
-            RegionHandle handle = _tracking.BeginTracking(0, PageSize);
+            RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
             handle.Reprotect();
 
             Assert.AreEqual(1, _tracking.GetRegionCount());
@@ -372,8 +372,8 @@ namespace Ryujinx.Memory.Tests
             // We expect there to be three regions after creating both, one for the small region and two covering the big one around it.
             // Regions are always split to avoid overlapping, which is why there are three instead of two.
 
-            RegionHandle handleSmall = _tracking.BeginTracking(PageSize, PageSize);
-            RegionHandle handleBig = _tracking.BeginTracking(0, PageSize * 4);
+            RegionHandle handleSmall = _tracking.BeginTracking(PageSize, PageSize, 0);
+            RegionHandle handleBig = _tracking.BeginTracking(0, PageSize * 4, 0);
 
             Assert.AreEqual(3, _tracking.GetRegionCount());
 
@@ -398,7 +398,7 @@ namespace Ryujinx.Memory.Tests
                 protection = newProtection;
             };
 
-            RegionHandle handle = _tracking.BeginTracking(0, PageSize);
+            RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
 
             // After creating the handle, there is no protection yet.
             Assert.AreEqual(MemoryPermission.ReadAndWrite, protection);
@@ -453,7 +453,7 @@ namespace Ryujinx.Memory.Tests
         [Test]
         public void PreciseAction()
         {
-            RegionHandle handle = _tracking.BeginTracking(0, PageSize);
+            RegionHandle handle = _tracking.BeginTracking(0, PageSize, 0);
 
             (ulong address, ulong size, bool write)? preciseTriggered = null;
             handle.RegisterPreciseAction((address, size, write) =>
diff --git a/Ryujinx.Memory/AddressSpaceManager.cs b/Ryujinx.Memory/AddressSpaceManager.cs
index b532ce5e0..ac89fca6d 100644
--- a/Ryujinx.Memory/AddressSpaceManager.cs
+++ b/Ryujinx.Memory/AddressSpaceManager.cs
@@ -462,7 +462,7 @@ namespace Ryujinx.Memory
         }
 
         /// <inheritdoc/>
-        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
+        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
         {
             // Only the ARM Memory Manager has tracking for now.
         }
diff --git a/Ryujinx.Memory/IVirtualMemoryManager.cs b/Ryujinx.Memory/IVirtualMemoryManager.cs
index 390371ad2..e1851d48b 100644
--- a/Ryujinx.Memory/IVirtualMemoryManager.cs
+++ b/Ryujinx.Memory/IVirtualMemoryManager.cs
@@ -175,7 +175,8 @@ namespace Ryujinx.Memory
         /// <param name="size">Size of the region</param>
         /// <param name="write">True if the region was written, false if read</param>
         /// <param name="precise">True if the access is precise, false otherwise</param>
-        void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false);
+        /// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
+        void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null);
 
         /// <summary>
         /// Reprotect a region of virtual memory for tracking.
diff --git a/Ryujinx.Memory/Tracking/AbstractRegion.cs b/Ryujinx.Memory/Tracking/AbstractRegion.cs
index a3c3990ea..bd4c8ab5c 100644
--- a/Ryujinx.Memory/Tracking/AbstractRegion.cs
+++ b/Ryujinx.Memory/Tracking/AbstractRegion.cs
@@ -50,7 +50,8 @@ namespace Ryujinx.Memory.Tracking
         /// <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 Signal(ulong address, ulong size, bool write);
+        /// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
+        public abstract void Signal(ulong address, ulong size, bool write, int? exemptId);
 
         /// <summary>
         /// Signals to the handles that a precise memory event has occurred. Assumes that the tracking lock has been obtained.
@@ -58,10 +59,11 @@ namespace Ryujinx.Memory.Tracking
         /// <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);
+        /// <param name="exemptId">Optional ID of the handles that should not be signalled</param>
+        public abstract void SignalPrecise(ulong address, ulong size, bool write, int? exemptId);
 
         /// <summary>
-        /// Split this region into two, around the specified address. 
+        /// 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>
         /// <param name="splitAddress">Address to split the region around</param>
diff --git a/Ryujinx.Memory/Tracking/MemoryTracking.cs b/Ryujinx.Memory/Tracking/MemoryTracking.cs
index 9a35cfb6c..bf1e0ad34 100644
--- a/Ryujinx.Memory/Tracking/MemoryTracking.cs
+++ b/Ryujinx.Memory/Tracking/MemoryTracking.cs
@@ -136,10 +136,11 @@ namespace Ryujinx.Memory.Tracking
         /// <param name="size">Size of the region</param>
         /// <param name="handles">Handles to inherit state from or reuse. When none are present, provide null</param>
         /// <param name="granularity">Desired granularity of write tracking</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
+        public MultiRegionHandle BeginGranularTracking(ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity, int id)
         {
-            return new MultiRegionHandle(this, address, size, handles, granularity);
+            return new MultiRegionHandle(this, address, size, handles, granularity, id);
         }
 
         /// <summary>
@@ -148,12 +149,13 @@ namespace Ryujinx.Memory.Tracking
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
         /// <param name="granularity">Desired granularity of write tracking</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity)
+        public SmartMultiRegionHandle BeginSmartGranularTracking(ulong address, ulong size, ulong granularity, int id)
         {
             (address, size) = PageAlign(address, size);
 
-            return new SmartMultiRegionHandle(this, address, size, granularity);
+            return new SmartMultiRegionHandle(this, address, size, granularity, id);
         }
 
         /// <summary>
@@ -161,14 +163,16 @@ namespace Ryujinx.Memory.Tracking
         /// </summary>
         /// <param name="address">CPU virtual address of the region</param>
         /// <param name="size">Size of the region</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        public RegionHandle BeginTracking(ulong address, ulong size)
+        public RegionHandle BeginTracking(ulong address, ulong size, int id)
         {
             var (paAddress, paSize) = PageAlign(address, size);
 
             lock (TrackingLock)
             {
-                RegionHandle handle = new RegionHandle(this, paAddress, paSize, address, size, _memoryManager.IsRangeMapped(address, size));
+                bool mapped = _memoryManager.IsRangeMapped(address, size);
+                RegionHandle handle = new RegionHandle(this, paAddress, paSize, address, size, id, mapped);
 
                 return handle;
             }
@@ -181,28 +185,31 @@ namespace Ryujinx.Memory.Tracking
         /// <param name="size">Size of the region</param>
         /// <param name="bitmap">The bitmap owning the dirty flag for this handle</param>
         /// <param name="bit">The bit of this handle within the dirty flag</param>
+        /// <param name="id">Handle ID</param>
         /// <returns>The memory tracking handle</returns>
-        internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit)
+        internal RegionHandle BeginTrackingBitmap(ulong address, ulong size, ConcurrentBitmap bitmap, int bit, int id)
         {
             var (paAddress, paSize) = PageAlign(address, size);
 
             lock (TrackingLock)
             {
-                RegionHandle handle = new RegionHandle(this, paAddress, paSize, address, size, bitmap, bit, _memoryManager.IsRangeMapped(address, size));
+                bool mapped = _memoryManager.IsRangeMapped(address, size);
+                RegionHandle handle = new RegionHandle(this, paAddress, paSize, address, size, bitmap, bit, id, mapped);
 
                 return handle;
             }
         }
 
         /// <summary>
-        /// Signal that a virtual memory event happened at the given location (one byte).
+        /// Signal that a virtual memory event happened at the given location.
         /// </summary>
         /// <param name="address">Virtual address accessed</param>
-        /// <param name="write">Whether the address was written to or read</param>
+        /// <param name="size">Size of the region affected in bytes</param>
+        /// <param name="write">Whether the region was written to or read</param>
         /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
-        public bool VirtualMemoryEventTracking(ulong address, bool write)
+        public bool VirtualMemoryEvent(ulong address, ulong size, bool write)
         {
-            return VirtualMemoryEvent(address, 1, write);
+            return VirtualMemoryEvent(address, size, write, precise: false, null);
         }
 
         /// <summary>
@@ -214,8 +221,9 @@ namespace Ryujinx.Memory.Tracking
         /// <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>
+        /// <param name="exemptId">Optional ID that of the handles that should not be signalled</param>
         /// <returns>True if the event triggered any tracking regions, false otherwise</returns>
-        public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise = false)
+        public bool VirtualMemoryEvent(ulong address, ulong size, bool write, bool precise, int? exemptId = null)
         {
             // Look up the virtual region using the region list.
             // Signal up the chain to relevant handles.
@@ -250,11 +258,11 @@ namespace Ryujinx.Memory.Tracking
 
                         if (precise)
                         {
-                            region.SignalPrecise(address, size, write);
+                            region.SignalPrecise(address, size, write, exemptId);
                         }
                         else
                         {
-                            region.Signal(address, size, write);
+                            region.Signal(address, size, write, exemptId);
                         }
                     }
                 }
diff --git a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
index 6ea2b7845..68fc5e759 100644
--- a/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/MultiRegionHandle.cs
@@ -30,7 +30,13 @@ namespace Ryujinx.Memory.Tracking
 
         public bool Dirty { get; private set; } = true;
 
-        internal MultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, IEnumerable<IRegionHandle> handles, ulong granularity)
+        internal MultiRegionHandle(
+            MemoryTracking tracking,
+            ulong address,
+            ulong size,
+            IEnumerable<IRegionHandle> handles,
+            ulong granularity,
+            int id)
         {
             _handles = new RegionHandle[(size + granularity - 1) / granularity];
             Granularity = granularity;
@@ -55,7 +61,7 @@ namespace Ryujinx.Memory.Tracking
                     // Fill any gap left before this handle.
                     while (i < startIndex)
                     {
-                        RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
+                        RegionHandle fillHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id);
                         fillHandle.Parent = this;
                         _handles[i++] = fillHandle;
                     }
@@ -76,7 +82,7 @@ namespace Ryujinx.Memory.Tracking
 
                             while (i < endIndex)
                             {
-                                RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
+                                RegionHandle splitHandle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id);
                                 splitHandle.Parent = this;
 
                                 splitHandle.Reprotect(handle.Dirty);
@@ -99,7 +105,7 @@ namespace Ryujinx.Memory.Tracking
             // Fill any remaining space with new handles.
             while (i < _handles.Length)
             {
-                RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i);
+                RegionHandle handle = tracking.BeginTrackingBitmap(address + (ulong)i * granularity, granularity, _dirtyBitmap, i, id);
                 handle.Parent = this;
                 _handles[i++] = handle;
             }
diff --git a/Ryujinx.Memory/Tracking/RegionHandle.cs b/Ryujinx.Memory/Tracking/RegionHandle.cs
index 580f94a51..7a59f9f25 100644
--- a/Ryujinx.Memory/Tracking/RegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/RegionHandle.cs
@@ -15,12 +15,12 @@ namespace Ryujinx.Memory.Tracking
         /// If more than this number of checks have been performed on a dirty flag since its last reprotect,
         /// then it is dirtied infrequently.
         /// </summary>
-        private static int CheckCountForInfrequent = 3;
+        private const int CheckCountForInfrequent = 3;
 
         /// <summary>
         /// Number of frequent dirty/consume in a row to make this handle volatile.
         /// </summary>
-        private static int VolatileThreshold = 5;
+        private const int VolatileThreshold = 5;
 
         public bool Dirty
         {
@@ -35,6 +35,7 @@ namespace Ryujinx.Memory.Tracking
         }
 
         internal int SequenceNumber { get; set; }
+        internal int Id { get; }
 
         public bool Unmapped { get; private set; }
 
@@ -97,14 +98,26 @@ namespace Ryujinx.Memory.Tracking
         /// <param name="realSize">The real, unaligned size of the handle</param>
         /// <param name="bitmap">The bitmap the dirty flag for this handle is stored in</param>
         /// <param name="bit">The bit index representing the dirty flag for this handle</param>
+        /// <param name="id">Handle ID</param>
         /// <param name="mapped">True if the region handle starts mapped</param>
-        internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, ConcurrentBitmap bitmap, int bit, bool mapped = true)
+        internal RegionHandle(
+            MemoryTracking tracking,
+            ulong address,
+            ulong size,
+            ulong realAddress,
+            ulong realSize,
+            ConcurrentBitmap bitmap,
+            int bit,
+            int id,
+            bool mapped = true)
         {
             Bitmap = bitmap;
             DirtyBit = bit;
 
             Dirty = mapped;
 
+            Id = id;
+
             Unmapped = !mapped;
             Address = address;
             Size = size;
@@ -131,11 +144,14 @@ namespace Ryujinx.Memory.Tracking
         /// <param name="size">Size of the region to track</param>
         /// <param name="realAddress">The real, unaligned address of the handle</param>
         /// <param name="realSize">The real, unaligned size of the handle</param>
+        /// <param name="id">Handle ID</param>
         /// <param name="mapped">True if the region handle starts mapped</param>
-        internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, bool mapped = true)
+        internal RegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong realAddress, ulong realSize, int id, bool mapped = true)
         {
             Bitmap = new ConcurrentBitmap(1, mapped);
 
+            Id = id;
+
             Unmapped = !mapped;
 
             Address = address;
diff --git a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
index 47fe72e5b..4acddefaf 100644
--- a/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
+++ b/Ryujinx.Memory/Tracking/SmartMultiRegionHandle.cs
@@ -18,10 +18,11 @@ namespace Ryujinx.Memory.Tracking
         private readonly ulong _granularity;
         private readonly ulong _size;
         private MemoryTracking _tracking;
+        private readonly int _id;
 
         public bool Dirty { get; private set; } = true;
 
-        internal SmartMultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong granularity)
+        internal SmartMultiRegionHandle(MemoryTracking tracking, ulong address, ulong size, ulong granularity, int id)
         {
             // For this multi-region handle, the handle list starts empty.
             // As regions are queried, they are added to the _handles array at their start index.
@@ -34,6 +35,7 @@ namespace Ryujinx.Memory.Tracking
 
             _address = address;
             _size = size;
+            _id = id;
         }
 
         public void SignalWrite()
@@ -102,7 +104,7 @@ namespace Ryujinx.Memory.Tracking
             RegionSignal signal = handle.PreAction;
             handle.Dispose();
 
-            RegionHandle splitLow = _tracking.BeginTracking(address, size);
+            RegionHandle splitLow = _tracking.BeginTracking(address, size, _id);
             splitLow.Parent = this;
             if (signal != null)
             {
@@ -110,7 +112,7 @@ namespace Ryujinx.Memory.Tracking
             }
             _handles[handleIndex] = splitLow;
 
-            RegionHandle splitHigh = _tracking.BeginTracking(address + size, handle.Size - size);
+            RegionHandle splitHigh = _tracking.BeginTracking(address + size, handle.Size - size, _id);
             splitHigh.Parent = this;
             if (signal != null)
             {
@@ -145,7 +147,7 @@ namespace Ryujinx.Memory.Tracking
                 if (handle != null)
                 {
                     // Fill up to the found handle.
-                    handle = _tracking.BeginTracking(startAddress, HandlesToBytes(i - startHandle));
+                    handle = _tracking.BeginTracking(startAddress, HandlesToBytes(i - startHandle), _id);
                     handle.Parent = this;
                     _handles[startHandle] = handle;
                     return;
@@ -153,7 +155,7 @@ namespace Ryujinx.Memory.Tracking
             }
 
             // Can fill the whole range.
-            _handles[startHandle] = _tracking.BeginTracking(startAddress, HandlesToBytes(1 + lastHandle - startHandle));
+            _handles[startHandle] = _tracking.BeginTracking(startAddress, HandlesToBytes(1 + lastHandle - startHandle), _id);
             _handles[startHandle].Parent = this;
         }
 
diff --git a/Ryujinx.Memory/Tracking/VirtualRegion.cs b/Ryujinx.Memory/Tracking/VirtualRegion.cs
index 57a0344ac..9651426b3 100644
--- a/Ryujinx.Memory/Tracking/VirtualRegion.cs
+++ b/Ryujinx.Memory/Tracking/VirtualRegion.cs
@@ -19,19 +19,24 @@ namespace Ryujinx.Memory.Tracking
             _tracking = tracking;
         }
 
-        public override void Signal(ulong address, ulong size, bool write)
+        /// <inheritdoc/>
+        public override void Signal(ulong address, ulong size, bool write, int? exemptId)
         {
             IList<RegionHandle> handles = Handles;
 
             for (int i = 0; i < handles.Count; i++)
             {
-                handles[i].Signal(address, size, write, ref handles);
+                if (exemptId == null || handles[i].Id != exemptId.Value)
+                {
+                    handles[i].Signal(address, size, write, ref handles);
+                }
             }
 
             UpdateProtection();
         }
 
-        public override void SignalPrecise(ulong address, ulong size, bool write)
+        /// <inheritdoc/>
+        public override void SignalPrecise(ulong address, ulong size, bool write, int? exemptId)
         {
             IList<RegionHandle> handles = Handles;
 
@@ -39,7 +44,10 @@ namespace Ryujinx.Memory.Tracking
 
             for (int i = 0; i < handles.Count; i++)
             {
-                allPrecise &= handles[i].SignalPrecise(address, size, write, ref handles);
+                if (exemptId == null || handles[i].Id != exemptId.Value)
+                {
+                    allPrecise &= handles[i].SignalPrecise(address, size, write, ref handles);
+                }
             }
 
             // Only update protection if a regular signal handler was called.
diff --git a/Ryujinx.Tests/Memory/MockMemoryManager.cs b/Ryujinx.Tests/Memory/MockMemoryManager.cs
index 3f7692636..eeecf419f 100644
--- a/Ryujinx.Tests/Memory/MockMemoryManager.cs
+++ b/Ryujinx.Tests/Memory/MockMemoryManager.cs
@@ -40,7 +40,7 @@ namespace Ryujinx.Tests.Memory
             throw new NotImplementedException();
         }
 
-        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false)
+        public void SignalMemoryTracking(ulong va, ulong size, bool write, bool precise = false, int? exemptId = null)
         {
             throw new NotImplementedException();
         }