diff --git a/Ryujinx.Graphics.GAL/ITexture.cs b/Ryujinx.Graphics.GAL/ITexture.cs
index 96b592937c..7f46806c7a 100644
--- a/Ryujinx.Graphics.GAL/ITexture.cs
+++ b/Ryujinx.Graphics.GAL/ITexture.cs
@@ -19,6 +19,7 @@ namespace Ryujinx.Graphics.GAL
 
         void SetData(ReadOnlySpan<byte> data);
         void SetData(ReadOnlySpan<byte> data, int layer, int level);
+        void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region);
         void SetStorage(BufferRange buffer);
         void Release();
     }
diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
index ea4d049ffd..8080ab6498 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/CommandHelper.cs
@@ -113,6 +113,8 @@ namespace Ryujinx.Graphics.GAL.Multithreading
                 TextureSetDataCommand.Run(ref GetCommand<TextureSetDataCommand>(memory), threaded, renderer);
             _lookup[(int)CommandType.TextureSetDataSlice] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
                 TextureSetDataSliceCommand.Run(ref GetCommand<TextureSetDataSliceCommand>(memory), threaded, renderer);
+            _lookup[(int)CommandType.TextureSetDataSliceRegion] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
+                TextureSetDataSliceRegionCommand.Run(ref GetCommand<TextureSetDataSliceRegionCommand>(memory), threaded, renderer);
             _lookup[(int)CommandType.TextureSetStorage] = (Span<byte> memory, ThreadedRenderer threaded, IRenderer renderer) =>
                 TextureSetStorageCommand.Run(ref GetCommand<TextureSetStorageCommand>(memory), threaded, renderer);
 
diff --git a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
index 8c3ad84461..c25f0834ec 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/CommandType.cs
@@ -37,6 +37,7 @@
         TextureRelease,
         TextureSetData,
         TextureSetDataSlice,
+        TextureSetDataSliceRegion,
         TextureSetStorage,
 
         WindowPresent,
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs
new file mode 100644
index 0000000000..b4285592fd
--- /dev/null
+++ b/Ryujinx.Graphics.GAL/Multithreading/Commands/Texture/TextureSetDataSliceRegionCommand.cs
@@ -0,0 +1,31 @@
+using Ryujinx.Graphics.GAL.Multithreading.Model;
+using Ryujinx.Graphics.GAL.Multithreading.Resources;
+using System;
+
+namespace Ryujinx.Graphics.GAL.Multithreading.Commands.Texture
+{
+    struct TextureSetDataSliceRegionCommand : IGALCommand
+    {
+        public CommandType CommandType => CommandType.TextureSetDataSliceRegion;
+        private TableRef<ThreadedTexture> _texture;
+        private TableRef<byte[]> _data;
+        private int _layer;
+        private int _level;
+        private Rectangle<int> _region;
+
+        public void Set(TableRef<ThreadedTexture> texture, TableRef<byte[]> data, int layer, int level, Rectangle<int> region)
+        {
+            _texture = texture;
+            _data = data;
+            _layer = layer;
+            _level = level;
+            _region = region;
+        }
+
+        public static void Run(ref TextureSetDataSliceRegionCommand command, ThreadedRenderer threaded, IRenderer renderer)
+        {
+            ThreadedTexture texture = command._texture.Get(threaded);
+            texture.Base.SetData(new ReadOnlySpan<byte>(command._data.Get(threaded)), command._layer, command._level, command._region);
+        }
+    }
+}
diff --git a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs
index 64d8aa3bd6..1e7d86ba33 100644
--- a/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs
+++ b/Ryujinx.Graphics.GAL/Multithreading/Resources/ThreadedTexture.cs
@@ -119,6 +119,12 @@ namespace Ryujinx.Graphics.GAL.Multithreading.Resources
             _renderer.QueueCommand();
         }
 
