diff --git a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs
index c601d52213..1c35b23c50 100644
--- a/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs
+++ b/Ryujinx.Core/OsHle/Handles/KProcessScheduler.cs
@@ -14,23 +14,23 @@ namespace Ryujinx.Core.OsHle.Handles
         {
             public KThread Thread { get; private set; }
 
-            public ManualResetEvent SyncWaitEvent  { get; private set; }
-            public AutoResetEvent   SchedWaitEvent { get; private set; }
+            public bool IsActive { get; set; }
 
-            public bool Active { get; set; }
-
-            public int SyncTimeout { get; set; }
+            public AutoResetEvent   WaitSync     { get; private set; }
+            public ManualResetEvent WaitActivity { get; private set; }
+            public AutoResetEvent   WaitSched    { get; private set; }
 
             public SchedulerThread(KThread Thread)
             {
                 this.Thread = Thread;
 
-                SyncWaitEvent  = new ManualResetEvent(true);
-                SchedWaitEvent = new AutoResetEvent(false);
+                IsActive = true;
 
-                Active = true;
+                WaitSync  = new AutoResetEvent(false);
 
-                SyncTimeout = 0;
+                WaitActivity = new ManualResetEvent(true);
+
+                WaitSched = new AutoResetEvent(false);
             }
 
             public void Dispose()
@@ -42,8 +42,11 @@ namespace Ryujinx.Core.OsHle.Handles
             {
                 if (Disposing)
                 {
-                    SyncWaitEvent.Dispose();
-                    SchedWaitEvent.Dispose();
+                    WaitSync.Dispose();
+
+                    WaitActivity.Dispose();
+
+                    WaitSched.Dispose();
                 }
             }
         }
@@ -206,25 +209,46 @@ namespace Ryujinx.Core.OsHle.Handles
                 throw new InvalidOperationException();
             }
 
-            SchedThread.Active = Active;
+            SchedThread.IsActive = Active;
 
-            UpdateSyncWaitEvent(SchedThread);
-
-            WaitIfNeeded(SchedThread);
+            if (Active)
+            {
+                SchedThread.WaitActivity.Set();
+            }
+            else
+            {
+                SchedThread.WaitActivity.Reset();
+            }
         }
 
-        public bool EnterWait(KThread Thread, int Timeout = -1)
+        public void EnterWait(KThread Thread)
         {
             if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
             {
                 throw new InvalidOperationException();
             }
 
-            SchedThread.SyncTimeout = Timeout;
+            Suspend(Thread.ProcessorId);
 
-            UpdateSyncWaitEvent(SchedThread);
+            SchedThread.WaitSync.WaitOne();
 
-            return WaitIfNeeded(SchedThread);
+            TryResumingExecution(SchedThread);
+        }
+
+        public bool EnterWait(KThread Thread, int Timeout)
+        {
+            if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
+            {
+                throw new InvalidOperationException();
+            }
+
+            Suspend(Thread.ProcessorId);
+
+            bool Result = SchedThread.WaitSync.WaitOne(Timeout);
+
+            TryResumingExecution(SchedThread);
+
+            return Result;
         }
 
         public void WakeUp(KThread Thread)
@@ -234,39 +258,7 @@ namespace Ryujinx.Core.OsHle.Handles
                 throw new InvalidOperationException();
             }
 
-            SchedThread.SyncTimeout = 0;
-
-            UpdateSyncWaitEvent(SchedThread);
-
-            WaitIfNeeded(SchedThread);
-        }
-
-        private void UpdateSyncWaitEvent(SchedulerThread SchedThread)
-        {
-            if (SchedThread.Active && SchedThread.SyncTimeout == 0)
-            {
-                SchedThread.SyncWaitEvent.Set();
-            }
-            else
-            {
-                SchedThread.SyncWaitEvent.Reset();
-            }
-        }
-
-        private bool WaitIfNeeded(SchedulerThread SchedThread)
-        {
-            KThread Thread = SchedThread.Thread;
-
-            if (!IsActive(SchedThread) && Thread.Thread.IsCurrentThread())
-            {
-                Suspend(Thread.ProcessorId);
-
-                return Resume(Thread);
-            }
-            else
-            {
-                return false;
-            }
+            SchedThread.WaitSync.Set();
         }
 
         public void Suspend(int ProcessorId)
@@ -292,53 +284,52 @@ namespace Ryujinx.Core.OsHle.Handles
         {
             PrintDbgThreadInfo(Thread, "yielded execution.");
 
-            lock (SchedLock)
+            if (IsActive(Thread))
             {
-                SchedulerThread SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.ActualPriority);
-
-                if (IsActive(Thread) && SchedThread == null)
+                lock (SchedLock)
                 {
-                    PrintDbgThreadInfo(Thread, "resumed because theres nothing better to run.");
+                    SchedulerThread SchedThread = WaitingToRun[Thread.ProcessorId].Pop(Thread.ActualPriority);
 
-                    return;
-                }
+                    if (SchedThread == null)
+                    {
+                        PrintDbgThreadInfo(Thread, "resumed because theres nothing better to run.");
 
-                if (SchedThread != null)
-                {
-                    RunThread(SchedThread);
+                        return;
+                    }
+
+                    if (SchedThread != null)
+                    {
+                        RunThread(SchedThread);
+                    }
                 }
             }
+            else
+            {
+                //Just stop running the thread if it's not active,
+                //and run whatever is waiting to run with the higuest priority.
+                Suspend(Thread.ProcessorId);
+            }
 
             Resume(Thread);
         }
 
-        public bool Resume(KThread Thread)
+        public void Resume(KThread Thread)
         {
             if (!AllThreads.TryGetValue(Thread, out SchedulerThread SchedThread))
             {
                 throw new InvalidOperationException();
             }
 
-            return TryResumingExecution(SchedThread);
+            TryResumingExecution(SchedThread);
         }
 
-        private bool TryResumingExecution(SchedulerThread SchedThread)
+        private void TryResumingExecution(SchedulerThread SchedThread)
         {
             KThread Thread = SchedThread.Thread;
 
-            if (!SchedThread.Active || SchedThread.SyncTimeout != 0)
-            {
-                PrintDbgThreadInfo(Thread, "entering inactive wait state...");
-            }
+            PrintDbgThreadInfo(Thread, "trying to resume...");
 
-            bool Result = false;
-
-            if (SchedThread.SyncTimeout != 0)
-            {
-                Result = SchedThread.SyncWaitEvent.WaitOne(SchedThread.SyncTimeout);
-
-                SchedThread.SyncTimeout = 0;
-            }
+            SchedThread.WaitActivity.WaitOne();
 
             lock (SchedLock)
             {
@@ -346,7 +337,7 @@ namespace Ryujinx.Core.OsHle.Handles
                 {
                     PrintDbgThreadInfo(Thread, "resuming execution...");
 
-                    return Result;
+                    return;
                 }
 
                 WaitingToRun[Thread.ProcessorId].Push(SchedThread);
@@ -354,18 +345,16 @@ namespace Ryujinx.Core.OsHle.Handles
                 PrintDbgThreadInfo(Thread, "entering wait state...");
             }
 
-            SchedThread.SchedWaitEvent.WaitOne();
+            SchedThread.WaitSched.WaitOne();
 
             PrintDbgThreadInfo(Thread, "resuming execution...");
-
-            return Result;
         }
 
         private void RunThread(SchedulerThread SchedThread)
         {
             if (!SchedThread.Thread.Thread.Execute())
             {
-                SchedThread.SchedWaitEvent.Set();
+                SchedThread.WaitSched.Set();
             }
             else
             {
@@ -380,21 +369,16 @@ namespace Ryujinx.Core.OsHle.Handles
                 throw new InvalidOperationException();
             }
 
-            return IsActive(SchedThread);
-        }
-
-        private bool IsActive(SchedulerThread SchedThread)
-        {
-            return SchedThread.Active && SchedThread.SyncTimeout == 0;
+            return SchedThread.IsActive;
         }
 
         private void PrintDbgThreadInfo(KThread Thread, string Message)
         {
             Log.PrintDebug(LogClass.KernelScheduler, "(" +
-                "ThreadId: "       + Thread.ThreadId       + ", " +
-                "ProcessorId: "    + Thread.ProcessorId    + ", " +
-                "ActualPriority: " + Thread.ActualPriority + ", " +
-                "WantedPriority: " + Thread.WantedPriority + ") " + Message);
+                "ThreadId = "       + Thread.ThreadId       + ", " +
+                "ProcessorId = "    + Thread.ProcessorId    + ", " +
+                "ActualPriority = " + Thread.ActualPriority + ", " +
+                "WantedPriority = " + Thread.WantedPriority + ") " + Message);
         }
 
         public void Dispose()
