diff --git a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
index c57f1a6f17..ae27c71293 100644
--- a/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/MemoryManager.cs
@@ -255,6 +255,49 @@ namespace Ryujinx.Graphics.Gpu.Memory
             }
         }
 
+        /// <summary>
+        /// Writes data to GPU mapped memory, stopping at the first unmapped page at the memory region, if any.
+        /// </summary>
+        /// <param name="va">GPU virtual address to write the data into</param>
+        /// <param name="data">The data to be written</param>
+        public void WriteMapped(ulong va, ReadOnlySpan<byte> data)
+        {
+            if (IsContiguous(va, data.Length))
+            {
+                Physical.Write(Translate(va), data);
+            }
+            else
+            {
+                int offset = 0, size;
+
+                if ((va & PageMask) != 0)
+                {
+                    ulong pa = Translate(va);
+
+                    size = Math.Min(data.Length, (int)PageSize - (int)(va & PageMask));
+
+                    if (pa != PteUnmapped && Physical.IsMapped(pa))
+                    {
+                        Physical.Write(pa, data.Slice(0, size));
+                    }
+
+                    offset += size;
+                }
+
+                for (; offset < data.Length; offset += size)
+                {
+                    ulong pa = Translate(va + (ulong)offset);
+
+                    size = Math.Min(data.Length - offset, (int)PageSize);
+
+                    if (pa != PteUnmapped && Physical.IsMapped(pa))
+                    {
+                        Physical.Write(pa, data.Slice(offset, size));
+                    }
+                }
+            }
+        }
+
         /// <summary>
         /// Maps a given range of pages to the specified CPU virtual address.
         /// </summary>
diff --git a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
index bfd3c04fa8..57590fb38b 100644
--- a/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
+++ b/Ryujinx.Graphics.Gpu/Memory/PhysicalMemory.cs
@@ -340,6 +340,16 @@ namespace Ryujinx.Graphics.Gpu.Memory
             return _cpuMemory.BeginSmartGranularTracking(address, size, granularity);
         }
 
+        /// <summary>
+        /// Checks if the page at a given address is mapped on CPU memory.
+        /// </summary>
+        /// <param name="address">CPU virtual address of the page to check</param>
+        /// <returns>True if mapped, false otherwise</returns>
+        public bool IsMapped(ulong address)
+        {
+            return _cpuMemory.IsMapped(address);
+        }
+
         /// <summary>
         /// Release our reference to the CPU memory manager.
         /// </summary>
diff --git a/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs b/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs
index 20cee4a180..13d0df498d 100644
--- a/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs
+++ b/Ryujinx.Graphics.Nvdec.FFmpeg/Surface.cs
@@ -15,11 +15,13 @@ namespace Ryujinx.Graphics.Nvdec.FFmpeg
         public Plane UPlane => new Plane((IntPtr)Frame->data[1], UvStride * UvHeight);
         public Plane VPlane => new Plane((IntPtr)Frame->data[2], UvStride * UvHeight);
 
+        public FrameField Field => Frame->interlaced_frame != 0 ? FrameField.Interlaced : FrameField.Progressive;
+
         public int Width => Frame->width;
         public int Height => Frame->height;
         public int Stride => Frame->linesize[0];
-        public int UvWidth => (Frame->width + 1) >> 1;
-        public int UvHeight => (Frame->height + 1) >> 1;
+        public int UvWidth => (Width + 1) >> 1;
+        public int UvHeight => (Height + 1) >> 1;
         public int UvStride => Frame->linesize[1];
 
         public Surface(int width, int height)
diff --git a/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs b/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs
index e41a31cac1..873f667ac7 100644
--- a/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs
+++ b/Ryujinx.Graphics.Nvdec.Vp9/Dsp/InvTxfm.cs
@@ -486,8 +486,7 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Dsp
                 Idct8(tempIn, tempOut);
                 for (j = 0; j < 8; ++j)
                 {
-                    dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i],
-                                                          BitUtils.RoundPowerOfTwo(tempOut[j], 5));
+                    dest[j * stride + i] = ClipPixelAdd(dest[j * stride + i], BitUtils.RoundPowerOfTwo(tempOut[j], 5));
                 }
             }
         }
diff --git a/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs b/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs
index 2b2a173eb8..d5b51bc2fa 100644
--- a/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs
+++ b/Ryujinx.Graphics.Nvdec.Vp9/Types/Surface.cs
@@ -15,6 +15,8 @@ namespace Ryujinx.Graphics.Nvdec.Vp9.Types
         public unsafe Plane UPlane => new Plane((IntPtr)UBuffer.ToPointer(), UBuffer.Length);
         public unsafe Plane VPlane => new Plane((IntPtr)VBuffer.ToPointer(), VBuffer.Length);
 
+        public FrameField Field => FrameField.Progressive;
+
         public int Width { get; }
         public int Height { get; }
         public int AlignedWidth { get; }