+        public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
+        {
+            _renderer.New<TextureSetDataSliceRegionCommand>().Set(Ref(this), Ref(data.ToArray()), layer, level, region);
+            _renderer.QueueCommand();
+        }
+
         public void SetStorage(BufferRange buffer)
         {
             _renderer.New<TextureSetStorageCommand>().Set(Ref(this), buffer);
diff --git a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
index 5814eeb706..da25a89d84 100644
--- a/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/Dma/DmaClass.cs
@@ -224,7 +224,8 @@ namespace Ryujinx.Graphics.Gpu.Engine.Dma
                         xCount,
                         yCount,
                         dstLinear,
-                        dst.MemoryLayout);
+                        dst.MemoryLayout.UnpackGobBlocksInY(),
+                        dst.MemoryLayout.UnpackGobBlocksInZ());
 
                     if (target != null)
                     {
diff --git a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
index c093905797..6120d29503 100644
--- a/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
+++ b/Ryujinx.Graphics.Gpu/Engine/InlineToMemory/InlineToMemoryClass.cs
@@ -29,6 +29,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
         private int _dstHeight;
         private int _dstStride;
         private int _dstGobBlocksInY;
+        private int _dstGobBlocksInZ;
         private int _lineLengthIn;
         private int _lineCount;
 
@@ -117,6 +118,7 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
             _dstHeight = (int)state.SetDstHeight;
             _dstStride = (int)state.PitchOut;
             _dstGobBlocksInY = 1 << (int)state.SetDstBlockSizeHeight;
+            _dstGobBlocksInZ = 1 << (int)state.SetDstBlockSizeDepth;
             _lineLengthIn = (int)state.LineLengthIn;
             _lineCount = (int)state.LineCount;
 
@@ -176,6 +178,31 @@ namespace Ryujinx.Graphics.Gpu.Engine.InlineToMemory
             }
             else
             {
+                // TODO: Verify if the destination X/Y and width/height are taken into account
+                // for linear texture transfers. If not, we can use the fast path for that aswell.
+                // Right now the copy code at the bottom assumes that it is used on both which might be incorrect.
+                if (!_isLinear)
+                {
+                    var target = memoryManager.Physical.TextureCache.FindTexture(
+                        memoryManager,
+                        _dstGpuVa,
+                        1,
+                        _dstStride,
+                        _dstHeight,
+                        _lineLengthIn,
+                        _lineCount,
+                        _isLinear,
+                        _dstGobBlocksInY,
+                        _dstGobBlocksInZ);
+
+                    if (target != null)
+                    {
+                        target.SetData(data, 0, 0, new GAL.Rectangle<int>(_dstX, _dstY, _lineLengthIn / target.Info.FormatInfo.BytesPerPixel, _lineCount));
+
+                        return;
+                    }
+                }
+
                 var dstCalculator = new OffsetCalculator(
                     _dstWidth,
                     _dstHeight,
diff --git a/Ryujinx.Graphics.Gpu/Image/Texture.cs b/Ryujinx.Graphics.Gpu/Image/Texture.cs
index a598f212e9..320bc01490 100644
--- a/Ryujinx.Graphics.Gpu/Image/Texture.cs
+++ b/Ryujinx.Graphics.Gpu/Image/Texture.cs
@@ -761,6 +761,24 @@ namespace Ryujinx.Graphics.Gpu.Image
             _hasData = true;
         }
 
+        /// <summary>
+        /// Uploads new texture data to the host GPU for a specific layer/level and 2D sub-region.
+        /// </summary>
+        /// <param name="data">New data</param>
+        /// <param name="layer">Target layer</param>
+        /// <param name="level">Target level</param>
+        /// <param name="region">Target sub-region of the texture to update</param>
+        public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
+        {
+            BlacklistScale();
+
+            HostTexture.SetData(data, layer, level, region);
+
+            _currentData = null;
+
+            _hasData = true;
+        }
+
         /// <summary>
         /// Converts texture data to a format and layout that is supported by the host GPU.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
index dcac9f643b..d76879eb4a 100644
--- a/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
+++ b/Ryujinx.Graphics.Gpu/Image/TextureCache.cs
@@ -905,7 +905,8 @@ namespace Ryujinx.Graphics.Gpu.Image
         /// <param name="xCount">Number of pixels to be copied per line</param>
         /// <param name="yCount">Number of lines to be copied</param>
         /// <param name="linear">True if the texture has a linear layout, false otherwise</param>
-        /// <param name="memoryLayout">If <paramref name="linear"/> is false, should have the memory layout, otherwise ignored</param>
+        /// <param name="gobBlocksInY">If <paramref name="linear"/> is false, the amount of GOB blocks in the Y axis</param>
+        /// <param name="gobBlocksInZ">If <paramref name="linear"/> is false, the amount of GOB blocks in the Z axis</param>
         /// <returns>A matching texture, or null if there is no match</returns>
         public Texture FindTexture(
             MemoryManager memoryManager,
@@ -916,7 +917,8 @@ namespace Ryujinx.Graphics.Gpu.Image
             int xCount,
             int yCount,
             bool linear,
-            MemoryLayout memoryLayout)
+            int gobBlocksInY,
+            int gobBlocksInZ)
         {
             ulong address = memoryManager.Translate(gpuVa);
 
@@ -955,8 +957,8 @@ namespace Ryujinx.Graphics.Gpu.Image
 
                     bool sizeMatch = xCount * bpp == texture.Info.Width * format.BytesPerPixel && height == texture.Info.Height;
                     bool formatMatch = !texture.Info.IsLinear &&
-                                        texture.Info.GobBlocksInY == memoryLayout.UnpackGobBlocksInY() &&
-                                        texture.Info.GobBlocksInZ == memoryLayout.UnpackGobBlocksInZ();
+                                        texture.Info.GobBlocksInY == gobBlocksInY &&
+                                        texture.Info.GobBlocksInZ == gobBlocksInZ;
 
                     match = sizeMatch && formatMatch;
                 }
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
index e5b39aa643..e46d5c48e2 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureBuffer.cs
@@ -58,6 +58,11 @@ namespace Ryujinx.Graphics.OpenGL.Image
             throw new NotSupportedException();
         }
 