diff --git a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
index 4c4fa56ed1..d81f44bd9b 100644
--- a/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
+++ b/Ryujinx.Core/OsHle/Ipc/IpcMessage.cs
@@ -198,5 +198,35 @@ namespace Ryujinx.Core.OsHle.Ipc
 
             return -1;
         }
+
+        public long GetBufferType0x21Position()
+        {
+            if (PtrBuff.Count > 0 && PtrBuff[0].Position != 0)
+            {
+                return PtrBuff[0].Position;
+            }
+
+            if (SendBuff.Count > 0)
+            {
+                return SendBuff[0].Position;
+            }
+
+            return 0;
+        }
+
+        public long GetBufferType0x22Position()
+        {
+            if (RecvListBuff.Count > 0 && RecvListBuff[0].Position != 0)
+            {
+                return RecvListBuff[0].Position;
+            }
+
+            if (ReceiveBuff.Count > 0)
+            {
+                return ReceiveBuff[0].Position;
+            }
+
+            return 0;
+        }
     }
 }
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
index da7dce894b..e855b77df0 100644
--- a/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
+++ b/Ryujinx.Core/OsHle/Kernel/SvcHandler.cs
@@ -86,6 +86,8 @@ namespace Ryujinx.Core.OsHle.Kernel
 
             if (SvcFuncs.TryGetValue(e.Id, out SvcFunc Func))
             {
+                Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} called.");
+
                 Func(ThreadState);
 
                 Ns.Log.PrintDebug(LogClass.KernelSvc, $"{Func.Method.Name} ended.");
diff --git a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
index d5593e022e..ec109b2d54 100644
--- a/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
+++ b/Ryujinx.Core/OsHle/Kernel/SvcThreadSync.cs
@@ -18,6 +18,11 @@ namespace Ryujinx.Core.OsHle.Kernel
             long MutexAddress      = (long)ThreadState.X1;
             int  WaitThreadHandle  =  (int)ThreadState.X2;
 