diff --git a/Ryujinx.Graphics.Nvdec/H264Decoder.cs b/Ryujinx.Graphics.Nvdec/H264Decoder.cs
index 69eeb4949d..6efeb899ec 100644
--- a/Ryujinx.Graphics.Nvdec/H264Decoder.cs
+++ b/Ryujinx.Graphics.Nvdec/H264Decoder.cs
@@ -31,7 +31,24 @@ namespace Ryujinx.Graphics.Nvdec
 
             if (decoder.Decode(ref info, outputSurface, bitstream))
             {
-                SurfaceWriter.Write(rm.Gmm, outputSurface, lumaOffset, chromaOffset);
+                if (outputSurface.Field == FrameField.Progressive)
+                {
+                    SurfaceWriter.Write(
+                        rm.Gmm,
+                        outputSurface,
+                        lumaOffset   + pictureInfo.LumaFrameOffset,
+                        chromaOffset + pictureInfo.ChromaFrameOffset);
+                }
+                else
+                {
+                    SurfaceWriter.WriteInterlaced(
+                        rm.Gmm,
+                        outputSurface,
+                        lumaOffset   + pictureInfo.LumaTopFieldOffset,
+                        chromaOffset + pictureInfo.ChromaTopFieldOffset,
+                        lumaOffset   + pictureInfo.LumaBottomFieldOffset,
+                        chromaOffset + pictureInfo.ChromaBottomFieldOffset);
+                }
             }
 
             rm.Cache.Put(outputSurface);
diff --git a/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs b/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs
index 5c294621a7..cc5c251bec 100644
--- a/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs
+++ b/Ryujinx.Graphics.Nvdec/Image/SurfaceWriter.cs
@@ -38,6 +38,55 @@ namespace Ryujinx.Graphics.Nvdec.Image
                 surface.UvHeight);
         }
 
+        public static void WriteInterlaced(
+            MemoryManager gmm,
+            ISurface surface,
+            uint lumaTopOffset,
+            uint chromaTopOffset,
+            uint lumaBottomOffset,
+            uint chromaBottomOffset)
+        {
+            int lumaSize = GetBlockLinearSize(surface.Width, surface.Height / 2, 1);
+
+            using var lumaTop = gmm.GetWritableRegion(ExtendOffset(lumaTopOffset), lumaSize);
+            using var lumaBottom = gmm.GetWritableRegion(ExtendOffset(lumaBottomOffset), lumaSize);
+
+            WriteLuma(
+                lumaTop.Memory.Span,
+                surface.YPlane.AsSpan(),
+                surface.Stride * 2,
+                surface.Width,
+                surface.Height / 2);
+
+            WriteLuma(
+                lumaBottom.Memory.Span,
+                surface.YPlane.AsSpan().Slice(surface.Stride),
+                surface.Stride * 2,
+                surface.Width,
+                surface.Height / 2);
+
+            int chromaSize = GetBlockLinearSize(surface.UvWidth, surface.UvHeight / 2, 2);
+
+            using var chromaTop = gmm.GetWritableRegion(ExtendOffset(chromaTopOffset), chromaSize);
+            using var chromaBottom = gmm.GetWritableRegion(ExtendOffset(chromaBottomOffset), chromaSize);
+
+            WriteChroma(
+                chromaTop.Memory.Span,
+                surface.UPlane.AsSpan(),
+                surface.VPlane.AsSpan(),
+                surface.UvStride * 2,
+                surface.UvWidth,
+                surface.UvHeight / 2);
+
+            WriteChroma(
+                chromaBottom.Memory.Span,
+                surface.UPlane.AsSpan().Slice(surface.UvStride),
+                surface.VPlane.AsSpan().Slice(surface.UvStride),
+                surface.UvStride * 2,
+                surface.UvWidth,
+                surface.UvHeight / 2);
+        }
+
         private static void WriteLuma(Span<byte> dst, ReadOnlySpan<byte> src, int srcStride, int width, int height)
         {
             LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, srcStride, 1, 2, src);
diff --git a/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs b/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs
index 326c40ae60..7c779dff3d 100644
--- a/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs
+++ b/Ryujinx.Graphics.Nvdec/Types/H264/PictureInfo.cs
@@ -26,10 +26,10 @@ namespace Ryujinx.Graphics.Nvdec.Types.H264
         public uint Transform8x8ModeFlag;
         public uint LumaPitch;
         public uint ChromaPitch;
-        public uint LumaTopOffset;
-        public uint LumaBottomOffset;
+        public uint LumaTopFieldOffset;
+        public uint LumaBottomFieldOffset;
         public uint LumaFrameOffset;
-        public uint ChromaTopOffset;
+        public uint ChromaTopFieldOffset;
         public uint ChromaBottomFieldOffset;
         public uint ChromaFrameOffset;
         public uint HistBufferSize;
diff --git a/Ryujinx.Graphics.Vic/Blender.cs b/Ryujinx.Graphics.Vic/Blender.cs
index 84171241e1..92b641d63f 100644
--- a/Ryujinx.Graphics.Vic/Blender.cs
+++ b/Ryujinx.Graphics.Vic/Blender.cs
@@ -48,38 +48,10 @@ namespace Ryujinx.Graphics.Vic
 
             int one = 1 << (mtx.MatrixRShift + 8);
 