+        public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
+        {
+            throw new NotSupportedException();
+        }
+
         public void SetStorage(BufferRange buffer)
         {
             if (_buffer != BufferHandle.Null &&
diff --git a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
index 8f9e2a6627..243ca1b3e9 100644
--- a/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
+++ b/Ryujinx.Graphics.OpenGL/Image/TextureView.cs
@@ -1,4 +1,5 @@
 using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common;
 using Ryujinx.Graphics.GAL;
 using System;
 
@@ -385,7 +386,34 @@ namespace Ryujinx.Graphics.OpenGL.Image
                     int width = Math.Max(Info.Width >> level, 1);
                     int height = Math.Max(Info.Height >> level, 1);
 
-                    ReadFrom2D((IntPtr)ptr, layer, level, width, height);
+                    ReadFrom2D((IntPtr)ptr, layer, level, 0, 0, width, height);
+                }
+            }
+        }
+
+        public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
+        {
+            if (Format == Format.S8UintD24Unorm)
+            {
+                data = FormatConverter.ConvertS8D24ToD24S8(data);
+            }
+
+            int wInBlocks = BitUtils.DivRoundUp(region.Width, Info.BlockWidth);
+            int hInBlocks = BitUtils.DivRoundUp(region.Height, Info.BlockHeight);
+
+            unsafe
+            {
+                fixed (byte* ptr = data)
+                {
+                    ReadFrom2D(
+                        (IntPtr)ptr,
+                        layer,
+                        level,
+                        region.X,
+                        region.Y,
+                        region.Width,
+                        region.Height,
+                        BitUtils.AlignUp(wInBlocks * Info.BytesPerPixel, 4) * hInBlocks);
                 }
             }
         }
@@ -397,15 +425,20 @@ namespace Ryujinx.Graphics.OpenGL.Image
 
         public void ReadFromPbo2D(int offset, int layer, int level, int width, int height)
         {
-            ReadFrom2D(IntPtr.Zero + offset, layer, level, width, height);
+            ReadFrom2D(IntPtr.Zero + offset, layer, level, 0, 0, width, height);
         }
 