+            Ns.Log.PrintDebug(LogClass.KernelSvc,
+                "OwnerThreadHandle = " + OwnerThreadHandle.ToString("x8")  + ", " +
+                "MutexAddress = "      + MutexAddress     .ToString("x16") + ", " +
+                "WaitThreadHandle = "  + WaitThreadHandle .ToString("x8"));
+
             if (IsPointingInsideKernel(MutexAddress))
             {
                 Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
@@ -38,6 +43,8 @@ namespace Ryujinx.Core.OsHle.Kernel
 
             KThread OwnerThread = Process.HandleTable.GetData<KThread>(OwnerThreadHandle);
 
+            Ns.Log.PrintDebug(LogClass.KernelSvc, "lock tid: " + OwnerThread.ThreadId.ToString());
+
             if (OwnerThread == null)
             {
                 Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid owner thread handle 0x{OwnerThreadHandle:x8}!");
@@ -69,6 +76,8 @@ namespace Ryujinx.Core.OsHle.Kernel
         {
             long MutexAddress = (long)ThreadState.X0;
 
+            Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexAddress = " + MutexAddress.ToString("x16"));
+
             if (IsPointingInsideKernel(MutexAddress))
             {
                 Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
@@ -102,6 +111,12 @@ namespace Ryujinx.Core.OsHle.Kernel
             int   ThreadHandle   =  (int)ThreadState.X2;
             ulong Timeout        =       ThreadState.X3;
 
+            Ns.Log.PrintDebug(LogClass.KernelSvc,
+                "OwnerThreadHandle = " + MutexAddress  .ToString("x16") + ", " +
+                "MutexAddress = "      + CondVarAddress.ToString("x16") + ", " +
+                "WaitThreadHandle = "  + ThreadHandle  .ToString("x8")  + ", " +
+                "Timeout = "           + Timeout       .ToString("x16"));
+
             if (IsPointingInsideKernel(MutexAddress))
             {
                 Ns.Log.PrintWarning(LogClass.KernelSvc, $"Invalid mutex address 0x{MutexAddress:x16}!");
@@ -166,6 +181,8 @@ namespace Ryujinx.Core.OsHle.Kernel
         {
             int MutexValue = Process.Memory.ReadInt32(MutexAddress);
 
+            Ns.Log.PrintDebug(LogClass.KernelSvc, "MutexValue = " + MutexValue.ToString("x8"));
+
             if (MutexValue != (OwnerThreadHandle | MutexHasListenersMask))
             {
                 return;
@@ -176,7 +193,7 @@ namespace Ryujinx.Core.OsHle.Kernel
 
             InsertWaitingMutexThread(OwnerThreadHandle, WaitThread);
 
-            Process.Scheduler.EnterWait(WaitThread);
+            Process.Scheduler.EnterWait(CurrThread);
         }
 
         private bool MutexUnlock(KThread CurrThread, long MutexAddress)
@@ -199,8 +216,12 @@ namespace Ryujinx.Core.OsHle.Kernel
                     OwnerThread = OwnerThread.NextMutexThread;
                 }
 
+                UpdateMutexOwner(CurrThread, OwnerThread, MutexAddress);
+
                 CurrThread.NextMutexThread = null;
 
+                CurrThread.UpdatePriority();
+
                 if (OwnerThread != null)
                 {
                     int HasListeners = OwnerThread.NextMutexThread != null ? MutexHasListenersMask : 0;
@@ -284,7 +305,9 @@ namespace Ryujinx.Core.OsHle.Kernel
             }
             else
             {
-                return Process.Scheduler.EnterWait(WaitThread);
+                Process.Scheduler.EnterWait(WaitThread);
+
+                return true;
             }
         }
 
@@ -314,8 +337,6 @@ namespace Ryujinx.Core.OsHle.Kernel
 
                         int MutexValue = Process.Memory.ReadInt32(CurrThread.MutexAddress);
 
-                        MutexValue &= ~MutexHasListenersMask;
-
                         if (MutexValue == 0)
                         {
                             //Give the lock to this thread.
@@ -325,15 +346,17 @@ namespace Ryujinx.Core.OsHle.Kernel
                             CurrThread.MutexAddress   = 0;
                             CurrThread.CondVarAddress = 0;
 
-                            CurrThread.MutexOwner = null;
+                            CurrThread.MutexOwner?.UpdatePriority();
 
-                            CurrThread.UpdatePriority();
+                            CurrThread.MutexOwner = null;
 
                             Process.Scheduler.WakeUp(CurrThread);
                         }
                         else
                         {
                             //Wait until the lock is released.
+                            MutexValue &= ~MutexHasListenersMask;
+
                             InsertWaitingMutexThread(MutexValue, CurrThread);
 
                             MutexValue |= MutexHasListenersMask;
@@ -399,6 +422,23 @@ namespace Ryujinx.Core.OsHle.Kernel
             OwnerThread.UpdatePriority();
         }
 
+        private void UpdateMutexOwner(KThread CurrThread, KThread NewOwner, long MutexAddress)
+        {
+            //Go through all threads waiting for the mutex,
+            //and update the MutexOwner field to point to the new owner.
+            CurrThread = CurrThread.NextMutexThread;
+
+            while (CurrThread != null)
+            {
+                if (CurrThread.MutexAddress == MutexAddress)
+                {
+                    CurrThread.MutexOwner = NewOwner;
+                }
+
+                CurrThread = CurrThread.NextMutexThread;
+            }
+        }
+
         private void AcquireMutexValue(long MutexAddress)
         {
             while (!Process.Memory.AcquireAddress(MutexAddress))
diff --git a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
index 10d6389445..f41a98d0ab 100644
--- a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
+++ b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
@@ -44,6 +44,7 @@ namespace Ryujinx.Core.OsHle.Services.Nv
             {
                 { ("/dev/nvhost-as-gpu",   0x4101), NvGpuAsIoctlBindChannel           },
                 { ("/dev/nvhost-as-gpu",   0x4102), NvGpuAsIoctlAllocSpace            },
+                { ("/dev/nvhost-as-gpu",   0x4105), NvGpuAsIoctlUnmap                 },
                 { ("/dev/nvhost-as-gpu",   0x4106), NvGpuAsIoctlMapBufferEx           },
                 { ("/dev/nvhost-as-gpu",   0x4108), NvGpuAsIoctlGetVaRegions          },
                 { ("/dev/nvhost-as-gpu",   0x4109), NvGpuAsIoctlInitializeEx          },
@@ -201,6 +202,19 @@ namespace Ryujinx.Core.OsHle.Services.Nv
             return 0;
         }
 
+        private long NvGpuAsIoctlUnmap(ServiceCtx Context)
+        {
+            long Position = Context.Request.GetSendBuffPtr();
+
+            MemReader Reader = new MemReader(Context.Memory, Position);
+
+            long Offset = Reader.ReadInt64();
+
+            Context.Ns.Gpu.MemoryMgr.Unmap(Offset, 0x10000);
+
+            return 0;
+        }
+
         private long NvGpuAsIoctlMapBufferEx(ServiceCtx Context)
         {
             long Position = Context.Request.GetSendBuffPtr();
@@ -319,6 +333,19 @@ namespace Ryujinx.Core.OsHle.Services.Nv
                 int Padding = Reader.ReadInt32();
                 int Offset  = Reader.ReadInt32();
                 int Pages   = Reader.ReadInt32();
+
+                NvMap Map = NvMaps.GetData<NvMap>(Context.Process, Handle);
+
+                if (Map == null)
+                {
+                    Context.Ns.Log.PrintWarning(LogClass.ServiceNv, $"invalid NvMap Handle {Handle}!");
+
+                    return -1; //TODO: Corrent error code.
+                }
+
+                Context.Ns.Gpu.MapMemory(Map.CpuAddress,
+                    (long)(uint)Offset << 16,
+                    (long)(uint)Pages  << 16);
             }
 
             //TODO
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs
new file mode 100644
index 0000000000..9ea3ae6e5c
--- /dev/null
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvChNvMap.cs
@@ -0,0 +1,31 @@
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Core.OsHle.Services.Nv
+{
+    class NvChNvMap
+    {
+        private static ConcurrentDictionary<Process, IdDictionary> NvMaps;
+
+        public void Create(ServiceCtx Context)
+        {
+            long InputPosition  = Context.Request.GetBufferType0x21Position();
+            long OutputPosition = Context.Request.GetBufferType0x22Position();
+
+            int Size = Context.Memory.ReadInt32(InputPosition);
+
+            int Handle = AddNvMap(Context, new NvMap(Size));
+
+            Context.Memory.WriteInt32(OutputPosition, Handle);
+        }
+
+        private int AddNvMap(ServiceCtx Context, NvMap Map)
+        {
+            return NvMaps[Context.Process].Add(Map);
+        }
+
+        public NvMap GetNvMap(ServiceCtx Context, int Handle)
+        {
+            return NvMaps[Context.Process].GetData<NvMap>(Handle);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs
index f3dd1f4718..570cef6827 100644
--- a/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs
+++ b/Ryujinx.Core/OsHle/Services/Nv/NvMap.cs
@@ -9,5 +9,12 @@ namespace Ryujinx.Core.OsHle.Services.Nv
         public int  Kind;
         public long CpuAddress;
         public long GpuAddress;
+
+        public NvMap() { }
+
+        public NvMap(int Size)
+        {
+            this.Size = Size;
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs
index af88412a29..e4aef20606 100644
--- a/Ryujinx.Graphics/Gal/IGalRenderer.cs
+++ b/Ryujinx.Graphics/Gal/IGalRenderer.cs
@@ -44,6 +44,8 @@ namespace Ryujinx.Graphics.Gal
 
         void SetViewport(int X, int Y, int Width, int Height);
 
+        void GetFrameBufferData(long Tag, Action<byte[]> Callback);
+
         //Rasterizer
         void ClearBuffers(int RtIndex, GalClearBufferFlags Flags);
 
@@ -51,7 +53,7 @@ namespace Ryujinx.Graphics.Gal
 
         void SetIndexArray(byte[] Buffer, GalIndexFormat Format);
 
-        void DrawArrays(int VbIndex, GalPrimitiveType PrimType);
+        void DrawArrays(int VbIndex, int First, int PrimCount, GalPrimitiveType PrimType);
 
         void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType);
 
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
index 05a7288a8a..8f265f5442 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
@@ -270,6 +270,31 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             }
         }
 
+        public void GetBufferData(long Tag, Action<byte[]> Callback)
+        {
+            if (Fbs.TryGetValue(Tag, out FrameBuffer Fb))
+            {
+                GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, Fb.Handle);
+
+                byte[] Data = new byte[Fb.Width * Fb.Height * 4];
+
+                (PixelFormat Format, PixelType Type) = OGLEnumConverter.GetTextureFormat(GalTextureFormat.A8B8G8R8);
+
+                GL.ReadPixels(
+                    0,
+                    0,
+                    Fb.Width,
+                    Fb.Height,
+                    Format,
+                    Type,
+                    Data);
+
+                Callback(Data);
+
+                GL.BindFramebuffer(FramebufferTarget.ReadFramebuffer, CurrFbHandle);
+            }
+        }
+
         private void SetViewport(Rect Viewport)
         {
             GL.Viewport(
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
index 9e0d45233d..b1504563f4 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
@@ -48,8 +48,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             public int VaoHandle;
             public int VboHandle;
-
-            public int PrimCount;
         }
 
         private struct IbInfo
@@ -102,8 +100,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             EnsureVbInitialized(VbIndex);
 
-            VertexBuffers[VbIndex].PrimCount = Buffer.Length / Stride;
-
             VbInfo Vb = VertexBuffers[VbIndex];
 
             IntPtr Length = new IntPtr(Buffer.Length);
@@ -171,29 +167,24 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
         }
 
-        public void DrawArrays(int VbIndex, GalPrimitiveType PrimType)
+        public void DrawArrays(int VbIndex, int First, int PrimCount, GalPrimitiveType PrimType)
         {
-            VbInfo Vb = VertexBuffers[VbIndex];
-
-            if (Vb.PrimCount == 0)
+            if (PrimCount == 0)
             {
                 return;
             }
 
+            VbInfo Vb = VertexBuffers[VbIndex];
+
             GL.BindVertexArray(Vb.VaoHandle);
 
-            GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), 0, Vb.PrimCount);
+            GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), First, PrimCount);
         }
 
         public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType)
         {
             VbInfo Vb = VertexBuffers[VbIndex];
 
-            if (Vb.PrimCount == 0)
-            {
-                return;
-            }
-
             PrimitiveType Mode = OGLEnumConverter.GetPrimitiveType(PrimType);
 
             GL.BindVertexArray(Vb.VaoHandle);
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs
index cf2da91c55..f941057375 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs
@@ -146,6 +146,11 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             ActionsQueue.Enqueue(() => FrameBuffer.SetViewport(X, Y, Width, Height));
         }
 