-
-            // NOTE: This is buggy on .NET 5.0.100, we use a workaround for now (see https://github.com/dotnet/runtime/issues/44704)
-            // TODO: Uncomment this when fixed.
-            //Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
-            //Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
-            //Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one);
-            //Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
-
-            Vector128<int> col1 = new Vector128<int>();
-            Vector128<int> col2 = new Vector128<int>();
-            Vector128<int> col3 = new Vector128<int>();
-            Vector128<int> col4 = new Vector128<int>();
-
-            col1 = Sse41.Insert(col1, mtx.MatrixCoeff00, 0);
-            col1 = Sse41.Insert(col1, mtx.MatrixCoeff10, 1);
-            col1 = Sse41.Insert(col1, mtx.MatrixCoeff20, 2);
-            col1 = Sse41.Insert(col1, 0, 3);
-
-            col2 = Sse41.Insert(col2, mtx.MatrixCoeff01, 0);
-            col2 = Sse41.Insert(col2, mtx.MatrixCoeff11, 1);
-            col2 = Sse41.Insert(col2, mtx.MatrixCoeff21, 2);
-            col2 = Sse41.Insert(col2, 0, 3);
-
-            col3 = Sse41.Insert(col3, mtx.MatrixCoeff02, 0);
-            col3 = Sse41.Insert(col3, mtx.MatrixCoeff12, 1);
-            col3 = Sse41.Insert(col3, mtx.MatrixCoeff22, 2);
-            col3 = Sse41.Insert(col3, one, 3);
-
-            col4 = Sse41.Insert(col4, mtx.MatrixCoeff03, 0);
-            col4 = Sse41.Insert(col4, mtx.MatrixCoeff13, 1);
-            col4 = Sse41.Insert(col4, mtx.MatrixCoeff23, 2);
-            col4 = Sse41.Insert(col4, 0, 3);
+            Vector128<int> col1 = Vector128.Create(mtx.MatrixCoeff00, mtx.MatrixCoeff10, mtx.MatrixCoeff20, 0);
+            Vector128<int> col2 = Vector128.Create(mtx.MatrixCoeff01, mtx.MatrixCoeff11, mtx.MatrixCoeff21, 0);
+            Vector128<int> col3 = Vector128.Create(mtx.MatrixCoeff02, mtx.MatrixCoeff12, mtx.MatrixCoeff22, one);
+            Vector128<int> col4 = Vector128.Create(mtx.MatrixCoeff03, mtx.MatrixCoeff13, mtx.MatrixCoeff23, 0);
 
             Vector128<int> rShift = Vector128.CreateScalar(mtx.MatrixRShift);
             Vector128<ushort> clMin = Vector128.Create((ushort)slot.SlotConfig.SoftClampLow);
diff --git a/Ryujinx.Graphics.Vic/Image/BufferPool.cs b/Ryujinx.Graphics.Vic/Image/BufferPool.cs
index 932d3dc923..cde7e6ebbe 100644
--- a/Ryujinx.Graphics.Vic/Image/BufferPool.cs
+++ b/Ryujinx.Graphics.Vic/Image/BufferPool.cs
@@ -14,7 +14,7 @@ namespace Ryujinx.Graphics.Vic.Image
         /// If the required buffer is larger than this, it won't be
         /// added to the pool to avoid long term high memory usage.
         /// </summary>