-        private void ReadFrom2D(IntPtr data, int layer, int level, int width, int height)
+        private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height)
+        {
+            int mipSize = Info.GetMipSize2D(level);
+
+            ReadFrom2D(data, layer, level, x, y, width, height, mipSize);
+        }
+
+        private void ReadFrom2D(IntPtr data, int layer, int level, int x, int y, int width, int height, int mipSize)
         {
             TextureTarget target = Target.Convert();
 
-            int mipSize = Info.GetMipSize2D(level);
-
             Bind(target, 0);
 
             FormatInfo format = FormatTable.GetFormatInfo(Info.Format);
@@ -418,7 +451,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.CompressedTexSubImage1D(
                             target,
                             level,
-                            0,
+                            x,
                             width,
                             format.PixelFormat,
                             mipSize,
@@ -429,7 +462,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.TexSubImage1D(
                             target,
                             level,
-                            0,
+                            x,
                             width,
                             format.PixelFormat,
                             format.PixelType,
@@ -443,7 +476,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.CompressedTexSubImage2D(
                             target,
                             level,
-                            0,
+                            x,
                             layer,
                             width,
                             1,
@@ -456,7 +489,7 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.TexSubImage2D(
                             target,
                             level,
-                            0,
+                            x,
                             layer,
                             width,
                             1,
@@ -472,8 +505,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.CompressedTexSubImage2D(
                             target,
                             level,
-                            0,
-                            0,
+                            x,
+                            y,
                             width,
                             height,
                             format.PixelFormat,
@@ -485,8 +518,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.TexSubImage2D(
                             target,
                             level,
-                            0,
-                            0,
+                            x,
+                            y,
                             width,
                             height,
                             format.PixelFormat,
@@ -503,8 +536,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.CompressedTexSubImage3D(
                             target,
                             level,
-                            0,
-                            0,
+                            x,
+                            y,
                             layer,
                             width,
                             height,
@@ -518,8 +551,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.TexSubImage3D(
                             target,
                             level,
-                            0,
-                            0,
+                            x,
+                            y,
                             layer,
                             width,
                             height,
@@ -536,8 +569,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.CompressedTexSubImage2D(
                             TextureTarget.TextureCubeMapPositiveX + layer,
                             level,
-                            0,
-                            0,
+                            x,
+                            y,
                             width,
                             height,
                             format.PixelFormat,
@@ -549,8 +582,8 @@ namespace Ryujinx.Graphics.OpenGL.Image
                         GL.TexSubImage2D(
                             TextureTarget.TextureCubeMapPositiveX + layer,
                             level,
-                            0,
-                            0,
+                            x,
+                            y,
                             width,
                             height,
                             format.PixelFormat,
diff --git a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
index c74956100b..3e90a96f5d 100644
--- a/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
+++ b/Ryujinx.Graphics.Vulkan/TextureBuffer.cs
@@ -98,6 +98,11 @@ namespace Ryujinx.Graphics.Vulkan
             throw new NotSupportedException();
         }
 
+        public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
+        {
+            throw new NotSupportedException();
+        }
+
         public void SetStorage(BufferRange buffer)
         {
             if (_bufferHandle == buffer.Handle &&
diff --git a/Ryujinx.Graphics.Vulkan/TextureView.cs b/Ryujinx.Graphics.Vulkan/TextureView.cs
index d94587bd6b..2eeafae9e4 100644
--- a/Ryujinx.Graphics.Vulkan/TextureView.cs
+++ b/Ryujinx.Graphics.Vulkan/TextureView.cs
@@ -819,7 +819,7 @@ namespace Ryujinx.Graphics.Vulkan
                 var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
                 var image = GetImage().Get(cbs).Value;
 
-                CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, x, y, width, height);
+                CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, size, true, 0, 0, x, y, width, height);
             }
 
             bufferHolder.WaitForFences();
@@ -893,7 +893,12 @@ namespace Ryujinx.Graphics.Vulkan
             SetData(data, layer, level, 1, 1, singleSlice: true);
         }
 
-        private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice)
+        public void SetData(ReadOnlySpan<byte> data, int layer, int level, Rectangle<int> region)
+        {
+            SetData(data, layer, level, 1, 1, singleSlice: true, region);
+        }
+
+        private void SetData(ReadOnlySpan<byte> data, int layer, int level, int layers, int levels, bool singleSlice, Rectangle<int>? region = null)
         {
             int bufferDataLength = GetBufferDataLength(data.Length);
 
@@ -917,7 +922,25 @@ namespace Ryujinx.Graphics.Vulkan
             var buffer = bufferHolder.GetBuffer(cbs.CommandBuffer).Get(cbs).Value;
             var image = imageAuto.Get(cbs).Value;
 
-            CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice);
+            if (region.HasValue)
+            {
+                CopyFromOrToBuffer(
+                    cbs.CommandBuffer,
+                    buffer,
+                    image,
+                    bufferDataLength,
+                    false,
+                    layer,
+                    level,
+                    region.Value.X,
+                    region.Value.Y,
+                    region.Value.Width,
+                    region.Value.Height);
+            }
+            else
+            {
+                CopyFromOrToBuffer(cbs.CommandBuffer, buffer, image, bufferDataLength, false, layer, level, layers, levels, singleSlice);
+            }
         }
 
         private int GetBufferDataLength(int length)
@@ -1059,6 +1082,8 @@ namespace Ryujinx.Graphics.Vulkan
             Image image,
             int size,
             bool to,
+            int dstLayer,
+            int dstLevel,
             int x,
             int y,
             int width,
@@ -1071,13 +1096,21 @@ namespace Ryujinx.Graphics.Vulkan
                 aspectFlags = ImageAspectFlags.ImageAspectDepthBit;
             }
 
-            var sl = new ImageSubresourceLayers(aspectFlags, (uint)FirstLevel, (uint)FirstLayer, 1);
+            var sl = new ImageSubresourceLayers(aspectFlags, (uint)(FirstLevel + dstLevel), (uint)(FirstLayer + dstLayer), 1);
 
             var extent = new Extent3D((uint)width, (uint)height, 1);
 
+            int rowLengthAlignment = Info.BlockWidth;
+
+            // We expect all data being written into the texture to have a stride aligned by 4.
+            if (!to && Info.BytesPerPixel < 4)
+            {
+                rowLengthAlignment = 4 / Info.BytesPerPixel;
+            }
+
             var region = new BufferImageCopy(
                 0,
-                (uint)AlignUpNpot(width, Info.BlockWidth),
+                (uint)AlignUpNpot(width, rowLengthAlignment),
                 (uint)AlignUpNpot(height, Info.BlockHeight),
                 sl,
                 new Offset3D(x, y, 0),