+        public void GetFrameBufferData(long Tag, Action<byte[]> Callback)
+        {
+            ActionsQueue.Enqueue(() => FrameBuffer.GetBufferData(Tag, Callback));
+        }
+
         public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags)
         {
             ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags));
@@ -173,14 +178,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format));
         }
 
-        public void DrawArrays(int VbIndex, GalPrimitiveType PrimType)
+        public void DrawArrays(int VbIndex, int First, int PrimCount, GalPrimitiveType PrimType)
         {
             if ((uint)VbIndex > 31)
             {
                 throw new ArgumentOutOfRangeException(nameof(VbIndex));
             }
 
-            ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, PrimType));
+            ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, First, PrimCount, PrimType));
         }
 
         public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType)
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
index d7173bcd63..c22f59264c 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -31,51 +31,53 @@ namespace Ryujinx.Graphics.Gal.Shader
         {
             InstsExpr = new Dictionary<ShaderIrInst, GetInstExpr>()
             {
-                { ShaderIrInst.And,   GetAndExpr   },
-                { ShaderIrInst.Asr,   GetAsrExpr   },
-                { ShaderIrInst.Band,  GetBandExpr  },
-                { ShaderIrInst.Bnot,  GetBnotExpr  },
-                { ShaderIrInst.Ceil,  GetCeilExpr  },
-                { ShaderIrInst.Ceq,   GetCeqExpr   },
-                { ShaderIrInst.Cge,   GetCgeExpr   },
-                { ShaderIrInst.Cgt,   GetCgtExpr   },
-                { ShaderIrInst.Clamp, GetClampExpr },
-                { ShaderIrInst.Cle,   GetCleExpr   },
-                { ShaderIrInst.Clt,   GetCltExpr   },
-                { ShaderIrInst.Cne,   GetCneExpr   },
-                { ShaderIrInst.Exit,  GetExitExpr  },
-                { ShaderIrInst.Fabs,  GetFabsExpr  },
-                { ShaderIrInst.Fadd,  GetFaddExpr  },
-                { ShaderIrInst.Fceq,  GetCeqExpr   },
-                { ShaderIrInst.Fcge,  GetCgeExpr   },
-                { ShaderIrInst.Fcgt,  GetCgtExpr   },
-                { ShaderIrInst.Fcle,  GetCleExpr   },
-                { ShaderIrInst.Fclt,  GetCltExpr   },
-                { ShaderIrInst.Fcne,  GetCneExpr   },
-                { ShaderIrInst.Fcos,  GetFcosExpr  },
-                { ShaderIrInst.Fex2,  GetFex2Expr  },
-                { ShaderIrInst.Ffma,  GetFfmaExpr  },
-                { ShaderIrInst.Flg2,  GetFlg2Expr  },
-                { ShaderIrInst.Floor, GetFloorExpr },
-                { ShaderIrInst.Fmul,  GetFmulExpr  },
-                { ShaderIrInst.Fneg,  GetFnegExpr  },
-                { ShaderIrInst.Frcp,  GetFrcpExpr  },
-                { ShaderIrInst.Frsq,  GetFrsqExpr  },
-                { ShaderIrInst.Fsin,  GetFsinExpr  },
-                { ShaderIrInst.Ftos,  GetFtosExpr  },
-                { ShaderIrInst.Ftou,  GetFtouExpr  },
-                { ShaderIrInst.Ipa,   GetIpaExpr   },
-                { ShaderIrInst.Kil,   GetKilExpr   },
-                { ShaderIrInst.Lsr,   GetLsrExpr   },
-                { ShaderIrInst.Not,   GetNotExpr   },
-                { ShaderIrInst.Or,    GetOrExpr    },
-                { ShaderIrInst.Stof,  GetStofExpr  },
-                { ShaderIrInst.Texq,  GetTexqExpr  },
-                { ShaderIrInst.Texs,  GetTexsExpr  },
-                { ShaderIrInst.Trunc, GetTruncExpr },
-                { ShaderIrInst.Txlf,  GetTxlfExpr  },
-                { ShaderIrInst.Utof,  GetUtofExpr  },
-                { ShaderIrInst.Xor,   GetXorExpr   }
+                { ShaderIrInst.And,    GetAndExpr    },
+                { ShaderIrInst.Asr,    GetAsrExpr    },
+                { ShaderIrInst.Band,   GetBandExpr   },
+                { ShaderIrInst.Bnot,   GetBnotExpr   },
+                { ShaderIrInst.Ceil,   GetCeilExpr   },
+                { ShaderIrInst.Ceq,    GetCeqExpr    },
+                { ShaderIrInst.Cge,    GetCgeExpr    },
+                { ShaderIrInst.Cgt,    GetCgtExpr    },
+                { ShaderIrInst.Clamps, GetClampsExpr },
+                { ShaderIrInst.Clampu, GetClampuExpr },
+                { ShaderIrInst.Cle,    GetCleExpr    },
+                { ShaderIrInst.Clt,    GetCltExpr    },
+                { ShaderIrInst.Cne,    GetCneExpr    },
+                { ShaderIrInst.Exit,   GetExitExpr   },
+                { ShaderIrInst.Fabs,   GetFabsExpr   },
+                { ShaderIrInst.Fadd,   GetFaddExpr   },
+                { ShaderIrInst.Fceq,   GetCeqExpr    },
+                { ShaderIrInst.Fcge,   GetCgeExpr    },
+                { ShaderIrInst.Fcgt,   GetCgtExpr    },
+                { ShaderIrInst.Fclamp, GetFclampExpr },
+                { ShaderIrInst.Fcle,   GetCleExpr    },
+                { ShaderIrInst.Fclt,   GetCltExpr    },
+                { ShaderIrInst.Fcne,   GetCneExpr    },
+                { ShaderIrInst.Fcos,   GetFcosExpr   },
+                { ShaderIrInst.Fex2,   GetFex2Expr   },
+                { ShaderIrInst.Ffma,   GetFfmaExpr   },
+                { ShaderIrInst.Flg2,   GetFlg2Expr   },
+                { ShaderIrInst.Floor,  GetFloorExpr  },
+                { ShaderIrInst.Fmul,   GetFmulExpr   },
+                { ShaderIrInst.Fneg,   GetFnegExpr   },
+                { ShaderIrInst.Frcp,   GetFrcpExpr   },
+                { ShaderIrInst.Frsq,   GetFrsqExpr   },
+                { ShaderIrInst.Fsin,   GetFsinExpr   },
+                { ShaderIrInst.Ftos,   GetFtosExpr   },
+                { ShaderIrInst.Ftou,   GetFtouExpr   },
+                { ShaderIrInst.Ipa,    GetIpaExpr    },
+                { ShaderIrInst.Kil,    GetKilExpr    },
+                { ShaderIrInst.Lsr,    GetLsrExpr    },
+                { ShaderIrInst.Not,    GetNotExpr    },
+                { ShaderIrInst.Or,     GetOrExpr     },
+                { ShaderIrInst.Stof,   GetStofExpr   },
+                { ShaderIrInst.Texq,   GetTexqExpr   },
+                { ShaderIrInst.Texs,   GetTexsExpr   },
+                { ShaderIrInst.Trunc,  GetTruncExpr  },
+                { ShaderIrInst.Txlf,   GetTxlfExpr   },
+                { ShaderIrInst.Utof,   GetUtofExpr   },
+                { ShaderIrInst.Xor,    GetXorExpr    }
             };
         }
 