-        private const int MaxBufferSize = 2048 * 1280;
+        private const int MaxBufferSize = 2048 * 2048;
 
         private struct PoolItem
         {
diff --git a/Ryujinx.Graphics.Vic/Image/InputSurface.cs b/Ryujinx.Graphics.Vic/Image/InputSurface.cs
index de003194d5..15ac04600b 100644
--- a/Ryujinx.Graphics.Vic/Image/InputSurface.cs
+++ b/Ryujinx.Graphics.Vic/Image/InputSurface.cs
@@ -2,16 +2,85 @@
 
 namespace Ryujinx.Graphics.Vic.Image
 {
+    ref struct RentedBuffer
+    {
+        public static RentedBuffer Empty => new RentedBuffer(Span<byte>.Empty, -1);
+
+        public Span<byte> Data;
+        public int Index;
+
+        public RentedBuffer(Span<byte> data, int index)
+        {
+            Data = data;
+            Index = index;
+        }
+
+        public void Return(BufferPool<byte> pool)
+        {
+            if (Index != -1)
+            {
+                pool.Return(Index);
+            }
+        }
+    }
+
     ref struct InputSurface
     {
         public ReadOnlySpan<byte> Buffer0;
         public ReadOnlySpan<byte> Buffer1;
         public ReadOnlySpan<byte> Buffer2;
 
+        public int Buffer0Index;
+        public int Buffer1Index;
+        public int Buffer2Index;
+
         public int Width;
         public int Height;
 
         public int UvWidth;
         public int UvHeight;
+
+        public void Initialize()
+        {
+            Buffer0Index = -1;
+            Buffer1Index = -1;
+            Buffer2Index = -1;
+        }
+
+        public void SetBuffer0(RentedBuffer buffer)
+        {
+            Buffer0 = buffer.Data;
+            Buffer0Index = buffer.Index;
+        }
+
+        public void SetBuffer1(RentedBuffer buffer)
+        {
+            Buffer1 = buffer.Data;
+            Buffer1Index = buffer.Index;
+        }
+
+        public void SetBuffer2(RentedBuffer buffer)
+        {
+            Buffer2 = buffer.Data;
+            Buffer2Index = buffer.Index;
+        }
+
+        public void Return(BufferPool<byte> pool)
+        {
+            if (Buffer0Index != -1)
+            {
+                pool.Return(Buffer0Index);
+            }
+
+            if (Buffer1Index != -1)
+            {
+                pool.Return(Buffer1Index);
+            }
+
+            if (Buffer2Index != -1)
+            {
+                pool.Return(Buffer2Index);
+            }
+        }
     }
 }
diff --git a/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs b/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs
index aa88091684..dda766a582 100644
--- a/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs
+++ b/Ryujinx.Graphics.Vic/Image/SurfaceReader.cs
@@ -1,5 +1,5 @@
 using Ryujinx.Common.Logging;
-using Ryujinx.Graphics.Gpu.Memory;
+using Ryujinx.Common.Memory;
 using Ryujinx.Graphics.Texture;
 using Ryujinx.Graphics.Vic.Types;
 using System;
@@ -12,24 +12,32 @@ namespace Ryujinx.Graphics.Vic.Image
 {
     static class SurfaceReader
     {
-        public static Surface Read(ResourceManager rm, ref SlotSurfaceConfig config, ref PlaneOffsets offsets)
+        public static Surface Read(
+            ResourceManager rm,
+            ref SlotConfig config,
+            ref SlotSurfaceConfig surfaceConfig,
+            ref Array8<PlaneOffsets> offsets)
         {
-            switch (config.SlotPixelFormat)
+            switch (surfaceConfig.SlotPixelFormat)
             {
-                case PixelFormat.Y8___V8U8_N420: return ReadNv12(rm, ref config, ref offsets);
+                case PixelFormat.Y8___V8U8_N420: return ReadNv12(rm, ref config, ref surfaceConfig, ref offsets);
             }
 
-            Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{config.SlotPixelFormat}\".");
+            Logger.Error?.Print(LogClass.Vic, $"Unsupported pixel format \"{surfaceConfig.SlotPixelFormat}\".");
 
-            int lw = config.SlotLumaWidth + 1;
-            int lh = config.SlotLumaHeight + 1;
+            int lw = surfaceConfig.SlotLumaWidth + 1;
+            int lh = surfaceConfig.SlotLumaHeight + 1;
 
             return new Surface(rm.SurfacePool, lw, lh);
         }
 
-        private unsafe static Surface ReadNv12(ResourceManager rm, ref SlotSurfaceConfig config, ref PlaneOffsets offsets)
+        private unsafe static Surface ReadNv12(
+            ResourceManager rm,
+            ref SlotConfig config,
+            ref SlotSurfaceConfig surfaceConfig,
+            ref Array8<PlaneOffsets> offsets)
         {
-            InputSurface input = ReadSurface(rm.Gmm, ref config, ref offsets, 1, 2);
+            InputSurface input = ReadSurface(rm, ref config, ref surfaceConfig, ref offsets, 1, 2);
 
             int width = input.Width;
             int height = input.Height;
@@ -160,6 +168,8 @@ namespace Ryujinx.Graphics.Vic.Image
                 }
             }
 
+            input.Return(rm.BufferPool);
+
             return output;
         }
 
@@ -170,84 +180,227 @@ namespace Ryujinx.Graphics.Vic.Image
         }
 
         private static InputSurface ReadSurface(
-            MemoryManager gmm,
-            ref SlotSurfaceConfig config,
-            ref PlaneOffsets offsets,
+            ResourceManager rm,
+            ref SlotConfig config,
+            ref SlotSurfaceConfig surfaceConfig,
+            ref Array8<PlaneOffsets> offsets,
             int bytesPerPixel,
             int planes)
         {
             InputSurface surface = new InputSurface();
 
-            int gobBlocksInY = 1 << config.SlotBlkHeight;
+            surface.Initialize();
 
-            bool linear = config.SlotBlkKind == 0;
+            int gobBlocksInY = 1 << surfaceConfig.SlotBlkHeight;
 
-            int lw = config.SlotLumaWidth + 1;
-            int lh = config.SlotLumaHeight + 1;
+            bool linear = surfaceConfig.SlotBlkKind == 0;
 
-            int cw = config.SlotChromaWidth + 1;
-            int ch = config.SlotChromaHeight + 1;
+            int lw = surfaceConfig.SlotLumaWidth + 1;
+            int lh = surfaceConfig.SlotLumaHeight + 1;
+
+            int cw = surfaceConfig.SlotChromaWidth + 1;
+            int ch = surfaceConfig.SlotChromaHeight + 1;
+
+            // Interlaced inputs have double the height when deinterlaced.
+            int heightShift = config.FrameFormat.IsField() ? 1 : 0;
 
             surface.Width = lw;
-            surface.Height = lh;
+            surface.Height = lh << heightShift;
             surface.UvWidth = cw;
-            surface.UvHeight = ch;
+            surface.UvHeight = ch << heightShift;
 
             if (planes > 0)
             {
-                surface.Buffer0 = ReadBuffer(gmm, offsets.LumaOffset, linear, lw, lh, bytesPerPixel, gobBlocksInY);
+                surface.SetBuffer0(ReadBuffer(rm, ref config, ref offsets, linear, 0, lw, lh, bytesPerPixel, gobBlocksInY));
             }
 
             if (planes > 1)
             {
-                surface.Buffer1 = ReadBuffer(gmm, offsets.ChromaUOffset, linear, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY);
+                surface.SetBuffer1(ReadBuffer(rm, ref config, ref offsets, linear, 1, cw, ch, planes == 2 ? 2 : 1, gobBlocksInY));
             }
 
             if (planes > 2)
             {
-                surface.Buffer2 = ReadBuffer(gmm, offsets.ChromaVOffset, linear, cw, ch, 1, gobBlocksInY);
+                surface.SetBuffer2(ReadBuffer(rm, ref config, ref offsets, linear, 2, cw, ch, 1, gobBlocksInY));
             }
 
             return surface;
         }
 
