diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index 7511bdfad..ba7dce7bf 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -302,15 +302,68 @@ namespace Ryujinx.Graphics.Gpu.Image
 
             _sequenceNumber = _context.SequenceNumber;
 
-            bool modified = _context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.Texture).Length != 0;
+            (ulong, ulong)[] modifiedRanges = _context.PhysicalMemory.GetModifiedRanges(Address, Size, ResourceName.Texture);
 
-            if (!modified && _hasData)
+            if (modifiedRanges.Length == 0 && _hasData)
             {
                 return;
             }
 
             ReadOnlySpan<byte> data = _context.PhysicalMemory.GetSpan(Address, Size);
 
+            // If the texture was modified by the host GPU, we do partial invalidation
+            // of the texture by getting GPU data and merging in the pages of memory
+            // that were modified.
+            // Note that if ASTC is not supported by the GPU we can't read it back since
+            // it will use a different format. Since applications shouldn't be writing
+            // ASTC textures from the GPU anyway, ignoring it should be safe.
+            if (_context.Methods.TextureManager.IsTextureModified(this) && !Info.FormatInfo.Format.IsAstc())
+            {
+                Span<byte> gpuData = GetTextureDataFromGpu();
+
+                ulong endAddress = Address + Size;
+
+                for (int i = 0; i < modifiedRanges.Length; i++)
+                {
+                    (ulong modifiedAddress, ulong modifiedSize) = modifiedRanges[i];
+
+                    ulong endModifiedAddress = modifiedAddress + modifiedSize;
+
+                    if (modifiedAddress < Address)
+                    {
+                        modifiedAddress = Address;
+                    }
+
+                    if (endModifiedAddress > endAddress)
+                    {
+                        endModifiedAddress = endAddress;
+                    }
+
+                    modifiedSize = endModifiedAddress - modifiedAddress;
+
+                    int offset = (int)(modifiedAddress - Address);
+                    int length = (int)modifiedSize;
+
+                    data.Slice(offset, length).CopyTo(gpuData.Slice(offset, length));
+                }
+
+                data = gpuData;
+            }
+
+            data = ConvertToHostCompatibleFormat(data);
+
+            HostTexture.SetData(data);
+
+            _hasData = true;
+        }
+
+        /// <summary>
+        /// Converts texture data to a format and layout that is supported by the host GPU.
+        /// </summary>
+        /// <param name="data">Data to be converted</param>
+        /// <returns>Converted data</returns>
+        private ReadOnlySpan<byte> ConvertToHostCompatibleFormat(ReadOnlySpan<byte> data)
+        {
             if (Info.IsLinear)
             {
                 data = LayoutConverter.ConvertLinearStridedToLinear(
@@ -360,9 +413,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                 data = decoded;
             }
 
-            HostTexture.SetData(data);
-
-            _hasData = true;
+            return data;
         }
 
         /// <summary>
@@ -374,6 +425,19 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// This may cause data corruption if the memory is already being used for something else on the CPU side.
         /// </summary>
         public void Flush()
+        {
+            _context.PhysicalMemory.Write(Address, GetTextureDataFromGpu());
+        }
+
+        /// <summary>
+        /// Gets data from the host GPU.
+        /// </summary>
+        /// <remarks>
+        /// This method should be used to retrieve data that was modified by the host GPU.
+        /// This is not cheap, avoid doing that unless strictly needed.
+        /// </remarks>
+        /// <returns>Host texture data</returns>
+        private Span<byte> GetTextureDataFromGpu()
         {
             Span<byte> data = HostTexture.GetData();
 
@@ -406,7 +470,7 @@ namespace Ryujinx.Graphics.Gpu.Image
                     data);
             }
 
-            _context.PhysicalMemory.Write(Address, data);
+            return data;
         }
 
         /// <summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
index 87fb4161e..27cbc6ff8 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureManager.cs
@@ -37,6 +37,7 @@ namespace Ryujinx.Graphics.Gpu.Image
         private readonly AutoDeleteCache _cache;
 
         private readonly HashSet<Texture> _modified;
+        private readonly HashSet<Texture> _modifiedLinear;
 
         /// <summary>
         /// Constructs a new instance of the texture manager.
@@ -62,6 +63,7 @@ namespace Ryujinx.Graphics.Gpu.Image
             _cache = new AutoDeleteCache();
 
             _modified = new HashSet<Texture>(new ReferenceEqualityComparer<Texture>());
+            _modifiedLinear = new HashSet<Texture>(new ReferenceEqualityComparer<Texture>());
         }
 
         /// <summary>
@@ -519,6 +521,11 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                     texture = overlap.CreateView(info, sizeInfo, firstLayer, firstLevel);
 
+                    if (IsTextureModified(overlap))
+                    {
+                        CacheTextureModified(texture);
+                    }
+
                     // The size only matters (and is only really reliable) when the
                     // texture is used on a sampler, because otherwise the size will be
                     // aligned.
@@ -554,6 +561,13 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                         overlap.HostTexture.CopyTo(newView, 0, 0);
 
+                        // Inherit modification from overlapping texture, do that before replacing
+                        // the view since the replacement operation removes it from the list.
+                        if (IsTextureModified(overlap))
+                        {
+                            CacheTextureModified(texture);
+                        }
+
                         overlap.ReplaceView(texture, overlapInfo, newView);
                     }
                 }
@@ -574,6 +588,11 @@ namespace Ryujinx.Graphics.Gpu.Image
                             out int firstLevel))
                         {
                             overlap.HostTexture.CopyTo(texture.HostTexture, firstLayer, firstLevel);
+
+                            if (IsTextureModified(overlap))
+                            {
+                                CacheTextureModified(texture);
+                            }
                         }
                     }
                 }
@@ -595,6 +614,16 @@ namespace Ryujinx.Graphics.Gpu.Image
             return texture;
         }
 
+        /// <summary>
+        /// Checks if a texture was modified by the host GPU.
+        /// </summary>
+        /// <param name="texture">Texture to be checked</param>
+        /// <returns>True if the texture was modified by the host GPU, false otherwise</returns>
+        public bool IsTextureModified(Texture texture)
+        {
+            return _modified.Contains(texture);
+        }
+
         /// <summary>
         /// Signaled when a cache texture is modified, and adds it to a set to be enumerated when flushing textures.
         /// </summary>
@@ -602,6 +631,11 @@ namespace Ryujinx.Graphics.Gpu.Image
         private void CacheTextureModified(Texture texture)
         {
             _modified.Add(texture);
+
+            if (texture.Info.IsLinear)
+            {
+                _modifiedLinear.Add(texture);
+            }
         }
 
         /// <summary>
@@ -611,6 +645,11 @@ namespace Ryujinx.Graphics.Gpu.Image
         private void CacheTextureDisposed(Texture texture)
         {
             _modified.Remove(texture);
+
+            if (texture.Info.IsLinear)
+            {
+                _modifiedLinear.Remove(texture);
+            }
         }
 
         /// <summary>
@@ -747,14 +786,12 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// </summary>
         public void Flush()
         {
-            foreach (Texture texture in _modified)
+            foreach (Texture texture in _modifiedLinear)
             {
-                if (texture.Info.IsLinear)
-                {
-                    texture.Flush();
-                }
+                texture.Flush();
             }
-            _modified.Clear();
+
+            _modifiedLinear.Clear();
         }
 
         /// <summary>
@@ -771,7 +808,6 @@ namespace Ryujinx.Graphics.Gpu.Image
                     texture.Flush();
                 }
             }
-            _modified.Clear();
         }
 
         /// <summary>