@@ -478,7 +480,19 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private string GetCeilExpr(ShaderIrOp Op) => GetUnaryCall(Op, "ceil");
 
-        private string GetClampExpr(ShaderIrOp Op) => GetTernaryCall(Op, "clamp");
+        private string GetClampsExpr(ShaderIrOp Op)
+        {
+            return "clamp(" + GetOperExpr(Op, Op.OperandA) + ", " +
+                              GetOperExpr(Op, Op.OperandB) + ", " +
+                              GetOperExpr(Op, Op.OperandC) + ")";
+        }
+
+        private string GetClampuExpr(ShaderIrOp Op)
+        {
+            return "int(clamp(uint(" + GetOperExpr(Op, Op.OperandA) + "), " +
+                             "uint(" + GetOperExpr(Op, Op.OperandB) + "), " +
+                             "uint(" + GetOperExpr(Op, Op.OperandC) + ")))";
+        }
 
         private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<");
         private string GetCeqExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "==");
@@ -499,6 +513,8 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private string GetFfmaExpr(ShaderIrOp Op) => GetTernaryExpr(Op, "*", "+");
 
+        private string GetFclampExpr(ShaderIrOp Op) => GetTernaryCall(Op, "clamp");
+
         private string GetFlg2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "log2");
 
         private string GetFloorExpr(ShaderIrOp Op) => GetUnaryCall(Op, "floor");
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
index 830aeb8cb1..42609bcefc 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
@@ -66,6 +66,21 @@ namespace Ryujinx.Graphics.Gal.Shader
             EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fmul);
         }
 
+        public static void Fset_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitFset(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void Fset_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitFset(Block, OpCode, ShaderOper.Immf);
+        }
+
+        public static void Fset_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitFset(Block, OpCode, ShaderOper.RR);
+        }
+
         public static void Fsetp_C(ShaderIrBlock Block, long OpCode)
         {
             EmitFsetp(Block, OpCode, ShaderOper.CR);
@@ -279,6 +294,78 @@ namespace Ryujinx.Graphics.Gal.Shader
             Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
         }
 
+        private static void EmitFset(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            EmitSet(Block, OpCode, true, Oper);
+        }
+
+        private static void EmitIset(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            EmitSet(Block, OpCode, false, Oper);
+        }
+
+        private static void EmitSet(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper)
+        {
+            bool Na = ((OpCode >> 43) & 1) != 0;
+            bool Ab = ((OpCode >> 44) & 1) != 0;
+            bool Nb = ((OpCode >> 53) & 1) != 0;
+            bool Aa = ((OpCode >> 54) & 1) != 0;
+
+            ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:   OperB = GetOperCbuf34   (OpCode); break;
+                case ShaderOper.Imm:  OperB = GetOperImm19_20 (OpCode); break;
+                case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
+                case ShaderOper.RR:   OperB = GetOperGpr20    (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            ShaderIrInst CmpInst;
+
+            if (IsFloat)
+            {
+                OperA = GetAluAbsNeg(OperA, Aa, Na);
+                OperB = GetAluAbsNeg(OperB, Ab, Nb);
+
+                CmpInst = GetCmpF(OpCode);
+            }
+            else
+            {
+                CmpInst = GetCmp(OpCode);
+            }
+
+            ShaderIrOp Op = new ShaderIrOp(CmpInst, OperA, OperB);
+
+            ShaderIrInst LopInst = GetBLop(OpCode);
+
+            ShaderIrOperPred PNode = GetOperPred39(OpCode);
+
+            ShaderIrOperImmf Imm0 = new ShaderIrOperImmf(0);
+            ShaderIrOperImmf Imm1 = new ShaderIrOperImmf(1);
+
+            ShaderIrNode Asg0 = new ShaderIrAsg(GetOperGpr0(OpCode), Imm0);
+            ShaderIrNode Asg1 = new ShaderIrAsg(GetOperGpr0(OpCode), Imm1);
+
+            if (LopInst != ShaderIrInst.Band || !PNode.IsConst)
+            {
+                ShaderIrOp Op2 = new ShaderIrOp(LopInst, Op, PNode);
+
+                Asg0 = new ShaderIrCond(Op2, Asg0, Not: true);
+                Asg1 = new ShaderIrCond(Op2, Asg1, Not: false);
+            }
+            else
+            {
+                Asg0 = new ShaderIrCond(Op, Asg0, Not: true);
+                Asg1 = new ShaderIrCond(Op, Asg1, Not: false);
+            }
+
+            Block.AddNode(GetPredNode(Asg0, OpCode));
+            Block.AddNode(GetPredNode(Asg1, OpCode));
+        }
+
         private static void EmitFsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
         {
             EmitSetp(Block, OpCode, true, Oper);
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
index 6d30cfed82..b9cf02f1f6 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
@@ -70,6 +70,21 @@ namespace Ryujinx.Graphics.Gal.Shader
             EmitI2f(Block, OpCode, ShaderOper.RR);
         }
 
+        public static void I2i_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitI2i(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void I2i_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitI2i(Block, OpCode, ShaderOper.Imm);
+        }
+
+        public static void I2i_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitI2i(Block, OpCode, ShaderOper.RR);
+        }
+
         public static void Mov_C(ShaderIrBlock Block, long OpCode)
         {
             ShaderIrOperCbuf Cbuf = GetOperCbuf34(OpCode);
@@ -183,7 +198,7 @@ namespace Ryujinx.Graphics.Gal.Shader
                 ShaderIrOperImmf IMin = new ShaderIrOperImmf(CMin);
                 ShaderIrOperImmf IMax = new ShaderIrOperImmf(CMax);
 
-                OperA = new ShaderIrOp(ShaderIrInst.Clamp, OperA, IMin, IMax);
+                OperA = new ShaderIrOp(ShaderIrInst.Fclamp, OperA, IMin, IMax);
             }
 
             ShaderIrInst Inst = Signed
@@ -252,6 +267,81 @@ namespace Ryujinx.Graphics.Gal.Shader
             Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
         }
 