-        private static ReadOnlySpan<byte> ReadBuffer(
-            MemoryManager gmm,
-            uint offset,
+        private static RentedBuffer ReadBuffer(
+            ResourceManager rm,
+            ref SlotConfig config,
+            ref Array8<PlaneOffsets> offsets,
             bool linear,
+            int plane,
+            int width,
+            int height,
+            int bytesPerPixel,
+            int gobBlocksInY)
+        {
+            FrameFormat frameFormat = config.FrameFormat;
+            bool isLuma = plane == 0;
+            bool isField = frameFormat.IsField();
+            bool isTopField = frameFormat.IsTopField(isLuma);
+            int stride = GetPitch(width, bytesPerPixel);
+            uint offset = GetOffset(ref offsets[0], plane);
+
+            int dstStart = 0;
+            int dstStride = stride;
+
+            if (isField)
+            {
+                dstStart = isTopField ? 0 : stride;
+                dstStride = stride * 2;
+            }
+
+            RentedBuffer buffer;
+
+            if (linear)
+            {
+                buffer = ReadBufferLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel);
+            }
+            else
+            {
+                buffer = ReadBufferBlockLinear(rm, offset, width, height, dstStart, dstStride, bytesPerPixel, gobBlocksInY);
+            }
+
+            if (isField || frameFormat.IsInterlaced())
+            {
+                RentedBuffer prevBuffer = RentedBuffer.Empty;
+                RentedBuffer nextBuffer = RentedBuffer.Empty;
+
+                if (config.PrevFieldEnable)
+                {
+                    prevBuffer = ReadBufferNoDeinterlace(rm, ref offsets[1], linear, plane, width, height, bytesPerPixel, gobBlocksInY);
+                }
+
+                if (config.NextFieldEnable)
+                {
+                    nextBuffer = ReadBufferNoDeinterlace(rm, ref offsets[2], linear, plane, width, height, bytesPerPixel, gobBlocksInY);
+                }
+
+                int w = width * bytesPerPixel;
+
+                switch (config.DeinterlaceMode)
+                {
+                    case DeinterlaceMode.Weave:
+                        Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField);
+                        break;
+                    case DeinterlaceMode.BobField:
+                        Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField);
+                        break;
+                    case DeinterlaceMode.Bob:
+                        bool isCurrentTop = isLuma ? config.IsEven : config.ChromaEven;
+                        Scaler.DeinterlaceBob(buffer.Data, w, stride, isCurrentTop ^ frameFormat.IsInterlacedBottomFirst());
+                        break;
+                    case DeinterlaceMode.NewBob:
+                    case DeinterlaceMode.Disi1:
+                        Scaler.DeinterlaceMotionAdaptive(buffer.Data, prevBuffer.Data, nextBuffer.Data, w, stride, isTopField);
+                        break;
+                    case DeinterlaceMode.WeaveLumaBobFieldChroma:
+                        if (isLuma)
+                        {
+                            Scaler.DeinterlaceWeave(buffer.Data, prevBuffer.Data, w, stride, isTopField);
+                        }
+                        else
+                        {
+                            Scaler.DeinterlaceBob(buffer.Data, w, stride, isTopField);
+                        }
+                        break;
+                    default:
+                        Logger.Error?.Print(LogClass.Vic, $"Unsupported deinterlace mode \"{config.DeinterlaceMode}\".");
+                        break;
+                }
+
+                prevBuffer.Return(rm.BufferPool);
+                nextBuffer.Return(rm.BufferPool);
+            }
+
+            return buffer;
+        }
+
+        private static uint GetOffset(ref PlaneOffsets offsets, int plane)
+        {
+            return plane switch
+            {
+                0 => offsets.LumaOffset,
+                1 => offsets.ChromaUOffset,
+                2 => offsets.ChromaVOffset,
+                _ => throw new ArgumentOutOfRangeException(nameof(plane))
+            };
+        }
+
+        private static RentedBuffer ReadBufferNoDeinterlace(
+            ResourceManager rm,
+            ref PlaneOffsets offsets,
+            bool linear,
+            int plane,
             int width,
             int height,
             int bytesPerPixel,
             int gobBlocksInY)
         {
             int stride = GetPitch(width, bytesPerPixel);
+            uint offset = GetOffset(ref offsets, plane);
 
             if (linear)
             {
-                return gmm.GetSpan(ExtendOffset(offset), stride * height);
+                return ReadBufferLinear(rm, offset, width, height, 0, stride, bytesPerPixel);
             }
 
-            return ReadBuffer(gmm, offset, width, height, stride, bytesPerPixel, gobBlocksInY);
+            return ReadBufferBlockLinear(rm, offset, width, height, 0, stride, bytesPerPixel, gobBlocksInY);
         }
 