+        private static void EmitI2i(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            IntType Type = GetIntType(OpCode);
+
+            if (Type == IntType.U64 ||
+                Type == IntType.S64)
+            {
+                //TODO: 64-bits support.
+                //Note: GLSL doesn't support 64-bits integers.
+                throw new NotImplementedException();
+            }
+
+            int Sel = (int)(OpCode >> 41) & 3;
+
+            bool NegA = ((OpCode >> 45) & 1) != 0;
+            bool AbsA = ((OpCode >> 49) & 1) != 0;
+            bool SatA = ((OpCode >> 50) & 1) != 0;
+
+            ShaderIrNode OperA;
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:   OperA = GetOperCbuf34   (OpCode); break;
+                case ShaderOper.Immf: OperA = GetOperImmf19_20(OpCode); break;
+                case ShaderOper.RR:   OperA = GetOperGpr20    (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            OperA = GetAluAbsNeg(OperA, AbsA, NegA);
+
+            bool Signed = Type >= IntType.S8;
+
+            int Shift = Sel * 8;
+
+            int Size = 8 << ((int)Type & 3);
+
+            if (Shift != 0)
+            {
+                OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift));
+            }
+
+            if (Size < 32)
+            {
+                uint Mask = uint.MaxValue >> (32 - Size);
+
+                if (SatA)
+                {
+                    uint CMin = 0;
+                    uint CMax = Mask;
+
+                    if (Signed)
+                    {
+                        uint HalfMask = Mask >> 1;
+
+                        CMin -= HalfMask + 1;
+                        CMax  = HalfMask;
+                    }
+
+                    ShaderIrOperImm IMin = new ShaderIrOperImm((int)CMin);
+                    ShaderIrOperImm IMax = new ShaderIrOperImm((int)CMax);
+
+                    OperA = new ShaderIrOp(Signed
+                        ? ShaderIrInst.Clamps
+                        : ShaderIrInst.Clampu, OperA, IMin, IMax);
+                }
+                else
+                {
+                    OperA = new ShaderIrOp(ShaderIrInst.And, OperA, new ShaderIrOperImm((int)Mask));
+                }
+            }
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode));
+        }
+
         private static IntType GetIntType(long OpCode)
         {
             bool Signed = ((OpCode >> 13) & 1) != 0;
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
index 1b72f64764..ce2b98fe97 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
@@ -13,7 +13,7 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         F_Start,
         Ceil,
-        Clamp,
+
         Fabs,
         Fadd,
         Fceq,
@@ -22,6 +22,7 @@ namespace Ryujinx.Graphics.Gal.Shader
         Fcgeu,
         Fcgt,
         Fcgtu,
+        Fclamp,
         Fcle,
         Fcleu,
         Fclt,
@@ -53,6 +54,8 @@ namespace Ryujinx.Graphics.Gal.Shader
         Ceq,
         Cge,
         Cgt,
+        Clamps,
+        Clampu,
         Cle,
         Clt,
         Cne,
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
index 762544cb90..65e249286b 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
@@ -6,11 +6,24 @@ namespace Ryujinx.Graphics.Gal.Shader
     {
         private const int EncodingBits = 14;
 
-        private static ShaderDecodeFunc[] OpCodes;
+        private class ShaderDecodeEntry
+        {
+            public ShaderDecodeFunc Func;
+
+            public int XBits;
+
+            public ShaderDecodeEntry(ShaderDecodeFunc Func, int XBits)
+            {
+                this.Func  = Func;
+                this.XBits = XBits;
+            }
+        }
+
+        private static ShaderDecodeEntry[] OpCodes;
 
         static ShaderOpCodeTable()
         {
-            OpCodes = new ShaderDecodeFunc[1 << EncodingBits];
+            OpCodes = new ShaderDecodeEntry[1 << EncodingBits];
 
 #region Instructions
             Set("111000110000xx", ShaderDecode.Exit);
@@ -31,12 +44,18 @@ namespace Ryujinx.Graphics.Gal.Shader
             Set("0100110001101x", ShaderDecode.Fmul_C);
             Set("0011100x01101x", ShaderDecode.Fmul_I);
             Set("0101110001101x", ShaderDecode.Fmul_R);
+            Set("0100100xxxxxxx", ShaderDecode.Fset_C);
+            Set("0011000xxxxxxx", ShaderDecode.Fset_I);
+            Set("01011000xxxxxx", ShaderDecode.Fset_R);
             Set("010010111011xx", ShaderDecode.Fsetp_C);
             Set("0011011x1011xx", ShaderDecode.Fsetp_I);
             Set("010110111011xx", ShaderDecode.Fsetp_R);
             Set("0100110010111x", ShaderDecode.I2f_C);
             Set("0011100x10111x", ShaderDecode.I2f_I);
             Set("0101110010111x", ShaderDecode.I2f_R);
+            Set("0100110011100x", ShaderDecode.I2i_C);
+            Set("0011100x11100x", ShaderDecode.I2i_I);
+            Set("0101110011100x", ShaderDecode.I2i_R);
             Set("11100000xxxxxx", ShaderDecode.Ipa);
             Set("010010110110xx", ShaderDecode.Isetp_C);
             Set("0011011x0110xx", ShaderDecode.Isetp_I);
@@ -91,6 +110,8 @@ namespace Ryujinx.Graphics.Gal.Shader
 
             XMask = ~XMask;
 
+            ShaderDecodeEntry Entry = new ShaderDecodeEntry(Func, XBits);
+
             for (int Index = 0; Index < (1 << XBits); Index++)
             {
                 Value &= XMask;
@@ -100,13 +121,16 @@ namespace Ryujinx.Graphics.Gal.Shader
                     Value |= ((Index >> X) & 1) << XPos[X];
                 }
 
-                OpCodes[Value] = Func;
+                if (OpCodes[Value] == null || OpCodes[Value].XBits > XBits)
+                {
+                    OpCodes[Value] = Entry;
+                }
             }
         }
 
         public static ShaderDecodeFunc GetDecoder(long OpCode)
         {
-            return OpCodes[(ulong)OpCode >> (64 - EncodingBits)];
+            return OpCodes[(ulong)OpCode >> (64 - EncodingBits)]?.Func;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpu.cs b/Ryujinx.Graphics/Gpu/NsGpu.cs
index 9a2e901288..e10889821e 100644
--- a/Ryujinx.Graphics/Gpu/NsGpu.cs
+++ b/Ryujinx.Graphics/Gpu/NsGpu.cs
@@ -7,10 +7,11 @@ namespace Ryujinx.Graphics.Gpu
     {
         public IGalRenderer Renderer { get; private set; }
 
-        internal NsGpuMemoryMgr MemoryMgr { get; private set; }
+        public NsGpuMemoryMgr MemoryMgr { get; private set; }
 
         public NvGpuFifo Fifo { get; private set; }
 
+        public NvGpuEngine2d Engine2d { get; private set; }
         public NvGpuEngine3d Engine3d { get; private set; }
 
         private Thread FifoProcessing;
@@ -25,6 +26,7 @@ namespace Ryujinx.Graphics.Gpu
 
             Fifo = new NvGpuFifo(this);
 
+            Engine2d = new NvGpuEngine2d(this);
             Engine3d = new NvGpuEngine3d(this);
 
             KeepRunning = true;
diff --git a/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs b/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs
index 7f50640d59..eff5178313 100644
--- a/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs
+++ b/Ryujinx.Graphics/Gpu/NsGpuMemoryMgr.cs
@@ -1,6 +1,6 @@
 namespace Ryujinx.Graphics.Gpu
 {
-    class NsGpuMemoryMgr
+    public class NsGpuMemoryMgr
     {
         private const long AddrSize   = 1L << 40;
 
@@ -50,12 +50,20 @@ namespace Ryujinx.Graphics.Gpu
             return GpuAddr;
         }
 
+        public void Unmap(long Position, long Size)
+        {
+            for (long Offset = 0; Offset < Size; Offset += PageSize)
+            {
+                SetPTAddr(Position + Offset, PteUnmapped);
+            }
+        }
+
         public long Map(long CpuAddr, long Size)
         {
             CpuAddr &= ~PageMask;
 
             long Position = GetFreePosition(Size);
-            
+
             if (Position != -1)
             {
                 for (long Offset = 0; Offset < Size; Offset += PageSize)
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine2d.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine2d.cs
new file mode 100644
index 0000000000..c2bee167d8
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine2d.cs
@@ -0,0 +1,158 @@
+using ChocolArm64.Memory;
+using Ryujinx.Graphics.Gal;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    public class NvGpuEngine2d : INvGpuEngine
+    {
+        private enum CopyOperation
+        {
+            SrcCopyAnd,
+            RopAnd,
+            Blend,
+            SrcCopy,
+            Rop,
+            SrcCopyPremult,
+            BlendPremult
+        }
+
+        public int[] Registers { get; private set; }
+
+        private NsGpu Gpu;
+
+        private Dictionary<int, NvGpuMethod> Methods;
+
+        public NvGpuEngine2d(NsGpu Gpu)
+        {
+            this.Gpu = Gpu;
+
+            Registers = new int[0xe00];
+
+            Methods = new Dictionary<int, NvGpuMethod>();
+
+            void AddMethod(int Meth, int Count, int Stride, NvGpuMethod Method)
+            {
+                while (Count-- > 0)
+                {
+                    Methods.Add(Meth, Method);
+
+                    Meth += Stride;
+                }
+            }
+
+            AddMethod(0xb5, 1, 1, TextureCopy);
+        }
+
+        public void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
+            {
+                Method(Memory, PBEntry);
+            }
+            else
+            {
+                WriteRegister(PBEntry);
+            }
+        }
+
+        private void TextureCopy(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            CopyOperation Operation = (CopyOperation)ReadRegister(NvGpuEngine2dReg.CopyOperation);
+
+            bool SrcLinear = ReadRegister(NvGpuEngine2dReg.SrcLinear) != 0;
+            int  SrcWidth  = ReadRegister(NvGpuEngine2dReg.SrcWidth);
+            int  SrcHeight = ReadRegister(NvGpuEngine2dReg.SrcHeight);
+
+            bool DstLinear = ReadRegister(NvGpuEngine2dReg.DstLinear) != 0;
+            int  DstWidth  = ReadRegister(NvGpuEngine2dReg.DstWidth);
+            int  DstHeight = ReadRegister(NvGpuEngine2dReg.DstHeight);
+            int  DstPitch  = ReadRegister(NvGpuEngine2dReg.DstPitch);
+            int  DstBlkDim = ReadRegister(NvGpuEngine2dReg.DstBlockDimensions);
+
+            TextureSwizzle DstSwizzle = DstLinear
+                ? TextureSwizzle.Pitch
+                : TextureSwizzle.BlockLinear;
+
+            int DstBlockHeight = 1 << ((DstBlkDim >> 4) & 0xf);
+
+            long Tag = MakeInt64From2xInt32(NvGpuEngine2dReg.SrcAddress);
+
+            TryGetCpuAddr(NvGpuEngine2dReg.SrcAddress, out long SrcAddress);
+            TryGetCpuAddr(NvGpuEngine2dReg.DstAddress, out long DstAddress);
+
+            bool IsFbTexture = Gpu.Engine3d.IsFrameBufferPosition(Tag);
+
+            if (IsFbTexture && DstLinear)
+            {
+                DstSwizzle = TextureSwizzle.BlockLinear;
+            }
+
+            Texture DstTexture = new Texture(
+                DstAddress,
+                DstWidth,
+                DstHeight,
+                DstBlockHeight,
+                DstBlockHeight,
+                DstSwizzle,
+                GalTextureFormat.A8B8G8R8);
+
+            if (IsFbTexture)
+            {
+                Gpu.Renderer.GetFrameBufferData(Tag, (byte[] Buffer) =>
+                {
+                    CopyTexture(Memory, DstTexture, Buffer);
+                });
+            }
+            else
+            {
+                long Size = SrcWidth * SrcHeight * 4;
+
+                byte[] Buffer = AMemoryHelper.ReadBytes(Memory, SrcAddress, Size);
+
+                CopyTexture(Memory, DstTexture, Buffer);
+            }
+        }
+
+        private void CopyTexture(AMemory Memory, Texture Texture, byte[] Buffer)
+        {
+            TextureWriter.Write(Memory, Texture, Buffer);
+        }
+
+        private bool TryGetCpuAddr(NvGpuEngine2dReg Reg, out long Position)
+        {
+            Position = MakeInt64From2xInt32(Reg);
+
+            Position = Gpu.GetCpuAddr(Position);
+
+            return Position != -1;
+        }
+
+        private long MakeInt64From2xInt32(NvGpuEngine2dReg Reg)
+        {
+            return
+                (long)Registers[(int)Reg + 0] << 32 |
+                (uint)Registers[(int)Reg + 1];
+        }
+
+        private void WriteRegister(NsGpuPBEntry PBEntry)
+        {
+            int ArgsCount = PBEntry.Arguments.Count;
+
+            if (ArgsCount > 0)
+            {
+                Registers[PBEntry.Method] = PBEntry.Arguments[ArgsCount - 1];
+            }
+        }
+
+        private int ReadRegister(NvGpuEngine2dReg Reg)
+        {
+            return Registers[(int)Reg];
+        }
+
+        private void WriteRegister(NvGpuEngine2dReg Reg, int Value)
+        {
+            Registers[(int)Reg] = Value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine2dReg.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine2dReg.cs
new file mode 100644
index 0000000000..903baca87c
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine2dReg.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    enum NvGpuEngine2dReg
+    {
+        DstFormat          = 0x80,
+        DstLinear          = 0x81,
+        DstBlockDimensions = 0x82,
+        DstDepth           = 0x83,
+        DstLayer           = 0x84,
+        DstPitch           = 0x85,
+        DstWidth           = 0x86,
+        DstHeight          = 0x87,
+        DstAddress         = 0x88,
+        SrcFormat          = 0x8c,
+        SrcLinear          = 0x8d,
+        SrcBlockDimensions = 0x8e,
+        SrcDepth           = 0x8f,
+        SrcLayer           = 0x90,
+        SrcPitch           = 0x91,
+        SrcWidth           = 0x92,
+        SrcHeight          = 0x93,
+        SrcAddress         = 0x94,
+        CopyOperation      = 0xab
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
index 88ad763349..bf04db367f 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
@@ -360,6 +360,9 @@ namespace Ryujinx.Graphics.Gpu
 
             for (int Index = 0; Index < 32; Index++)
             {
+                int VertexFirst = ReadRegister(NvGpuEngine3dReg.VertexArrayFirst);
+                int VertexCount = ReadRegister(NvGpuEngine3dReg.VertexArrayCount);
+
                 int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4);
 
                 bool Enable = (Control & 0x1000) != 0;
@@ -394,7 +397,7 @@ namespace Ryujinx.Graphics.Gpu
                 }
                 else
                 {
-                    Gpu.Renderer.DrawArrays(Index, PrimType);
+                    Gpu.Renderer.DrawArrays(Index, VertexFirst, VertexCount, PrimType);
                 }
             }
         }
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
index 605ca9dab5..0d995619d7 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
@@ -12,6 +12,8 @@ namespace Ryujinx.Graphics.Gpu
         ViewportTranslateX   = 0x283,
         ViewportTranslateY   = 0x284,
         ViewportTranslateZ   = 0x285,
+        VertexArrayFirst     = 0x35d,
+        VertexArrayCount     = 0x35e,
         VertexAttribNFormat  = 0x458,
         IBlendEnable         = 0x4b9,
         BlendSeparateAlpha   = 0x4cf,
diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs
index df765895c4..68c2902a53 100644
--- a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs
+++ b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs
@@ -139,11 +139,17 @@ namespace Ryujinx.Graphics.Gpu
             {
                 switch (SubChannels[PBEntry.SubChannel])
                 {
+                    case NvGpuEngine._2d: Call2dMethod(Memory, PBEntry); break;
                     case NvGpuEngine._3d: Call3dMethod(Memory, PBEntry); break;
                 }
             }
         }
 
+        private void Call2dMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            Gpu.Engine2d.CallMethod(Memory, PBEntry);
+        }
+
         private void Call3dMethod(AMemory Memory, NsGpuPBEntry PBEntry)
         {
             if (PBEntry.Method < 0xe00)
diff --git a/Ryujinx.Graphics/Gpu/TextureHelper.cs b/Ryujinx.Graphics/Gpu/TextureHelper.cs
new file mode 100644
index 0000000000..d3c2ac14b5
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/TextureHelper.cs
@@ -0,0 +1,23 @@
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    static class TextureHelper
+    {
+        public static ISwizzle GetSwizzle(Texture Texture, int Width, int Bpp)
+        {
+            switch (Texture.Swizzle)
+            {
+                case TextureSwizzle.Pitch:
+                case TextureSwizzle.PitchColorKey:
+                     return new LinearSwizzle(Texture.Pitch, Bpp);
+
+                case TextureSwizzle.BlockLinear:
+                case TextureSwizzle.BlockLinearColorKey:
+                    return new BlockLinearSwizzle(Width, Bpp, Texture.BlockHeight);
+            }
+
+            throw new NotImplementedException(Texture.Swizzle.ToString());
+        }
+    }
+}
diff --git a/Ryujinx.Graphics/Gpu/TextureReader.cs b/Ryujinx.Graphics/Gpu/TextureReader.cs
index 4c3b4fb17e..17fd95c5c5 100644
--- a/Ryujinx.Graphics/Gpu/TextureReader.cs
+++ b/Ryujinx.Graphics/Gpu/TextureReader.cs
@@ -30,7 +30,7 @@ namespace Ryujinx.Graphics.Gpu
 
             byte[] Output = new byte[Width * Height * 2];
 
-            ISwizzle Swizzle = GetSwizzle(Texture, Width, 2);
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
 
             fixed (byte* BuffPtr = Output)
             {
@@ -59,7 +59,7 @@ namespace Ryujinx.Graphics.Gpu
 
             byte[] Output = new byte[Width * Height * 4];
 
-            ISwizzle Swizzle = GetSwizzle(Texture, Width, 4);
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4);
 
             fixed (byte* BuffPtr = Output)
             {
@@ -88,7 +88,7 @@ namespace Ryujinx.Graphics.Gpu
 
             byte[] Output = new byte[Width * Height * 8];
 
-            ISwizzle Swizzle = GetSwizzle(Texture, Width, 8);
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8);
 
             fixed (byte* BuffPtr = Output)
             {
@@ -117,7 +117,7 @@ namespace Ryujinx.Graphics.Gpu
 
             byte[] Output = new byte[Width * Height * 16];
 
-            ISwizzle Swizzle = GetSwizzle(Texture, Width, 16);
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16);
 
             fixed (byte* BuffPtr = Output)
             {
@@ -140,21 +140,5 @@ namespace Ryujinx.Graphics.Gpu
 
             return Output;
         }
-
-        private static ISwizzle GetSwizzle(Texture Texture, int Width, int Bpp)
-        {
-            switch (Texture.Swizzle)
-            {
-                case TextureSwizzle.Pitch:
-                case TextureSwizzle.PitchColorKey:
-                     return new LinearSwizzle(Texture.Pitch, Bpp);
-
-                case TextureSwizzle.BlockLinear:
-                case TextureSwizzle.BlockLinearColorKey:
-                    return new BlockLinearSwizzle(Width, Bpp, Texture.BlockHeight);
-            }
-
-            throw new NotImplementedException(Texture.Swizzle.ToString());
-        }
     }
 }
diff --git a/Ryujinx.Graphics/Gpu/TextureWriter.cs b/Ryujinx.Graphics/Gpu/TextureWriter.cs
new file mode 100644
index 0000000000..2f25de73fc
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/TextureWriter.cs
@@ -0,0 +1,45 @@
+using ChocolArm64.Memory;
+using Ryujinx.Graphics.Gal;
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    public static class TextureWriter
+    {
+        public static void Write(AMemory Memory, Texture Texture, byte[] Data)
+        {
+            switch (Texture.Format)
+            {
+                case GalTextureFormat.A8B8G8R8: Write4Bpp(Memory, Texture, Data); break;
+
+                default:
+                    throw new NotImplementedException(Texture.Format.ToString());
+            }
+        }
+
+        private unsafe static void Write4Bpp(AMemory Memory, Texture Texture, byte[] Data)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 4);
+
+            fixed (byte* BuffPtr = Data)
+            {
+                long InOffs = 0;
+
+                for (int Y = 0; Y < Height; Y++)
+                for (int X = 0; X < Width;  X++)
+                {
+                    long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
+
+                    int Pixel = *(int*)(BuffPtr + InOffs);
+
+                    Memory.WriteInt32Unchecked(Texture.Position + Offset, Pixel);
+
+                    InOffs += 4;
+                }
+            }
+        }
+    }
+}
diff --git a/Ryujinx/Config.cs b/Ryujinx/Config.cs
index d5364edcb2..86b74c9667 100644
--- a/Ryujinx/Config.cs
+++ b/Ryujinx/Config.cs
@@ -22,14 +22,11 @@ namespace Ryujinx
 
             AOptimizations.DisableMemoryChecks = !Convert.ToBoolean(Parser.Value("Enable_Memory_Checks"));
 