-        private static ReadOnlySpan<byte> ReadBuffer(
-            MemoryManager gmm,
+        private static RentedBuffer ReadBufferLinear(
+            ResourceManager rm,
             uint offset,
             int width,
             int height,
+            int dstStart,
+            int dstStride,
+            int bytesPerPixel)
+        {
+            int srcStride = GetPitch(width, bytesPerPixel);
+            int inSize = srcStride * height;
+
+            ReadOnlySpan<byte> src = rm.Gmm.GetSpan(ExtendOffset(offset), inSize);
+
+            int outSize = dstStride * height;
+            int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer);
+            Span<byte> dst = buffer;
+            dst = dst.Slice(0, outSize);
+
+            for (int y = 0; y < height; y++)
+            {
+                src.Slice(y * srcStride, srcStride).CopyTo(dst.Slice(dstStart + y * dstStride, srcStride));
+            }
+
+            return new RentedBuffer(dst, bufferIndex);
+        }
+
+        private static RentedBuffer ReadBufferBlockLinear(
+            ResourceManager rm,
+            uint offset,
+            int width,
+            int height,
+            int dstStart,
             int dstStride,
             int bytesPerPixel,
             int gobBlocksInY)
         {
             int inSize = GetBlockLinearSize(width, height, bytesPerPixel, gobBlocksInY);
 
-            ReadOnlySpan<byte> src = gmm.GetSpan(ExtendOffset(offset), inSize);
+            ReadOnlySpan<byte> src = rm.Gmm.GetSpan(ExtendOffset(offset), inSize);
 
-            Span<byte> dst = new byte[dstStride * height];
+            int outSize = dstStride * height;
+            int bufferIndex = rm.BufferPool.RentMinimum(outSize, out byte[] buffer);
+            Span<byte> dst = buffer;
+            dst = dst.Slice(0, outSize);
 
-            LayoutConverter.ConvertBlockLinearToLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
+            LayoutConverter.ConvertBlockLinearToLinear(dst.Slice(dstStart), width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
 
-            return dst;
+            return new RentedBuffer(dst, bufferIndex);
         }
     }
 }