-            Console.WriteLine(Parser.Value("Logging_Enable_Warn"));
-
-            bool LoggingEnableDebug = Convert.ToBoolean(Parser.Value("Logging_Enable_Debug"));
-            bool LoggingEnableStub  = Convert.ToBoolean(Parser.Value("Logging_Enable_Stub"));
-            bool LoggingEnableInfo  = Convert.ToBoolean(Parser.Value("Logging_Enable_Info"));
-            bool LoggingEnableTrace = Convert.ToBoolean(Parser.Value("Logging_Enable_Trace"));
-            bool LoggingEnableWarn  = Convert.ToBoolean(Parser.Value("Logging_Enable_Warn"));
-            bool LoggingEnableError = Convert.ToBoolean(Parser.Value("Logging_Enable_Error"));
+            Log.SetEnable(LogLevel.Debug,   Convert.ToBoolean(Parser.Value("Logging_Enable_Debug")));
+            Log.SetEnable(LogLevel.Stub,    Convert.ToBoolean(Parser.Value("Logging_Enable_Stub")));
+            Log.SetEnable(LogLevel.Info,    Convert.ToBoolean(Parser.Value("Logging_Enable_Info")));
+            Log.SetEnable(LogLevel.Warning, Convert.ToBoolean(Parser.Value("Logging_Enable_Warn")));
+            Log.SetEnable(LogLevel.Error,   Convert.ToBoolean(Parser.Value("Logging_Enable_Error")));
 
             string[] FilteredLogClasses = Parser.Value("Logging_Filtered_Classes").Split(',', StringSplitOptions.RemoveEmptyEntries);