diff --git a/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs b/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs
index dd64720e98..297a04b6d7 100644
--- a/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs
+++ b/Ryujinx.Graphics.Vic/Image/SurfaceWriter.cs
@@ -15,7 +15,7 @@ namespace Ryujinx.Graphics.Vic.Image
             switch (config.OutPixelFormat)
             {
                 case PixelFormat.A8B8G8R8:
-                case PixelFormat.X8B8G8R8:    
+                case PixelFormat.X8B8G8R8:
                     WriteA8B8G8R8(rm, input, ref config, ref offsets);
                     break;
                 case PixelFormat.A8R8G8B8:
@@ -433,7 +433,7 @@ namespace Ryujinx.Graphics.Vic.Image
         {
             if (linear)
             {
-                rm.Gmm.Write(ExtendOffset(offset), src);
+                rm.Gmm.WriteMapped(ExtendOffset(offset), src);
                 return;
             }
 
@@ -456,7 +456,7 @@ namespace Ryujinx.Graphics.Vic.Image
 
             LayoutConverter.ConvertLinearToBlockLinear(dst, width, height, dstStride, bytesPerPixel, gobBlocksInY, src);
 
-            rm.Gmm.Write(ExtendOffset(offset), dst);
+            rm.Gmm.WriteMapped(ExtendOffset(offset), dst);
 
             rm.BufferPool.Return(dstIndex);
         }
diff --git a/Ryujinx.Graphics.Vic/Scaler.cs b/Ryujinx.Graphics.Vic/Scaler.cs
new file mode 100644
index 0000000000..18ae66c4a4
--- /dev/null
+++ b/Ryujinx.Graphics.Vic/Scaler.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Runtime.Intrinsics;
+using System.Runtime.Intrinsics.X86;
+
+namespace Ryujinx.Graphics.Vic
+{
+    static class Scaler
+    {
+        public static void DeinterlaceWeave(Span<byte> data, ReadOnlySpan<byte> prevData, int width, int fieldSize, bool isTopField)
+        {
+            // Prev I    Curr I    Curr P
+            // TTTTTTTT  BBBBBBBB  TTTTTTTT
+            // --------  --------  BBBBBBBB
+
+            if (isTopField)
+            {
+                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
+                {
+                    prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset + fieldSize, width));
+                }
+            }
+            else
+            {
+                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
+                {
+                    prevData.Slice(offset >> 1, width).CopyTo(data.Slice(offset, width));
+                }
+            }
+        }
+
+        public static void DeinterlaceBob(Span<byte> data, int width, int fieldSize, bool isTopField)
+        {
+            // Curr I    Curr P
+            // TTTTTTTT  TTTTTTTT
+            // --------  TTTTTTTT
+
+            if (isTopField)
+            {
+                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
+                {
+                    data.Slice(offset, width).CopyTo(data.Slice(offset + fieldSize, width));
+                }
+            }
+            else
+            {
+                for (int offset = 0; offset < data.Length; offset += fieldSize * 2)
+                {
+                    data.Slice(offset + fieldSize, width).CopyTo(data.Slice(offset, width));
+                }
+            }
+        }
+
+        public unsafe static void DeinterlaceMotionAdaptive(
+            Span<byte> data,
+            ReadOnlySpan<byte> prevData,
+            ReadOnlySpan<byte> nextData,
+            int width,
+            int fieldSize,
+            bool isTopField)
+        {
+            // Very simple motion adaptive algorithm.
+            // If the pixel changed between previous and next frame, use Bob, otherwise use Weave.
+            //
+            // Example pseudo code:
+            // C_even = (P_even == N_even) ? P_even : C_odd
+            // Where: C is current frame, P is previous frame and N is next frame, and even/odd are the fields.
+            //
+            // Note: This does not fully match the hardware algorithm.
+            // The motion adaptive deinterlacing implemented on hardware is considerably more complex,
+            // and hard to implement accurately without proper documentation as for example, the
+            // method used for motion estimation is unknown.
+
+            int start = isTopField ? fieldSize : 0;
+            int otherFieldOffset = isTopField ? -fieldSize : fieldSize;
+
+            fixed (byte* pData = data, pPrevData = prevData, pNextData = nextData)
+            {
+                for (int offset = start; offset < data.Length; offset += fieldSize * 2)
+                {
+                    int refOffset = (offset - start) >> 1;
+                    int x = 0;
+
+                    if (Avx2.IsSupported)
+                    {
+                        for (; x < (width & ~0x1f); x += 32)
+                        {
+                            Vector256<byte> prevPixels = Avx.LoadVector256(pPrevData + refOffset + x);
+                            Vector256<byte> nextPixels = Avx.LoadVector256(pNextData + refOffset + x);
+                            Vector256<byte> bob = Avx.LoadVector256(pData + offset + otherFieldOffset + x);
+                            Vector256<byte> diff = Avx2.CompareEqual(prevPixels, nextPixels);
+                            Avx.Store(pData + offset + x, Avx2.BlendVariable(bob, prevPixels, diff));
+                        }
+                    }
+                    else if (Sse41.IsSupported)
+                    {
+                        for (; x < (width & ~0xf); x += 16)
+                        {
+                            Vector128<byte> prevPixels = Sse2.LoadVector128(pPrevData + refOffset + x);
+                            Vector128<byte> nextPixels = Sse2.LoadVector128(pNextData + refOffset + x);
+                            Vector128<byte> bob = Sse2.LoadVector128(pData + offset + otherFieldOffset + x);
+                            Vector128<byte> diff = Sse2.CompareEqual(prevPixels, nextPixels);
+                            Sse2.Store(pData + offset + x, Sse41.BlendVariable(bob, prevPixels, diff));
+                        }
+                    }
+
+                    for (; x < width; x++)
+                    {
+                        byte prevPixel = prevData[refOffset + x];
+                        byte nextPixel = nextData[refOffset + x];
+
+                        if (nextPixel != prevPixel)
+                        {
+                            data[offset + x] = data[offset + otherFieldOffset + x];
+                        }
+                        else
+                        {
+                            data[offset + x] = prevPixel;
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs b/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs
new file mode 100644
index 0000000000..aa0654f01c
--- /dev/null
+++ b/Ryujinx.Graphics.Vic/Types/DeinterlaceMode.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Vic.Types
+{
+    enum DeinterlaceMode
+    {
+        Weave,
+        BobField,
+        Bob,
+        NewBob,
+        Disi1,
+        WeaveLumaBobFieldChroma
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Vic/Types/FrameFormat.cs b/Ryujinx.Graphics.Vic/Types/FrameFormat.cs
new file mode 100644
index 0000000000..91f5751b4d
--- /dev/null
+++ b/Ryujinx.Graphics.Vic/Types/FrameFormat.cs
@@ -0,0 +1,79 @@
+namespace Ryujinx.Graphics.Vic.Types
+{
+    enum FrameFormat
+    {
+        Progressive,
+        InterlacedTopFieldFirst,
+        InterlacedBottomFieldFirst,
+        TopField,
+        BottomField,
+        SubPicProgressive,
+        SubPicInterlacedTopFieldFirst,
+        SubPicInterlacedBottomFieldFirst,
+        SubPicTopField,
+        SubPicBottomField,
+        TopFieldChromaBottom,
+        BottomFieldChromaTop,
+        SubPicTopFieldChromaBottom,
+        SubPicBottomFieldChromaTop
+    }
+
+    static class FrameFormatExtensions
+    {
+        public static bool IsField(this FrameFormat frameFormat)
+        {
+            switch (frameFormat)
+            {
+                case FrameFormat.TopField:
+                case FrameFormat.BottomField:
+                case FrameFormat.SubPicTopField:
+                case FrameFormat.SubPicBottomField:
+                case FrameFormat.TopFieldChromaBottom:
+                case FrameFormat.BottomFieldChromaTop:
+                case FrameFormat.SubPicTopFieldChromaBottom:
+                case FrameFormat.SubPicBottomFieldChromaTop:
+                    return true;
+            }
+
+            return false;
+        }
+
+        public static bool IsInterlaced(this FrameFormat frameFormat)
+        {
+            switch (frameFormat)
+            {
+                case FrameFormat.InterlacedTopFieldFirst:
+                case FrameFormat.InterlacedBottomFieldFirst:
+                case FrameFormat.SubPicInterlacedTopFieldFirst:
+                case FrameFormat.SubPicInterlacedBottomFieldFirst:
+                    return true;
+            }
+
+            return false;
+        }
+
+        public static bool IsInterlacedBottomFirst(this FrameFormat frameFormat)
+        {
+            return frameFormat == FrameFormat.InterlacedBottomFieldFirst ||
+                   frameFormat == FrameFormat.SubPicInterlacedBottomFieldFirst;
+        }
+
+        public static bool IsTopField(this FrameFormat frameFormat, bool isLuma)
+        {
+            switch (frameFormat)
+            {
+                case FrameFormat.TopField:
+                case FrameFormat.SubPicTopField:
+                    return true;
+                case FrameFormat.TopFieldChromaBottom:
+                case FrameFormat.SubPicTopFieldChromaBottom:
+                    return isLuma;
+                case FrameFormat.BottomFieldChromaTop:
+                case FrameFormat.SubPicBottomFieldChromaTop:
+                    return !isLuma;
+            }
+
+            return false;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Vic/Types/SlotConfig.cs b/Ryujinx.Graphics.Vic/Types/SlotConfig.cs
index 183ee4ac20..373e76f697 100644
--- a/Ryujinx.Graphics.Vic/Types/SlotConfig.cs
+++ b/Ryujinx.Graphics.Vic/Types/SlotConfig.cs
@@ -27,7 +27,7 @@
         public bool PrevMotionFieldEnable => _word0.Extract(13);
         public bool PpMotionFieldEnable => _word0.Extract(14);
         public bool CombMotionFieldEnable => _word0.Extract(15);
-        public int FrameFormat => _word0.Extract(16, 4);
+        public FrameFormat FrameFormat => (FrameFormat)_word0.Extract(16, 4);
         public int FilterLengthY => _word0.Extract(20, 2);
         public int FilterLengthX => _word0.Extract(22, 2);
         public int Panoramic => _word0.Extract(24, 12);
@@ -36,7 +36,7 @@
         public int FilterDetail => _word1.Extract(74, 10);
         public int ChromaNoise => _word1.Extract(84, 10);
         public int ChromaDetail => _word1.Extract(94, 10);
-        public int DeinterlaceMode => _word1.Extract(104, 4);
+        public DeinterlaceMode DeinterlaceMode => (DeinterlaceMode)_word1.Extract(104, 4);
         public int MotionAccumWeight => _word1.Extract(108, 3);
         public int NoiseIir => _word1.Extract(111, 11);
         public int LightLevel => _word1.Extract(122, 4);
diff --git a/Ryujinx.Graphics.Vic/VicDevice.cs b/Ryujinx.Graphics.Vic/VicDevice.cs
index 21021c58c2..537b8ba47c 100644
--- a/Ryujinx.Graphics.Vic/VicDevice.cs
+++ b/Ryujinx.Graphics.Vic/VicDevice.cs
@@ -43,9 +43,9 @@ namespace Ryujinx.Graphics.Vic
                     continue;
                 }
 
-                var offsets = _state.State.SetSurfacexSlotx[i][0];
+                ref var offsets = ref _state.State.SetSurfacexSlotx[i];
 
-                using Surface src = SurfaceReader.Read(_rm, ref slot.SlotSurfaceConfig, ref offsets);
+                using Surface src = SurfaceReader.Read(_rm, ref slot.SlotConfig, ref slot.SlotSurfaceConfig, ref offsets);
 
                 Blender.BlendOne(output, src, ref slot);
             }
diff --git a/Ryujinx.Graphics.Video/FrameField.cs b/Ryujinx.Graphics.Video/FrameField.cs
new file mode 100644
index 0000000000..2bff0e75f4
--- /dev/null
+++ b/Ryujinx.Graphics.Video/FrameField.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Video
+{
+    public enum FrameField
+    {
+        Progressive,
+        Interlaced
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics.Video/ISurface.cs b/Ryujinx.Graphics.Video/ISurface.cs
index fb66f31a8b..7c1661f1fa 100644
--- a/Ryujinx.Graphics.Video/ISurface.cs
+++ b/Ryujinx.Graphics.Video/ISurface.cs
@@ -8,6 +8,8 @@ namespace Ryujinx.Graphics.Video
         Plane UPlane { get; }
         Plane VPlane { get; }
 
+        FrameField Field { get; }
+
         int Width { get; }
         int Height { get; }
         int Stride { get; }