diff --git a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
index 8568e8f291..e41f03a430 100644
--- a/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
+++ b/Ryujinx.Core/OsHle/Services/Nv/INvDrvServices.cs
@@ -538,9 +538,9 @@ namespace Ryujinx.Core.OsHle.Services.Nv
                 {
                     byte[] Data = AMemoryHelper.ReadBytes(Context.Memory, CpuAddr, Size);
 
-                    NsGpuPBEntry[] PushBuffer = NsGpuPBEntry.DecodePushBuffer(Data);
+                    NsGpuPBEntry[] PushBuffer = NvGpuPushBuffer.Decode(Data);
 
-                    Context.Ns.Gpu.ProcessPushBuffer(PushBuffer, Context.Memory);
+                    Context.Ns.Gpu.Fifo.PushBuffer(Context.Memory, PushBuffer);
                 }
             }
 
@@ -680,8 +680,8 @@ namespace Ryujinx.Core.OsHle.Services.Nv
             }
 
             Map.CpuAddress = Addr;
-            Map.Align   = Align;
-            Map.Kind    = Kind;
+            Map.Align      = Align;
+            Map.Kind       = Kind;
 
             return 0;
         }
diff --git a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs
index 4301c0e6da..45b99ead1b 100644
--- a/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs
+++ b/Ryujinx.Core/OsHle/Services/Vi/NvFlinger.cs
@@ -244,12 +244,16 @@ namespace Ryujinx.Core.OsHle.Services.Android
         {
             int Slot = ParcelReader.ReadInt32();
 
-            int  BufferCount = ParcelReader.ReadInt32();
-            long BufferSize  = ParcelReader.ReadInt64();
+            int BufferCount = ParcelReader.ReadInt32();
 
-            BufferQueue[Slot].State = BufferState.Free;
+            if (BufferCount > 0)
+            {
+                long BufferSize = ParcelReader.ReadInt64();
 
-            BufferQueue[Slot].Data = new GbpBuffer(ParcelReader);
+                BufferQueue[Slot].State = BufferState.Free;
+
+                BufferQueue[Slot].Data = new GbpBuffer(ParcelReader);
+            }
 
             return MakeReplyParcel(Context, 0);
         }
diff --git a/Ryujinx.Graphics/Gal/GalBlendEquation.cs b/Ryujinx.Graphics/Gal/GalBlendEquation.cs
new file mode 100644
index 0000000000..d9f8e79934
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalBlendEquation.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalBlendEquation
+    {
+        FuncAdd             = 0x8006,
+        Min                 = 0x8007,
+        Max                 = 0x8008,
+        FuncSubtract        = 0x800a,
+        FuncReverseSubtract = 0x800b
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalBlendFactor.cs b/Ryujinx.Graphics/Gal/GalBlendFactor.cs
new file mode 100644
index 0000000000..de7d45fd48
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalBlendFactor.cs
@@ -0,0 +1,25 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalBlendFactor
+    {
+        Zero                  = 0x4000,
+        One                   = 0x4001,
+        SrcColor              = 0x4300,
+        OneMinusSrcColor      = 0x4301,
+        SrcAlpha              = 0x4302,
+        OneMinusSrcAlpha      = 0x4303,
+        DstAlpha              = 0x4304,
+        OneMinusDstAlpha      = 0x4305,
+        DstColor              = 0x4306,
+        OneMinusDstColor      = 0x4307,
+        SrcAlphaSaturate      = 0x4308,
+        ConstantColor         = 0xc001,
+        OneMinusConstantColor = 0xc002,
+        ConstantAlpha         = 0xc003,
+        OneMinusConstantAlpha = 0xc004,
+        Src1Color             = 0xc900,
+        OneMinusSrc1Color     = 0xc901,
+        Src1Alpha             = 0xc902,
+        OneMinusSrc1Alpha     = 0xc903
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs
new file mode 100644
index 0000000000..8565051cac
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalClearBufferFlags.cs
@@ -0,0 +1,15 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal
+{
+    [Flags]
+    public enum GalClearBufferFlags
+    {
+        Depth      = 1 << 0,
+        Stencil    = 1 << 1,
+        ColorRed   = 1 << 2,
+        ColorGreen = 1 << 3,
+        ColorBlue  = 1 << 4,
+        ColorAlpha = 1 << 5
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalColorF.cs b/Ryujinx.Graphics/Gal/GalColorF.cs
new file mode 100644
index 0000000000..7cfb171dcd
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalColorF.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public struct GalColorF
+    {
+        public float Red   { get; private set; }
+        public float Green { get; private set; }
+        public float Blue  { get; private set; }
+        public float Alpha { get; private set; }
+
+        public GalColorF(
+            float Red,
+            float Green,
+            float Blue,
+            float Alpha)
+        {
+            this.Red   = Red;
+            this.Green = Green;
+            this.Blue  = Blue;
+            this.Alpha = Alpha;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalIndexFormat.cs b/Ryujinx.Graphics/Gal/GalIndexFormat.cs
new file mode 100644
index 0000000000..71a50cdba8
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalIndexFormat.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalIndexFormat
+    {
+        Byte  = 0,
+        Int16 = 1,
+        Int32 = 2
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalShaderType.cs b/Ryujinx.Graphics/Gal/GalShaderType.cs
new file mode 100644
index 0000000000..eb5aaf889a
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalShaderType.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalShaderType
+    {
+        Vertex         = 0,
+        TessControl    = 1,
+        TessEvaluation = 2,
+        Geometry       = 3,
+        Fragment       = 4
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTexture.cs b/Ryujinx.Graphics/Gal/GalTexture.cs
new file mode 100644
index 0000000000..fcf1f1ad2b
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTexture.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public struct GalTexture
+    {
+        public byte[] Data;
+
+        public int Width;
+        public int Height;
+
+        public GalTextureFormat Format;
+
+        public GalTexture(byte[] Data, int Width, int Height, GalTextureFormat Format)
+        {
+            this.Data   = Data;
+            this.Width  = Width;
+            this.Height = Height;
+            this.Format = Format;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTextureFilter.cs b/Ryujinx.Graphics/Gal/GalTextureFilter.cs
new file mode 100644
index 0000000000..8e9669f00f
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTextureFilter.cs
@@ -0,0 +1,8 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalTextureFilter
+    {
+        Nearest = 1,
+        Linear  = 2
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs
new file mode 100644
index 0000000000..cf948526a5
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs
@@ -0,0 +1,10 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalTextureFormat
+    {
+        A8B8G8R8 = 0x8,
+        BC1      = 0x24,
+        BC2      = 0x25,
+        BC3      = 0x26
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs
new file mode 100644
index 0000000000..2123ec7d24
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTextureMipFilter.cs
@@ -0,0 +1,9 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalTextureMipFilter
+    {
+        None    = 1,
+        Nearest = 2,
+        Linear  = 3
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTextureSampler.cs b/Ryujinx.Graphics/Gal/GalTextureSampler.cs
new file mode 100644
index 0000000000..b9e5c7658d
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTextureSampler.cs
@@ -0,0 +1,33 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public struct GalTextureSampler
+    {
+        public GalTextureWrap AddressU { get; private set; }
+        public GalTextureWrap AddressV { get; private set; }
+        public GalTextureWrap AddressP { get; private set; }
+
+        public GalTextureFilter    MinFilter { get; private set; }
+        public GalTextureFilter    MagFilter { get; private set; }
+        public GalTextureMipFilter MipFilter { get; private set; }
+
+        public GalColorF BorderColor { get; private set; }
+
+        public GalTextureSampler(
+            GalTextureWrap      AddressU,
+            GalTextureWrap      AddressV,
+            GalTextureWrap      AddressP,
+            GalTextureFilter    MinFilter,
+            GalTextureFilter    MagFilter,
+            GalTextureMipFilter MipFilter,
+            GalColorF           BorderColor)
+        {
+            this.AddressU    = AddressU;
+            this.AddressV    = AddressV;
+            this.AddressP    = AddressP;
+            this.MinFilter   = MinFilter;
+            this.MagFilter   = MagFilter;
+            this.MipFilter   = MipFilter;
+            this.BorderColor = BorderColor;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalTextureWrap.cs b/Ryujinx.Graphics/Gal/GalTextureWrap.cs
new file mode 100644
index 0000000000..66e5315409
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/GalTextureWrap.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public enum GalTextureWrap
+    {
+        Repeat              = 0,
+        MirroredRepeat      = 1,
+        ClampToEdge         = 2,
+        ClampToBorder       = 3,
+        Clamp               = 4,
+        MirrorClampToEdge   = 5,
+        MirrorClampToBorder = 6,
+        MirrorClamp         = 7
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs
index dc38c59345..563e624d53 100644
--- a/Ryujinx.Graphics/Gal/GalVertexAttrib.cs
+++ b/Ryujinx.Graphics/Gal/GalVertexAttrib.cs
@@ -2,8 +2,6 @@ namespace Ryujinx.Graphics.Gal
 {
     public struct GalVertexAttrib
     {
-        public int  Index   { get; private set; }
-        public int  Buffer  { get; private set; }
         public bool IsConst { get; private set; }
         public int  Offset  { get; private set; }
 
@@ -13,16 +11,12 @@ namespace Ryujinx.Graphics.Gal
         public bool IsBgra { get; private set; }
 
         public GalVertexAttrib(
-            int                 Index,
-            int                 Buffer,
             bool                IsConst,
             int                 Offset,
             GalVertexAttribSize Size,
             GalVertexAttribType Type,
             bool                IsBgra)
         {
-            this.Index   = Index;
-            this.Buffer  = Buffer;
             this.IsConst = IsConst;
             this.Offset  = Offset;
             this.Size    = Size;
diff --git a/Ryujinx.Graphics/Gal/IGalRenderer.cs b/Ryujinx.Graphics/Gal/IGalRenderer.cs
index aa4eac4e14..99534672d1 100644
--- a/Ryujinx.Graphics/Gal/IGalRenderer.cs
+++ b/Ryujinx.Graphics/Gal/IGalRenderer.cs
@@ -1,4 +1,5 @@
 using System;
+using System.Collections.Generic;
 
 namespace Ryujinx.Graphics.Gal
 {
@@ -21,10 +22,56 @@ namespace Ryujinx.Graphics.Gal
             float OffsY,
             float Rotate);
 
-        void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs);
+        //Blend
+        void SetBlendEnable(bool Enable);
 
-        void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height);
+        void SetBlend(
+            GalBlendEquation Equation,
+            GalBlendFactor   FuncSrc,
+            GalBlendFactor   FuncDst);
 
-        void BindTexture(int Index);
+        void SetBlendSeparate(
+            GalBlendEquation EquationRgb,
+            GalBlendEquation EquationAlpha,
+            GalBlendFactor   FuncSrcRgb,
+            GalBlendFactor   FuncDstRgb,
+            GalBlendFactor   FuncSrcAlpha,
+            GalBlendFactor   FuncDstAlpha);
+
+        //Frame Buffer
+        void SetFb(int FbIndex, int Width, int Height);
+
+        void BindFrameBuffer(int FbIndex);
+
+        void DrawFrameBuffer(int FbIndex);
+
+        //Rasterizer
+        void ClearBuffers(int RtIndex, GalClearBufferFlags Flags);
+
+        void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs);
+
+        void SetIndexArray(byte[] Buffer, GalIndexFormat Format);
+
+        void DrawArrays(int VbIndex, GalPrimitiveType PrimType);
+
+        void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType);
+
+        //Shader
+        void CreateShader(long Tag, GalShaderType Type, byte[] Data);
+
+        IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag);
+
+        void SetConstBuffer(long Tag, int Cbuf, byte[] Data);
+
+        void SetUniform1(string UniformName, int Value);
+
+        void BindShader(long Tag);
+
+        void BindProgram();
+
+        //Texture
+        void SetTexture(int Index, GalTexture Tex);
+
+        void SetSampler(int Index, GalTextureSampler Sampler);
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs
index b761811f64..7e7725d611 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/FrameBuffer.cs
@@ -219,7 +219,7 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 PixelFormat.Rgba,
                 PixelType.UnsignedByte,
                 Pixels);
-            
+
             GL.ActiveTexture(TextureUnit.Texture0);
 
             GL.BindVertexArray(VaoHandle);
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs
new file mode 100644
index 0000000000..97ff8e13b4
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLBlend.cs
@@ -0,0 +1,49 @@
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    class OGLBlend
+    {
+        public void Enable()
+        {
+            GL.Enable(EnableCap.Blend);
+        }
+
+        public void Disable()
+        {
+            GL.Disable(EnableCap.Blend);
+        }
+
+        public void Set(
+            GalBlendEquation Equation,
+            GalBlendFactor   FuncSrc,
+            GalBlendFactor   FuncDst)
+        {
+            GL.BlendEquation(
+                OGLEnumConverter.GetBlendEquation(Equation));
+
+            GL.BlendFunc(
+                OGLEnumConverter.GetBlendFactorSrc(FuncSrc),
+                OGLEnumConverter.GetBlendFactorDst(FuncDst));
+        }
+
+        public void SetSeparate(
+            GalBlendEquation EquationRgb,
+            GalBlendEquation EquationAlpha,
+            GalBlendFactor   FuncSrcRgb,
+            GalBlendFactor   FuncDstRgb,
+            GalBlendFactor   FuncSrcAlpha,
+            GalBlendFactor   FuncDstAlpha)
+        {
+            GL.BlendEquationSeparate(
+                OGLEnumConverter.GetBlendEquation(EquationRgb),
+                OGLEnumConverter.GetBlendEquation(EquationAlpha));
+
+            GL.BlendFuncSeparate(
+                OGLEnumConverter.GetBlendFactorSrc(FuncSrcRgb),
+                OGLEnumConverter.GetBlendFactorDst(FuncDstRgb),
+                OGLEnumConverter.GetBlendFactorSrc(FuncSrcAlpha),
+                OGLEnumConverter.GetBlendFactorDst(FuncDstAlpha));
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
new file mode 100644
index 0000000000..6518de5fbf
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
@@ -0,0 +1,129 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    static class OGLEnumConverter
+    {
+        public static DrawElementsType GetDrawElementsType(GalIndexFormat Format)
+        {
+            switch (Format)
+            {
+                case GalIndexFormat.Byte:  return DrawElementsType.UnsignedByte;
+                case GalIndexFormat.Int16: return DrawElementsType.UnsignedShort;
+                case GalIndexFormat.Int32: return DrawElementsType.UnsignedInt;
+            }
+
+            throw new ArgumentException(nameof(Format));
+        }
+
+        public static PrimitiveType GetPrimitiveType(GalPrimitiveType Type)
+        {
+            switch (Type)
+            {
+                case GalPrimitiveType.Points:                 return PrimitiveType.Points;
+                case GalPrimitiveType.Lines:                  return PrimitiveType.Lines;
+                case GalPrimitiveType.LineLoop:               return PrimitiveType.LineLoop;
+                case GalPrimitiveType.LineStrip:              return PrimitiveType.LineStrip;
+                case GalPrimitiveType.Triangles:              return PrimitiveType.Triangles;
+                case GalPrimitiveType.TriangleStrip:          return PrimitiveType.TriangleStrip;
+                case GalPrimitiveType.TriangleFan:            return PrimitiveType.TriangleFan;
+                case GalPrimitiveType.Quads:                  return PrimitiveType.Quads;
+                case GalPrimitiveType.QuadStrip:              return PrimitiveType.QuadStrip;
+                case GalPrimitiveType.Polygon:                return PrimitiveType.Polygon;
+                case GalPrimitiveType.LinesAdjacency:         return PrimitiveType.LinesAdjacency;
+                case GalPrimitiveType.LineStripAdjacency:     return PrimitiveType.LineStripAdjacency;
+                case GalPrimitiveType.TrianglesAdjacency:     return PrimitiveType.TrianglesAdjacency;
+                case GalPrimitiveType.TriangleStripAdjacency: return PrimitiveType.TriangleStripAdjacency;
+                case GalPrimitiveType.Patches:                return PrimitiveType.Patches;
+            }
+
+            throw new ArgumentException(nameof(Type));
+        }
+
+        public static ShaderType GetShaderType(GalShaderType Type)
+        {
+            switch (Type)
+            {
+                case GalShaderType.Vertex:         return ShaderType.VertexShader;
+                case GalShaderType.TessControl:    return ShaderType.TessControlShader;
+                case GalShaderType.TessEvaluation: return ShaderType.TessEvaluationShader;
+                case GalShaderType.Geometry:       return ShaderType.GeometryShader;
+                case GalShaderType.Fragment:       return ShaderType.FragmentShader;
+            }
+
+            throw new ArgumentException(nameof(Type));
+        }
+
+        public static PixelInternalFormat GetCompressedTextureFormat(GalTextureFormat Format)
+        {
+            switch (Format)
+            {
+                case GalTextureFormat.BC1: return PixelInternalFormat.CompressedRgbaS3tcDxt1Ext;
+                case GalTextureFormat.BC2: return PixelInternalFormat.CompressedRgbaS3tcDxt3Ext;
+                case GalTextureFormat.BC3: return PixelInternalFormat.CompressedRgbaS3tcDxt5Ext;
+            }
+
+            throw new NotImplementedException(Format.ToString());
+        }
+
+        public static TextureWrapMode GetTextureWrapMode(GalTextureWrap Wrap)
+        {
+            switch (Wrap)
+            {
+                case GalTextureWrap.Repeat:              return TextureWrapMode.Repeat;
+                case GalTextureWrap.MirroredRepeat:      return TextureWrapMode.MirroredRepeat;
+                case GalTextureWrap.ClampToEdge:         return TextureWrapMode.ClampToEdge;
+                case GalTextureWrap.ClampToBorder:       return TextureWrapMode.ClampToBorder;
+                case GalTextureWrap.Clamp:               return TextureWrapMode.Clamp;
+
+                //TODO: Those needs extensions (and are currently wrong).
+                case GalTextureWrap.MirrorClampToEdge:   return TextureWrapMode.ClampToEdge;
+                case GalTextureWrap.MirrorClampToBorder: return TextureWrapMode.ClampToBorder;
+                case GalTextureWrap.MirrorClamp:         return TextureWrapMode.Clamp;
+            }
+
+            throw new ArgumentException(nameof(Wrap));
+        }
+
+        public static TextureMinFilter GetTextureMinFilter(
+            GalTextureFilter    MinFilter,
+            GalTextureMipFilter MipFilter)
+        {
+            //TODO: Mip (needs mipmap support first).
+            switch (MinFilter)
+            {
+                case GalTextureFilter.Nearest: return TextureMinFilter.Nearest;
+                case GalTextureFilter.Linear:  return TextureMinFilter.Linear;
+            }
+
+            throw new ArgumentException(nameof(MinFilter));
+        }
+
+        public static TextureMagFilter GetTextureMagFilter(GalTextureFilter Filter)
+        {
+            switch (Filter)
+            {
+                case GalTextureFilter.Nearest: return TextureMagFilter.Nearest;
+                case GalTextureFilter.Linear:  return TextureMagFilter.Linear;
+            }
+
+            throw new ArgumentException(nameof(Filter));
+        }
+
+        public static BlendEquationMode GetBlendEquation(GalBlendEquation BlendEquation)
+        {
+            return (BlendEquationMode)BlendEquation;
+        }
+
+        public static BlendingFactorSrc GetBlendFactorSrc(GalBlendFactor BlendFactor)
+        {
+            return (BlendingFactorSrc)(BlendFactor - 0x4000);
+        }
+
+        public static BlendingFactorDest GetBlendFactorDst(GalBlendFactor BlendFactor)
+        {
+            return (BlendingFactorDest)(BlendFactor - 0x4000);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
new file mode 100644
index 0000000000..f9c42ae014
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLFrameBuffer.cs
@@ -0,0 +1,182 @@
+using OpenTK;
+using OpenTK.Graphics.OpenGL;
+using System;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    class OGLFrameBuffer
+    {
+        private struct FrameBuffer
+        {
+            public int FbHandle;
+            public int RbHandle;
+            public int TexHandle;
+        }
+
+        private struct ShaderProgram
+        {
+            public int Handle;
+            public int VpHandle;
+            public int FpHandle;
+        }
+
+        private FrameBuffer[] Fbs;
+
+        private ShaderProgram Shader;
+
+        private bool IsInitialized;
+
+        private int VaoHandle;
+        private int VboHandle;
+
+        public OGLFrameBuffer()
+        {
+            Fbs = new FrameBuffer[16];
+
+            Shader = new ShaderProgram();
+        }
+
+        public void Set(int Index, int Width, int Height)
+        {
+            if (Fbs[Index].FbHandle != 0)
+            {
+                return;
+            }
+
+            Fbs[Index].FbHandle  = GL.GenFramebuffer();
+            Fbs[Index].RbHandle  = GL.GenRenderbuffer();
+            Fbs[Index].TexHandle = GL.GenTexture();
+
+            GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fbs[Index].FbHandle);
+
+            GL.BindRenderbuffer(RenderbufferTarget.Renderbuffer, Fbs[Index].RbHandle);
+
+            GL.RenderbufferStorage(RenderbufferTarget.Renderbuffer, RenderbufferStorage.Depth24Stencil8, 1280, 720);
+
+            GL.FramebufferRenderbuffer(FramebufferTarget.Framebuffer, FramebufferAttachment.DepthStencilAttachment, RenderbufferTarget.Renderbuffer, Fbs[Index].RbHandle);
+
+            GL.BindTexture(TextureTarget.Texture2D, Fbs[Index].TexHandle);
+
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
+
+            GL.TexImage2D(TextureTarget.Texture2D, 0, PixelInternalFormat.Rgba, 1280, 720, 0, PixelFormat.Rgba, PixelType.UnsignedByte, IntPtr.Zero);
+
+            GL.FramebufferTexture(FramebufferTarget.Framebuffer, FramebufferAttachment.ColorAttachment0, Fbs[Index].TexHandle, 0);
+
+            GL.DrawBuffer(DrawBufferMode.ColorAttachment0);
+        }
+
+        public void Bind(int Index)
+        {
+            if (Fbs[Index].FbHandle == 0)
+            {
+                return;
+            }
+
+            GL.BindFramebuffer(FramebufferTarget.Framebuffer, Fbs[Index].FbHandle);
+        }
+
+        public void Draw(int Index)
+        {
+            if (Fbs[Index].FbHandle == 0)
+            {
+                return;
+            }
+
+            EnsureInitialized();
+
+            GL.BindFramebuffer(FramebufferTarget.Framebuffer, 0);
+
+            GL.BindTexture(TextureTarget.Texture2D, Fbs[Index].TexHandle);
+
+            GL.ActiveTexture(TextureUnit.Texture0);
+
+            GL.BindVertexArray(VaoHandle);
+
+            GL.UseProgram(Shader.Handle);
+
+            GL.DrawArrays(PrimitiveType.TriangleStrip, 0, 4);
+        }
+
+        private void EnsureInitialized()
+        {
+            if (!IsInitialized)
+            {
+                IsInitialized = true;
+
+                SetupShader();
+                SetupVertex();
+            }
+        }
+
+        private void SetupShader()
+        {
+            Shader.VpHandle = GL.CreateShader(ShaderType.VertexShader);
+            Shader.FpHandle = GL.CreateShader(ShaderType.FragmentShader);
+
+            string VpSource = EmbeddedResource.GetString("GlFbVtxShader");
+            string FpSource = EmbeddedResource.GetString("GlFbFragShader");
+
+            GL.ShaderSource(Shader.VpHandle, VpSource);
+            GL.ShaderSource(Shader.FpHandle, FpSource);
+            GL.CompileShader(Shader.VpHandle);
+            GL.CompileShader(Shader.FpHandle);
+
+            Shader.Handle = GL.CreateProgram();
+
+            GL.AttachShader(Shader.Handle, Shader.VpHandle);
+            GL.AttachShader(Shader.Handle, Shader.FpHandle);
+            GL.LinkProgram(Shader.Handle);
+            GL.UseProgram(Shader.Handle);
+
+            Matrix2 Transform = Matrix2.CreateScale(1, -1);
+
+            int TexUniformLocation = GL.GetUniformLocation(Shader.Handle, "tex");
+
+            GL.Uniform1(TexUniformLocation, 0);
+
+            int WindowSizeUniformLocation = GL.GetUniformLocation(Shader.Handle, "window_size");
+
+            GL.Uniform2(WindowSizeUniformLocation, new Vector2(1280.0f, 720.0f));
+
+            int TransformUniformLocation = GL.GetUniformLocation(Shader.Handle, "transform");
+
+            GL.UniformMatrix2(TransformUniformLocation, false, ref Transform);
+        }
+
+        private void SetupVertex()
+        {
+            VaoHandle = GL.GenVertexArray();
+            VboHandle = GL.GenBuffer();
+
+            float[] Buffer = new float[]
+            {
+                -1,  1,  0,  0,
+                 1,  1,  1,  0,
+                -1, -1,  0,  1,
+                 1, -1,  1,  1
+            };
+
+            IntPtr Length = new IntPtr(Buffer.Length * 4);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+            GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+
+            GL.BindVertexArray(VaoHandle);
+
+            GL.EnableVertexAttribArray(0);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+            GL.VertexAttribPointer(0, 2, VertexAttribPointerType.Float, false, 16, 0);
+
+            GL.EnableVertexAttribArray(1);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, VboHandle);
+
+            GL.VertexAttribPointer(1, 2, VertexAttribPointerType.Float, false, 16, 8);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
new file mode 100644
index 0000000000..9e0d45233d
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLRasterizer.cs
@@ -0,0 +1,231 @@
+using OpenTK.Graphics.OpenGL;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    class OGLRasterizer
+    {
+        private static Dictionary<GalVertexAttribSize, int> AttribElements =
+                   new Dictionary<GalVertexAttribSize, int>()
+        {
+            { GalVertexAttribSize._32_32_32_32, 4 },
+            { GalVertexAttribSize._32_32_32,    3 },
+            { GalVertexAttribSize._16_16_16_16, 4 },
+            { GalVertexAttribSize._32_32,       2 },
+            { GalVertexAttribSize._16_16_16,    3 },
+            { GalVertexAttribSize._8_8_8_8,     4 },
+            { GalVertexAttribSize._16_16,       2 },
+            { GalVertexAttribSize._32,          1 },
+            { GalVertexAttribSize._8_8_8,       3 },
+            { GalVertexAttribSize._8_8,         2 },
+            { GalVertexAttribSize._16,          1 },
+            { GalVertexAttribSize._8,           1 },
+            { GalVertexAttribSize._10_10_10_2,  4 },
+            { GalVertexAttribSize._11_11_10,    3 }
+        };
+
+        private static Dictionary<GalVertexAttribSize, VertexAttribPointerType> AttribTypes =
+                   new Dictionary<GalVertexAttribSize, VertexAttribPointerType>()
+        {
+            { GalVertexAttribSize._32_32_32_32, VertexAttribPointerType.Int   },
+            { GalVertexAttribSize._32_32_32,    VertexAttribPointerType.Int   },
+            { GalVertexAttribSize._16_16_16_16, VertexAttribPointerType.Short },
+            { GalVertexAttribSize._32_32,       VertexAttribPointerType.Int   },
+            { GalVertexAttribSize._16_16_16,    VertexAttribPointerType.Short },
+            { GalVertexAttribSize._8_8_8_8,     VertexAttribPointerType.Byte  },
+            { GalVertexAttribSize._16_16,       VertexAttribPointerType.Short },
+            { GalVertexAttribSize._32,          VertexAttribPointerType.Int   },
+            { GalVertexAttribSize._8_8_8,       VertexAttribPointerType.Byte  },
+            { GalVertexAttribSize._8_8,         VertexAttribPointerType.Byte  },
+            { GalVertexAttribSize._16,          VertexAttribPointerType.Short },
+            { GalVertexAttribSize._8,           VertexAttribPointerType.Byte  },
+            { GalVertexAttribSize._10_10_10_2,  VertexAttribPointerType.Int   }, //?
+            { GalVertexAttribSize._11_11_10,    VertexAttribPointerType.Int   }  //?
+        };
+
+        private struct VbInfo
+        {
+            public int VaoHandle;
+            public int VboHandle;
+
+            public int PrimCount;
+        }
+
+        private struct IbInfo
+        {
+            public int IboHandle;
+            public int Count;
+
+            public DrawElementsType Type;
+        }
+
+        private VbInfo[] VertexBuffers;
+
+        private IbInfo IndexBuffer;
+
+        public OGLRasterizer()
+        {
+            VertexBuffers = new VbInfo[32];
+
+            IndexBuffer = new IbInfo();
+        }
+
+        public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags)
+        {
+            ClearBufferMask Mask = 0;
+
+            //OpenGL doesn't support clearing just a single color channel,
+            //so we can't just clear all channels...
+            if (Flags.HasFlag(GalClearBufferFlags.ColorRed)   &&
+                Flags.HasFlag(GalClearBufferFlags.ColorGreen) &&
+                Flags.HasFlag(GalClearBufferFlags.ColorBlue)  &&
+                Flags.HasFlag(GalClearBufferFlags.ColorAlpha))
+            {
+                Mask = ClearBufferMask.ColorBufferBit;
+            }
+
+            if (Flags.HasFlag(GalClearBufferFlags.Depth))
+            {
+                Mask |= ClearBufferMask.DepthBufferBit;
+            }
+
+            if (Flags.HasFlag(GalClearBufferFlags.Stencil))
+            {
+                Mask |= ClearBufferMask.StencilBufferBit;
+            }
+
+            GL.Clear(Mask);
+        }
+
+        public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs)
+        {
+            EnsureVbInitialized(VbIndex);
+
+            VertexBuffers[VbIndex].PrimCount = Buffer.Length / Stride;
+
+            VbInfo Vb = VertexBuffers[VbIndex];
+
+            IntPtr Length = new IntPtr(Buffer.Length);
+
+            GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
+            GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
+
+            GL.BindVertexArray(Vb.VaoHandle);
+
+            for (int Attr = 0; Attr < 16; Attr++)
+            {
+                GL.DisableVertexAttribArray(Attr);
+            }
+
+            for (int Index = 0; Index < Attribs.Length; Index++)
+            {
+                GalVertexAttrib Attrib = Attribs[Index];
+
+                GL.EnableVertexAttribArray(Index);
+
+                GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
+
+                bool Unsigned =
+                    Attrib.Type == GalVertexAttribType.Unorm ||
+                    Attrib.Type == GalVertexAttribType.Uint  ||
+                    Attrib.Type == GalVertexAttribType.Uscaled;
+
+                bool Normalize =
+                    Attrib.Type == GalVertexAttribType.Snorm ||
+                    Attrib.Type == GalVertexAttribType.Unorm;
+
+                VertexAttribPointerType Type = 0;
+
+                if (Attrib.Type == GalVertexAttribType.Float)
+                {
+                    Type = VertexAttribPointerType.Float;
+                }
+                else
+                {
+                    Type = AttribTypes[Attrib.Size] + (Unsigned ? 1 : 0);
+                }
+
+                int Size   = AttribElements[Attrib.Size];
+                int Offset = Attrib.Offset;
+
+                GL.VertexAttribPointer(Index, Size, Type, Normalize, Stride, Offset);
+            }
+
+            GL.BindVertexArray(0);
+        }
+
+        public void SetIndexArray(byte[] Buffer, GalIndexFormat Format)
+        {
+            EnsureIbInitialized();
+
+            IndexBuffer.Type = OGLEnumConverter.GetDrawElementsType(Format);
+
+            IndexBuffer.Count = Buffer.Length >> (int)Format;
+
+            IntPtr Length = new IntPtr(Buffer.Length);
+
+            GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle);
+            GL.BufferData(BufferTarget.ElementArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
+            GL.BindBuffer(BufferTarget.ElementArrayBuffer, 0);
+        }
+
+        public void DrawArrays(int VbIndex, GalPrimitiveType PrimType)
+        {
+            VbInfo Vb = VertexBuffers[VbIndex];
+
+            if (Vb.PrimCount == 0)
+            {
+                return;
+            }
+
+            GL.BindVertexArray(Vb.VaoHandle);
+
+            GL.DrawArrays(OGLEnumConverter.GetPrimitiveType(PrimType), 0, Vb.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);
+
+            GL.BindBuffer(BufferTarget.ElementArrayBuffer, IndexBuffer.IboHandle);
+
+            GL.DrawElements(Mode, IndexBuffer.Count, IndexBuffer.Type, First);
+        }
+
+        private void EnsureVbInitialized(int VbIndex)
+        {
+            VbInfo Vb = VertexBuffers[VbIndex];
+
+            if (Vb.VaoHandle == 0)
+            {
+                Vb.VaoHandle = GL.GenVertexArray();
+            }
+
+            if (Vb.VboHandle == 0)
+            {
+                Vb.VboHandle = GL.GenBuffer();
+            }
+
+            VertexBuffers[VbIndex] = Vb;
+        }
+
+        private void EnsureIbInitialized()
+        {
+            if (IndexBuffer.IboHandle == 0)
+            {
+                IndexBuffer.IboHandle = GL.GenBuffer();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
new file mode 100644
index 0000000000..6d6ac555d6
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLShader.cs
@@ -0,0 +1,253 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Graphics.Gal.Shader;
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    class OGLShader
+    {
+        private class ShaderStage : IDisposable
+        {
+            public int Handle { get; private set; }
+
+            public bool IsCompiled { get; private set; }
+
+            public GalShaderType Type { get; private set; }
+
+            public string Code { get; private set; }
+
+            public IEnumerable<ShaderDeclInfo> TextureUsage { get; private set; }
+            public IEnumerable<ShaderDeclInfo> UniformUsage { get; private set; }
+
+            public ShaderStage(
+                GalShaderType               Type,
+                string                      Code,
+                IEnumerable<ShaderDeclInfo> TextureUsage,
+                IEnumerable<ShaderDeclInfo> UniformUsage)
+            {
+                this.Type         = Type;
+                this.Code         = Code;
+                this.TextureUsage = TextureUsage;
+                this.UniformUsage = UniformUsage;
+            }
+
+            public void Compile()
+            {
+                if (Handle == 0)
+                {
+                    Handle = GL.CreateShader(OGLEnumConverter.GetShaderType(Type));
+
+                    CompileAndCheck(Handle, Code);
+                }
+            }
+
+            public void Dispose()
+            {
+                Dispose(true);
+            }
+
+            protected virtual void Dispose(bool Disposing)
+            {
+                if (Disposing && Handle != 0)
+                {
+                    GL.DeleteShader(Handle);
+
+                    Handle = 0;
+                }
+            }
+        }
+
+        private struct ShaderProgram
+        {
+            public ShaderStage Vertex;
+            public ShaderStage TessControl;
+            public ShaderStage TessEvaluation;
+            public ShaderStage Geometry;
+            public ShaderStage Fragment;
+        }
+
+        private ShaderProgram Current;
+
+        private ConcurrentDictionary<long, ShaderStage> Stages;
+
+        private Dictionary<ShaderProgram, int> Programs;
+
+        public int CurrentProgramHandle { get; private set; }
+
+        public OGLShader()
+        {
+            Stages = new ConcurrentDictionary<long, ShaderStage>();
+
+            Programs = new Dictionary<ShaderProgram, int>();
+        }
+
+        public void Create(long Tag, GalShaderType Type, byte[] Data)
+        {
+            Stages.GetOrAdd(Tag, (Key) => ShaderStageFactory(Type, Data));
+        }
+
+        private ShaderStage ShaderStageFactory(GalShaderType Type, byte[] Data)
+        {
+            GlslProgram Program = GetGlslProgram(Data, Type);
+
+            return new ShaderStage(
+                Type,
+                Program.Code,
+                Program.Textures,
+                Program.Uniforms);
+        }
+
+        private GlslProgram GetGlslProgram(byte[] Data, GalShaderType Type)
+        {
+            int[] Code = new int[(Data.Length - 0x50) >> 2];
+
+            using (MemoryStream MS = new MemoryStream(Data))
+            {
+                MS.Seek(0x50, SeekOrigin.Begin);
+
+                BinaryReader Reader = new BinaryReader(MS);
+
+                for (int Index = 0; Index < Code.Length; Index++)
+                {
+                    Code[Index] = Reader.ReadInt32();
+                }
+            }
+
+            GlslDecompiler Decompiler = new GlslDecompiler();
+
+            return Decompiler.Decompile(Code, Type);
+        }
+
+        public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag)
+        {
+            if (Stages.TryGetValue(Tag, out ShaderStage Stage))
+            {
+                return Stage.TextureUsage;
+            }
+
+            return Enumerable.Empty<ShaderDeclInfo>();
+        }
+
+        public void SetConstBuffer(long Tag, int Cbuf, byte[] Data)
+        {
+            BindProgram();
+
+            if (Stages.TryGetValue(Tag, out ShaderStage Stage))
+            {
+                foreach (ShaderDeclInfo DeclInfo in Stage.UniformUsage.Where(x => x.Cbuf == Cbuf))
+                {
+                    float Value = BitConverter.ToSingle(Data, DeclInfo.Index * 4);
+
+                    int Location = GL.GetUniformLocation(CurrentProgramHandle, DeclInfo.Name);
+
+                    GL.Uniform1(Location, Value);
+                }
+            }
+        }
+
+        public void SetUniform1(string UniformName, int Value)
+        {
+            BindProgram();
+
+            int Location = GL.GetUniformLocation(CurrentProgramHandle, UniformName);
+
+            GL.Uniform1(Location, Value);
+        }
+
+        public void Bind(long Tag)
+        {
+            if (Stages.TryGetValue(Tag, out ShaderStage Stage))
+            {
+                Bind(Stage);
+            }
+        }
+
+        private void Bind(ShaderStage Stage)
+        {
+            switch (Stage.Type)
+            {
+                case GalShaderType.Vertex:         Current.Vertex         = Stage; break;
+                case GalShaderType.TessControl:    Current.TessControl    = Stage; break;
+                case GalShaderType.TessEvaluation: Current.TessEvaluation = Stage; break;
+                case GalShaderType.Geometry:       Current.Geometry       = Stage; break;
+                case GalShaderType.Fragment:       Current.Fragment       = Stage; break;
+            }
+        }
+
+        public void BindProgram()
+        {
+            if (Current.Vertex   == null ||
+                Current.Fragment == null)
+            {
+                return;
+            }
+
+            if (!Programs.TryGetValue(Current, out int Handle))
+            {
+                Handle = GL.CreateProgram();
+
+                AttachIfNotNull(Handle, Current.Vertex);
+                AttachIfNotNull(Handle, Current.TessControl);
+                AttachIfNotNull(Handle, Current.TessEvaluation);
+                AttachIfNotNull(Handle, Current.Geometry);
+                AttachIfNotNull(Handle, Current.Fragment);
+
+                GL.LinkProgram(Handle);
+
+                CheckProgramLink(Handle);
+
+                Programs.Add(Current, Handle);
+            }
+
+            GL.UseProgram(Handle);
+
+            CurrentProgramHandle = Handle;
+        }
+
+        private void AttachIfNotNull(int ProgramHandle, ShaderStage Stage)
+        {
+            if (Stage != null)
+            {
+                Stage.Compile();
+
+                GL.AttachShader(ProgramHandle, Stage.Handle);
+            }
+        }
+
+        public static void CompileAndCheck(int Handle, string Code)
+        {
+            GL.ShaderSource(Handle, Code);
+            GL.CompileShader(Handle);
+
+            CheckCompilation(Handle);
+        }
+
+        private static void CheckCompilation(int Handle)
+        {
+            int Status = 0;
+
+            GL.GetShader(Handle, ShaderParameter.CompileStatus, out Status);
+
+            if (Status == 0)
+            {
+                throw new ShaderException(GL.GetShaderInfoLog(Handle));
+            }
+        }
+
+        private static void CheckProgramLink(int Handle)
+        {
+            int Status = 0;
+
+            GL.GetProgram(Handle, GetProgramParameterName.LinkStatus, out Status);
+
+            if (Status == 0)
+            {
+                throw new ShaderException(GL.GetProgramInfoLog(Handle));
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs
new file mode 100644
index 0000000000..559e0eda74
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLTexture.cs
@@ -0,0 +1,96 @@
+using OpenTK.Graphics.OpenGL;
+
+namespace Ryujinx.Graphics.Gal.OpenGL
+{
+    class OGLTexture
+    {
+        private int[] Textures;
+
+        public OGLTexture()
+        {
+            Textures = new int[80];
+        }
+
+        public void Set(int Index, GalTexture Tex)
+        {
+            GL.ActiveTexture(TextureUnit.Texture0 + Index);
+
+            int Handle = EnsureTextureInitialized(Index);
+
+            GL.BindTexture(TextureTarget.Texture2D, Handle);
+
+            int W = Tex.Width;
+            int H = Tex.Height;
+
+            byte[] Data = Tex.Data;
+
+            int Length = Data.Length;
+
+            if (IsCompressedTextureFormat(Tex.Format))
+            {
+                PixelInternalFormat Pif = OGLEnumConverter.GetCompressedTextureFormat(Tex.Format);
+
+                GL.CompressedTexImage2D(TextureTarget.Texture2D, 0, Pif, W, H, 0, Length, Data);
+            }
+            else
+            {
+                //TODO: Get those from Texture format.
+                const PixelInternalFormat Pif = PixelInternalFormat.Rgba;
+
+                const PixelFormat Pf = PixelFormat.Rgba;
+
+                const PixelType Pt = PixelType.UnsignedByte;
+
+                GL.TexImage2D(TextureTarget.Texture2D, 0, Pif, W, H, 0, Pf, Pt, Data);
+            }
+        }
+
+        public void Set(int Index, GalTextureSampler Sampler)
+        {
+            int Handle = EnsureTextureInitialized(Index);
+
+            GL.BindTexture(TextureTarget.Texture2D, Handle);
+
+            int WrapS = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressU);
+            int WrapT = (int)OGLEnumConverter.GetTextureWrapMode(Sampler.AddressV);
+
+            int MinFilter = (int)OGLEnumConverter.GetTextureMinFilter(Sampler.MinFilter, Sampler.MipFilter);
+            int MagFilter = (int)OGLEnumConverter.GetTextureMagFilter(Sampler.MagFilter);
+
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, WrapS);
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, WrapT);
+
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, MinFilter);
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, MagFilter);
+
+            float[] Color = new float[]
+            {
+                Sampler.BorderColor.Red,
+                Sampler.BorderColor.Green,
+                Sampler.BorderColor.Blue,
+                Sampler.BorderColor.Alpha
+            };
+
+            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureBorderColor, Color);
+        }
+
+        private static bool IsCompressedTextureFormat(GalTextureFormat Format)
+        {
+            return Format == GalTextureFormat.BC1 ||
+                   Format == GalTextureFormat.BC2 ||
+                   Format == GalTextureFormat.BC3;
+        }
+
+        private int EnsureTextureInitialized(int TexIndex)
+        {
+            int Handle = Textures[TexIndex];
+
+            if (Handle == 0)
+            {
+                Handle = Textures[TexIndex] = GL.GenTexture();
+            }
+
+            return Handle;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs
index 002e54dadc..0b7bf92ac4 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OpenGLRenderer.cs
@@ -1,5 +1,4 @@
 using OpenTK;
-using OpenTK.Graphics.OpenGL;
 using System;
 using System.Collections.Concurrent;
 using System.Collections.Generic;
@@ -8,22 +7,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 {
     public class OpenGLRenderer : IGalRenderer
     {
-        private struct VertexBuffer
-        {
-            public int VaoHandle;
-            public int VboHandle;
+        private OGLBlend Blend;
 
-            public int PrimCount;
-        }
+        private OGLFrameBuffer FrameBuffer;
 
-        private struct Texture
-        {
-            public int Handle;
-        }
+        private OGLRasterizer Rasterizer;
 
-        private List<VertexBuffer> VertexBuffers;
+        private OGLShader Shader;
 
-        private Texture[] Textures;
+        private OGLTexture Texture;
 
         private ConcurrentQueue<Action> ActionsQueue;
 
@@ -31,9 +23,15 @@ namespace Ryujinx.Graphics.Gal.OpenGL
 
         public OpenGLRenderer()
         {
-            VertexBuffers = new List<VertexBuffer>();
+            Blend = new OGLBlend();
 
-            Textures = new Texture[8];
+            FrameBuffer = new OGLFrameBuffer();
+
+            Rasterizer = new OGLRasterizer();
+
+            Shader = new OGLShader();
+
+            Texture = new OGLTexture();
 
             ActionsQueue = new ConcurrentQueue<Action>();
         }
@@ -66,18 +64,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         public void Render()
         {
             FbRenderer.Render();
-
-            for (int Index = 0; Index < VertexBuffers.Count; Index++)
-            {
-                VertexBuffer Vb = VertexBuffers[Index];
-
-                if (Vb.VaoHandle != 0 &&
-                    Vb.PrimCount != 0)
-                {
-                    GL.BindVertexArray(Vb.VaoHandle);
-                    GL.DrawArrays(PrimitiveType.TriangleStrip, 0, Vb.PrimCount);
-                }
-            }
         }
 
         public void SetWindowSize(int Width, int Height)
@@ -106,218 +92,161 @@ namespace Ryujinx.Graphics.Gal.OpenGL
             FbRenderer.Set(Fb, Width, Height, Transform, Offs);
         }
 
-        public void SendVertexBuffer(int Index, byte[] Buffer, int Stride, GalVertexAttrib[] Attribs)
+        public void SetBlendEnable(bool Enable)
         {
-            if (Index < 0)
+            if (Enable)
             {
-                throw new ArgumentOutOfRangeException(nameof(Index));
+                ActionsQueue.Enqueue(() => Blend.Enable());
             }
-
-            if (Buffer.Length == 0 || Stride == 0)
+            else
             {
-                return;
+                ActionsQueue.Enqueue(() => Blend.Disable());
             }
-
-            EnsureVbInitialized(Index);
-
-            VertexBuffer Vb = VertexBuffers[Index];
-
-            Vb.PrimCount = Buffer.Length / Stride;
-
-            VertexBuffers[Index] = Vb;
-
-            IntPtr Length = new IntPtr(Buffer.Length);
-
-            GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
-            GL.BufferData(BufferTarget.ArrayBuffer, Length, Buffer, BufferUsageHint.StreamDraw);
-            GL.BindBuffer(BufferTarget.ArrayBuffer, 0);
-
-            GL.BindVertexArray(Vb.VaoHandle);
-
-            for (int Attr = 0; Attr < 16; Attr++)
-            {
-                GL.DisableVertexAttribArray(Attr);
-            }
-
-            foreach (GalVertexAttrib Attrib in Attribs)
-            {
-                if (Attrib.Index >= 3) break;
-
-                GL.EnableVertexAttribArray(Attrib.Index);
-
-                GL.BindBuffer(BufferTarget.ArrayBuffer, Vb.VboHandle);
-
-                int Size = 0;
-
-                switch (Attrib.Size)
-                {
-                    case GalVertexAttribSize._8:
-                    case GalVertexAttribSize._16:
-                    case GalVertexAttribSize._32:
-                        Size = 1;
-                        break;
-                    case GalVertexAttribSize._8_8:
-                    case GalVertexAttribSize._16_16:
-                    case GalVertexAttribSize._32_32:
-                        Size = 2;
-                        break;
-                    case GalVertexAttribSize._8_8_8:
-                    case GalVertexAttribSize._11_11_10:
-                    case GalVertexAttribSize._16_16_16:
-                    case GalVertexAttribSize._32_32_32:
-                        Size = 3;
-                        break;
-                    case GalVertexAttribSize._8_8_8_8:
-                    case GalVertexAttribSize._10_10_10_2:
-                    case GalVertexAttribSize._16_16_16_16:
-                    case GalVertexAttribSize._32_32_32_32:
-                        Size = 4;
-                        break;
-                }
-
-                bool Signed =
-                    Attrib.Type == GalVertexAttribType.Snorm ||
-                    Attrib.Type == GalVertexAttribType.Sint  ||
-                    Attrib.Type == GalVertexAttribType.Sscaled;
-
-                bool Normalize =
-                    Attrib.Type == GalVertexAttribType.Snorm ||
-                    Attrib.Type == GalVertexAttribType.Unorm;
-
-                VertexAttribPointerType Type = 0;
-
-                switch (Attrib.Type)
-                {
-                    case GalVertexAttribType.Snorm:
-                    case GalVertexAttribType.Unorm:
-                    case GalVertexAttribType.Sint:
-                    case GalVertexAttribType.Uint:
-                    case GalVertexAttribType.Uscaled:
-                    case GalVertexAttribType.Sscaled:                    
-                    {
-                        switch (Attrib.Size)
-                        {
-                            case GalVertexAttribSize._8:
-                            case GalVertexAttribSize._8_8:
-                            case GalVertexAttribSize._8_8_8:
-                            case GalVertexAttribSize._8_8_8_8:
-                            {
-                                Type = Signed
-                                    ? VertexAttribPointerType.Byte
-                                    : VertexAttribPointerType.UnsignedByte;
-
-                                break;
-                            }
-
-                            case GalVertexAttribSize._16:
-                            case GalVertexAttribSize._16_16:
-                            case GalVertexAttribSize._16_16_16:
-                            case GalVertexAttribSize._16_16_16_16:
-                            {
-                                Type = Signed
-                                    ? VertexAttribPointerType.Short
-                                    : VertexAttribPointerType.UnsignedShort;
-
-                                break;
-                            }
-
-                            case GalVertexAttribSize._10_10_10_2:
-                            case GalVertexAttribSize._11_11_10:
-                            case GalVertexAttribSize._32:
-                            case GalVertexAttribSize._32_32:
-                            case GalVertexAttribSize._32_32_32:
-                            case GalVertexAttribSize._32_32_32_32:
-                            {
-                                Type = Signed
-                                    ? VertexAttribPointerType.Int
-                                    : VertexAttribPointerType.UnsignedInt;
-
-                                break;
-                            }
-                        }
-
-                        break;
-                    }
-
-                    case GalVertexAttribType.Float:
-                    {
-                        Type = VertexAttribPointerType.Float;
-
-                        break;
-                    }
-                }
-
-                GL.VertexAttribPointer(
-                    Attrib.Index,
-                    Size,
-                    Type,
-                    Normalize,
-                    Stride,
-                    Attrib.Offset);
-            }
-
-            GL.BindVertexArray(0);
         }
 
-        public void SendR8G8B8A8Texture(int Index, byte[] Buffer, int Width, int Height)
+        public void SetBlend(
+            GalBlendEquation Equation,
+            GalBlendFactor   FuncSrc,
+            GalBlendFactor   FuncDst)
         {
-            EnsureTexInitialized(Index);
-
-            GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapS, (int)TextureWrapMode.Repeat);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureWrapT, (int)TextureWrapMode.Repeat);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMinFilter, (int)TextureMinFilter.Linear);
-            GL.TexParameter(TextureTarget.Texture2D, TextureParameterName.TextureMagFilter, (int)TextureMagFilter.Linear);
-            GL.TexImage2D(TextureTarget.Texture2D,
-                0,
-                PixelInternalFormat.Rgba,
-                Width,
-                Height,
-                0,
-                PixelFormat.Rgba,
-                PixelType.UnsignedByte,
-                Buffer);
+            ActionsQueue.Enqueue(() => Blend.Set(Equation, FuncSrc, FuncDst));
         }
 
-        public void BindTexture(int Index)
+        public void SetBlendSeparate(
+            GalBlendEquation EquationRgb,
+            GalBlendEquation EquationAlpha,
+            GalBlendFactor   FuncSrcRgb,
+            GalBlendFactor   FuncDstRgb,
+            GalBlendFactor   FuncSrcAlpha,
+            GalBlendFactor   FuncDstAlpha)
         {
-            GL.ActiveTexture(TextureUnit.Texture0 + Index);
-
-            GL.BindTexture(TextureTarget.Texture2D, Textures[Index].Handle);            
+            ActionsQueue.Enqueue(() =>
+            {
+                Blend.SetSeparate(
+                    EquationRgb,
+                    EquationAlpha,
+                    FuncSrcRgb,
+                    FuncDstRgb,
+                    FuncSrcAlpha,
+                    FuncDstAlpha);
+            });
         }
 
-        private void EnsureVbInitialized(int VbIndex)
+        public void SetFb(int FbIndex, int Width, int Height)
         {
-            while (VbIndex >= VertexBuffers.Count)
-            {
-                VertexBuffers.Add(new VertexBuffer());
-            }
-
-            VertexBuffer Vb = VertexBuffers[VbIndex];
-
-            if (Vb.VaoHandle == 0)
-            {
-                Vb.VaoHandle = GL.GenVertexArray();
-            }
-
-            if (Vb.VboHandle == 0)
-            {
-                Vb.VboHandle = GL.GenBuffer();
-            }
-
-            VertexBuffers[VbIndex] = Vb;
+            ActionsQueue.Enqueue(() => FrameBuffer.Set(FbIndex, Width, Height));
         }
 
-        private void EnsureTexInitialized(int TexIndex)
+        public void BindFrameBuffer(int FbIndex)
         {
-            Texture Tex = Textures[TexIndex];
+            ActionsQueue.Enqueue(() => FrameBuffer.Bind(FbIndex));
+        }
 
-            if (Tex.Handle == 0)
+        public void DrawFrameBuffer(int FbIndex)
+        {
+            ActionsQueue.Enqueue(() => FrameBuffer.Draw(FbIndex));
+        }
+
+        public void ClearBuffers(int RtIndex, GalClearBufferFlags Flags)
+        {
+            ActionsQueue.Enqueue(() => Rasterizer.ClearBuffers(RtIndex, Flags));
+        }
+
+        public void SetVertexArray(int VbIndex, int Stride, byte[] Buffer, GalVertexAttrib[] Attribs)
+        {
+            if ((uint)VbIndex > 31)
             {
-                Tex.Handle = GL.GenTexture();
+                throw new ArgumentOutOfRangeException(nameof(VbIndex));
             }
 
-            Textures[TexIndex] = Tex;
+            ActionsQueue.Enqueue(() => Rasterizer.SetVertexArray(VbIndex, Stride,
+                Buffer  ?? throw new ArgumentNullException(nameof(Buffer)),
+                Attribs ?? throw new ArgumentNullException(nameof(Attribs))));
+        }
+
+        public void SetIndexArray(byte[] Buffer, GalIndexFormat Format)
+        {
+            if (Buffer == null)
+            {
+                throw new ArgumentNullException(nameof(Buffer));
+            }
+
+            ActionsQueue.Enqueue(() => Rasterizer.SetIndexArray(Buffer, Format));
+        }
+
+        public void DrawArrays(int VbIndex, GalPrimitiveType PrimType)
+        {
+            if ((uint)VbIndex > 31)
+            {
+                throw new ArgumentOutOfRangeException(nameof(VbIndex));
+            }
+
+            ActionsQueue.Enqueue(() => Rasterizer.DrawArrays(VbIndex, PrimType));
+        }
+
+        public void DrawElements(int VbIndex, int First, GalPrimitiveType PrimType)
+        {
+            if ((uint)VbIndex > 31)
+            {
+                throw new ArgumentOutOfRangeException(nameof(VbIndex));
+            }
+
+            ActionsQueue.Enqueue(() => Rasterizer.DrawElements(VbIndex, First, PrimType));
+        }
+
+        public void CreateShader(long Tag, GalShaderType Type, byte[] Data)
+        {
+            if (Data == null)
+            {
+                throw new ArgumentNullException(nameof(Data));
+            }
+
+            Shader.Create(Tag, Type, Data);
+        }
+
+        public void SetConstBuffer(long Tag, int Cbuf, byte[] Data)
+        {
+            if (Data == null)
+            {
+                throw new ArgumentNullException(nameof(Data));
+            }
+
+            ActionsQueue.Enqueue(() => Shader.SetConstBuffer(Tag, Cbuf, Data));
+        }
+
+        public void SetUniform1(string UniformName, int Value)
+        {
+            if (UniformName == null)
+            {
+                throw new ArgumentNullException(nameof(UniformName));
+            }
+
+            ActionsQueue.Enqueue(() => Shader.SetUniform1(UniformName, Value));
+        }
+
+        public IEnumerable<ShaderDeclInfo> GetTextureUsage(long Tag)
+        {
+            return Shader.GetTextureUsage(Tag);
+        }
+
+        public void BindShader(long Tag)
+        {
+            ActionsQueue.Enqueue(() => Shader.Bind(Tag));
+        }
+
+        public void BindProgram()
+        {
+            ActionsQueue.Enqueue(() => Shader.BindProgram());
+        }
+
+        public void SetTexture(int Index, GalTexture Tex)
+        {
+            ActionsQueue.Enqueue(() => Texture.Set(Index, Tex));
+        }
+
+        public void SetSampler(int Index, GalTextureSampler Sampler)
+        {
+            ActionsQueue.Enqueue(() => Texture.Set(Index, Sampler));
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
new file mode 100644
index 0000000000..898b90b51f
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
@@ -0,0 +1,212 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class GlslDecl
+    {
+        public const int VertexIdAttr    = 0x2fc;
+        public const int GlPositionWAttr = 0x7c;
+
+        private const int AttrStartIndex = 8;
+        private const int TexStartIndex = 8;
+
+        private const string InAttrName  = "in_attr";
+        private const string OutAttrName = "out_attr";
+        private const string UniformName = "c";
+
+        private const string GprName     = "gpr";
+        private const string PredName    = "pred";
+        private const string TextureName = "tex";
+
+        public const string FragmentOutputName = "FragColor";
+
+        private string[] StagePrefixes = new string[] { "vp", "tcp", "tep", "gp", "fp" };
+
+        private string StagePrefix;
+
+        private Dictionary<int, ShaderDeclInfo> m_Textures;
+
+        private Dictionary<(int, int), ShaderDeclInfo> m_Uniforms;
+
+        private Dictionary<int, ShaderDeclInfo> m_InAttributes;
+        private Dictionary<int, ShaderDeclInfo> m_OutAttributes;
+
+        private Dictionary<int, ShaderDeclInfo> m_Gprs;
+        private Dictionary<int, ShaderDeclInfo> m_Preds;
+
+        public IReadOnlyDictionary<int, ShaderDeclInfo> Textures => m_Textures;
+
+        public IReadOnlyDictionary<(int, int), ShaderDeclInfo> Uniforms => m_Uniforms;
+
+        public IReadOnlyDictionary<int, ShaderDeclInfo> InAttributes  => m_InAttributes;
+        public IReadOnlyDictionary<int, ShaderDeclInfo> OutAttributes => m_OutAttributes;
+
+        public IReadOnlyDictionary<int, ShaderDeclInfo> Gprs  => m_Gprs;
+        public IReadOnlyDictionary<int, ShaderDeclInfo> Preds => m_Preds;
+
+        public GalShaderType ShaderType { get; private set; }
+
+        public GlslDecl(ShaderIrNode[] Nodes, GalShaderType ShaderType)
+        {
+            this.ShaderType = ShaderType;
+
+            StagePrefix = StagePrefixes[(int)ShaderType] + "_";
+
+            m_Uniforms = new Dictionary<(int, int), ShaderDeclInfo>();
+
+            m_Textures = new Dictionary<int, ShaderDeclInfo>();
+
+            m_InAttributes  = new Dictionary<int, ShaderDeclInfo>();
+            m_OutAttributes = new Dictionary<int, ShaderDeclInfo>();
+
+            m_Gprs  = new Dictionary<int, ShaderDeclInfo>();
+            m_Preds = new Dictionary<int, ShaderDeclInfo>();
+
+            //FIXME: Only valid for vertex shaders.
+            if (ShaderType == GalShaderType.Fragment)
+            {
+                m_Gprs.Add(0, new ShaderDeclInfo(FragmentOutputName, 0, 0, 4));
+            }
+            else
+            {
+                m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4));
+            }
+
+            foreach (ShaderIrNode Node in Nodes)
+            {
+                Traverse(null, Node);
+            }
+        }
+
+        private void Traverse(ShaderIrNode Parent, ShaderIrNode Node)
+        {
+            switch (Node)
+            {
+                case ShaderIrAsg Asg:
+                {
+                    Traverse(Asg, Asg.Dst);
+                    Traverse(Asg, Asg.Src);
+
+                    break;
+                }
+
+                case ShaderIrCond Cond:
+                {
+                    Traverse(Cond, Cond.Pred);
+                    Traverse(Cond, Cond.Child);
+
+                    break;
+                }
+
+                case ShaderIrOp Op:
+                {
+                    Traverse(Op, Op.OperandA);
+                    Traverse(Op, Op.OperandB);
+                    Traverse(Op, Op.OperandC);
+
+                    if (Op.Inst == ShaderIrInst.Texr ||
+                        Op.Inst == ShaderIrInst.Texg ||
+                        Op.Inst == ShaderIrInst.Texb ||
+                        Op.Inst == ShaderIrInst.Texa)
+                    {
+                        int Handle = ((ShaderIrOperImm)Op.OperandC).Value;
+
+                        int Index = Handle - TexStartIndex;
+
+                        string Name = StagePrefix + TextureName + Index;
+
+                        m_Textures.TryAdd(Handle, new ShaderDeclInfo(Name, Handle));
+                    }
+                    break;
+                }
+
+                case ShaderIrOperCbuf Cbuf:
+                {
+                    string Name = StagePrefix + UniformName + Cbuf.Index + "_" + Cbuf.Offs;
+
+                    ShaderDeclInfo DeclInfo = new ShaderDeclInfo(Name, Cbuf.Offs, Cbuf.Index);
+
+                    m_Uniforms.TryAdd((Cbuf.Index, Cbuf.Offs), DeclInfo);
+
+                    break;
+                }
+
+                case ShaderIrOperAbuf Abuf:
+                {
+                    //This is a built-in input variable.
+                    if (Abuf.Offs == VertexIdAttr)
+                    {
+                        break;
+                    }
+
+                    int Index =  Abuf.Offs >> 4;
+                    int Elem  = (Abuf.Offs >> 2) & 3;
+
+                    int GlslIndex = Index - AttrStartIndex;
+
+                    ShaderDeclInfo DeclInfo;
+
+                    if (Parent is ShaderIrAsg Asg && Asg.Dst == Node)
+                    {
+                        if (!m_OutAttributes.TryGetValue(Index, out DeclInfo))
+                        {
+                            DeclInfo = new ShaderDeclInfo(OutAttrName + GlslIndex, GlslIndex);
+
+                            m_OutAttributes.Add(Index, DeclInfo);
+                        }
+                    }
+                    else
+                    {
+                        if (!m_InAttributes.TryGetValue(Index, out DeclInfo))
+                        {
+                            DeclInfo = new ShaderDeclInfo(InAttrName + GlslIndex, GlslIndex);
+
+                            m_InAttributes.Add(Index, DeclInfo);
+                        }
+                    }
+
+                    DeclInfo.Enlarge(Elem + 1);
+
+                    break;
+                }
+
+                case ShaderIrOperGpr Gpr:
+                {
+                    if (!Gpr.IsConst && !HasName(m_Gprs, Gpr.Index))
+                    {
+                        string Name = GprName + Gpr.Index;
+
+                        m_Gprs.TryAdd(Gpr.Index, new ShaderDeclInfo(Name, Gpr.Index));
+                    }
+                    break;
+                }
+
+                case ShaderIrOperPred Pred:
+                {
+                    if (!Pred.IsConst && !HasName(m_Preds, Pred.Index))
+                    {
+                        string Name = PredName + Pred.Index;
+
+                        m_Preds.TryAdd(Pred.Index, new ShaderDeclInfo(Name, Pred.Index));
+                    }
+                    break;
+                }
+            }
+        }
+
+        private bool HasName(Dictionary<int, ShaderDeclInfo> Decls, int Index)
+        {
+            int VecIndex = Index >> 2;
+
+            if (Decls.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo))
+            {
+                if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size)
+                {
+                    return true;
+                }
+            }
+
+            return Decls.ContainsKey(Index);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
new file mode 100644
index 0000000000..eda70cefa6
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -0,0 +1,644 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class GlslDecompiler
+    {
+        private delegate string GetInstExpr(ShaderIrOp Op);
+
+        private Dictionary<ShaderIrInst, GetInstExpr> InstsExpr;
+
+        private enum OperType
+        {
+            Bool,
+            F32,
+            I32
+        }
+
+        private const string IdentationStr = "    ";
+
+        private static string[] ElemTypes = new string[] { "float", "vec2", "vec3", "vec4" };
+
+        private GlslDecl Decl;
+
+        private StringBuilder SB;
+
+        public GlslDecompiler()
+        {
+            InstsExpr = new Dictionary<ShaderIrInst, GetInstExpr>()
+            {
+                { ShaderIrInst.And,  GetAndExpr  },
+                { ShaderIrInst.Asr,  GetAsrExpr  },
+                { ShaderIrInst.Band, GetBandExpr },
+                { ShaderIrInst.Bnot, GetBnotExpr },
+                { ShaderIrInst.Clt,  GetCltExpr  },
+                { ShaderIrInst.Ceq,  GetCeqExpr  },
+                { ShaderIrInst.Cle,  GetCleExpr  },
+                { ShaderIrInst.Cgt,  GetCgtExpr  },
+                { ShaderIrInst.Cne,  GetCneExpr  },
+                { ShaderIrInst.Cge,  GetCgeExpr  },
+                { ShaderIrInst.Exit, GetExitExpr },
+                { ShaderIrInst.Fabs, GetFabsExpr },
+                { ShaderIrInst.Fadd, GetFaddExpr },
+                { ShaderIrInst.Fcos, GetFcosExpr },
+                { ShaderIrInst.Fex2, GetFex2Expr },
+                { ShaderIrInst.Ffma, GetFfmaExpr },
+                { ShaderIrInst.Flg2, GetFlg2Expr },
+                { ShaderIrInst.Fmul, GetFmulExpr },
+                { ShaderIrInst.Fneg, GetFnegExpr },
+                { ShaderIrInst.Frcp, GetFrcpExpr },
+                { ShaderIrInst.Frsq, GetFrsqExpr },
+                { ShaderIrInst.Fsin, GetFsinExpr },
+                { ShaderIrInst.Ipa,  GetIpaExpr  },
+                { ShaderIrInst.Kil,  GetKilExpr  },
+                { ShaderIrInst.Lsr,  GetLsrExpr  },
+                { ShaderIrInst.Not,  GetNotExpr  },
+                { ShaderIrInst.Or,   GetOrExpr   },
+                { ShaderIrInst.Stof, GetStofExpr },
+                { ShaderIrInst.Utof, GetUtofExpr },
+                { ShaderIrInst.Texr, GetTexrExpr },
+                { ShaderIrInst.Texg, GetTexgExpr },
+                { ShaderIrInst.Texb, GetTexbExpr },
+                { ShaderIrInst.Texa, GetTexaExpr },
+                { ShaderIrInst.Xor,  GetXorExpr  },
+            };
+        }
+
+        public GlslProgram Decompile(int[] Code, GalShaderType ShaderType)
+        {
+            ShaderIrBlock Block = ShaderDecoder.DecodeBasicBlock(Code, 0, ShaderType);
+
+            ShaderIrNode[] Nodes = Block.GetNodes();
+
+            Decl = new GlslDecl(Nodes, ShaderType);
+
+            SB = new StringBuilder();
+
+            SB.AppendLine("#version 330 core");
+
+            PrintDeclTextures();
+            PrintDeclUniforms();
+            PrintDeclInAttributes();
+            PrintDeclOutAttributes();
+            PrintDeclGprs();
+            PrintDeclPreds();
+
+            PrintBlockScope("void main()", 1, Nodes);
+
+            string GlslCode = SB.ToString();
+
+            return new GlslProgram(
+                GlslCode,
+                Decl.Textures.Values,
+                Decl.Uniforms.Values);
+        }
+
+        private void PrintDeclTextures()
+        {
+            PrintDecls(Decl.Textures, "uniform sampler2D");
+        }
+
+        private void PrintDeclUniforms()
+        {
+            foreach (ShaderDeclInfo DeclInfo in Decl.Uniforms.Values.OrderBy(DeclKeySelector))
+            {
+                SB.AppendLine($"uniform {GetDecl(DeclInfo)};");
+            }
+
+            if (Decl.Uniforms.Count > 0)
+            {
+                SB.AppendLine();
+            }
+        }
+
+        private void PrintDeclInAttributes()
+        {
+            PrintDeclAttributes(Decl.InAttributes.Values, "in");
+        }
+
+        private void PrintDeclOutAttributes()
+        {
+            PrintDeclAttributes(Decl.OutAttributes.Values, "out");
+        }
+
+        private void PrintDeclAttributes(IEnumerable<ShaderDeclInfo> Decls, string InOut)
+        {
+            int Count = 0;
+
+            foreach (ShaderDeclInfo DeclInfo in Decls.OrderBy(DeclKeySelector))
+            {
+                if (DeclInfo.Index >= 0)
+                {
+                    SB.AppendLine($"layout (location = {DeclInfo.Index}) {InOut} {GetDecl(DeclInfo)};");
+
+                    Count++;
+                }
+            }
+
+            if (Count > 0)
+            {
+                SB.AppendLine();
+            }
+        }
+
+        private void PrintDeclGprs()
+        {
+            PrintDecls(Decl.Gprs);
+        }
+
+        private void PrintDeclPreds()
+        {
+            PrintDecls(Decl.Preds, "bool");
+        }
+
+        private void PrintDecls(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, string CustomType = null)
+        {
+            foreach (ShaderDeclInfo DeclInfo in Dict.Values.OrderBy(DeclKeySelector))
+            {
+                string Name;
+
+                if (CustomType != null)
+                {
+                    Name = CustomType + " " + DeclInfo.Name + ";";
+                }
+                else if (DeclInfo.Name == GlslDecl.FragmentOutputName)
+                {
+                    Name = "layout (location = 0) out " + GetDecl(DeclInfo) + ";";
+                }
+                else
+                {
+                    Name = GetDecl(DeclInfo) + ";";
+                }
+
+                SB.AppendLine(Name);
+            }
+
+            if (Dict.Count > 0)
+            {
+                SB.AppendLine();
+            }
+        }
+
+        private int DeclKeySelector(ShaderDeclInfo DeclInfo)
+        {
+            return DeclInfo.Cbuf << 24 | DeclInfo.Index;
+        }
+
+        private string GetDecl(ShaderDeclInfo DeclInfo)
+        {
+            return ElemTypes[DeclInfo.Size - 1] + " " + DeclInfo.Name;
+        }
+
+        private void PrintBlockScope(string ScopeName, int IdentationLevel, params ShaderIrNode[] Nodes)
+        {
+            string Identation = string.Empty;
+
+            for (int Index = 0; Index < IdentationLevel - 1; Index++)
+            {
+                Identation += IdentationStr;
+            }
+
+            if (ScopeName != string.Empty)
+            {
+                ScopeName += " ";
+            }
+
+            SB.AppendLine(Identation + ScopeName + "{");
+
+            string LastLine = Identation + "}";
+
+            if (IdentationLevel > 0)
+            {
+                Identation += IdentationStr;
+            }
+
+            for (int Index = 0; Index < Nodes.Length; Index++)
+            {
+                ShaderIrNode Node = Nodes[Index];
+
+                if (Node is ShaderIrCond Cond)
+                {
+                    string SubScopeName = "if (" + GetSrcExpr(Cond.Pred, true) + ")";
+
+                    PrintBlockScope(SubScopeName, IdentationLevel + 1, Cond.Child);
+                }
+                else if (Node is ShaderIrAsg Asg && IsValidOutOper(Asg.Dst))
+                {
+                    string Expr = GetSrcExpr(Asg.Src, true);
+
+                    Expr = GetExprWithCast(Asg.Dst, Asg.Src, Expr);
+
+                    SB.AppendLine(Identation + GetDstOperName(Asg.Dst) + " = " + Expr + ";");
+                }
+                else if (Node is ShaderIrOp Op)
+                {
+                    SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";");
+                }
+                else
+                {
+                    throw new InvalidOperationException();
+                }
+            }
+
+            SB.AppendLine(LastLine);
+        }
+
+        private bool IsValidOutOper(ShaderIrNode Node)
+        {
+            if (Node is ShaderIrOperGpr Gpr && Gpr.IsConst)
+            {
+                return false;
+            }
+            else if (Node is ShaderIrOperPred Pred && Pred.IsConst)
+            {
+                return false;
+            }
+
+            return true;
+        }
+
+        private string GetDstOperName(ShaderIrNode Node)
+        {
+            if (Node is ShaderIrOperAbuf Abuf)
+            {
+                return GetOutAbufName(Abuf);
+            }
+            else if (Node is ShaderIrOperGpr Gpr)
+            {
+                return GetName(Gpr);
+            }
+            else if (Node is ShaderIrOperPred Pred)
+            {
+                return GetName(Pred);
+            }
+
+            throw new ArgumentException(nameof(Node));
+        }
+
+        private string GetSrcExpr(ShaderIrNode Node, bool Entry = false)
+        {
+            switch (Node)
+            {
+                case ShaderIrOperAbuf Abuf: return GetName (Abuf);
+                case ShaderIrOperCbuf Cbuf: return GetName (Cbuf);
+                case ShaderIrOperGpr  Gpr:  return GetName (Gpr);
+                case ShaderIrOperImm  Imm:  return GetValue(Imm);
+                case ShaderIrOperImmf Immf: return GetValue(Immf);
+                case ShaderIrOperPred Pred: return GetName (Pred);
+
+                case ShaderIrOp Op:
+                    string Expr;
+
+                    if (InstsExpr.TryGetValue(Op.Inst, out GetInstExpr GetExpr))
+                    {
+                        Expr = GetExpr(Op);
+                    }
+                    else
+                    {
+                        throw new NotImplementedException(Op.Inst.ToString());
+                    }
+
+                    if (!Entry && NeedsParentheses(Op))
+                    {
+                        Expr = "(" + Expr + ")";
+                    }
+
+                    return Expr;
+
+                default: throw new ArgumentException(nameof(Node));
+            }
+        }
+
+        private static bool NeedsParentheses(ShaderIrOp Op)
+        {
+            switch (Op.Inst)
+            {
+                case ShaderIrInst.Frcp:
+                    return true;
+
+                case ShaderIrInst.Ipa:
+                case ShaderIrInst.Texr:
+                case ShaderIrInst.Texg:
+                case ShaderIrInst.Texb:
+                case ShaderIrInst.Texa:
+                    return false;
+            }
+
+            return Op.OperandB != null ||
+                   Op.OperandC != null;
+        }
+
+        private string GetName(ShaderIrOperCbuf Cbuf)
+        {
+            if (!Decl.Uniforms.TryGetValue((Cbuf.Index, Cbuf.Offs), out ShaderDeclInfo DeclInfo))
+            {
+                throw new InvalidOperationException();
+            }
+
+            return DeclInfo.Name;
+        }
+
+        private string GetOutAbufName(ShaderIrOperAbuf Abuf)
+        {
+            return GetName(Decl.OutAttributes, Abuf);
+        }
+
+        private string GetName(ShaderIrOperAbuf Abuf)
+        {
+            if (Abuf.Offs == GlslDecl.GlPositionWAttr && Decl.ShaderType == GalShaderType.Fragment)
+            {
+                return "(1f / gl_FragCoord.w)";
+            }
+
+            if (Abuf.Offs == GlslDecl.VertexIdAttr)
+            {
+                return "gl_VertexID";
+            }
+
+            return GetName(Decl.InAttributes, Abuf);
+        }
+
+        private string GetName(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, ShaderIrOperAbuf Abuf)
+        {
+            int Index =  Abuf.Offs >> 4;
+            int Elem  = (Abuf.Offs >> 2) & 3;
+
+            if (!Dict.TryGetValue(Index, out ShaderDeclInfo DeclInfo))
+            {
+                throw new InvalidOperationException();
+            }
+
+            return DeclInfo.Size > 1 ? DeclInfo.Name + "." + GetAttrSwizzle(Elem) : DeclInfo.Name;
+        }
+
+        private string GetName(ShaderIrOperGpr Gpr)
+        {
+            return Gpr.IsConst ? "0" : GetNameWithSwizzle(Decl.Gprs, Gpr.Index);
+        }
+
+        private string GetValue(ShaderIrOperImm Imm)
+        {
+            //Only use hex is the value is too big and would likely be hard to read as int.
+            if (Imm.Value >  0xfff ||
+                Imm.Value < -0xfff)
+            {
+                return "0x" + Imm.Value.ToString("x8", CultureInfo.InvariantCulture);
+            }
+            else
+            {
+                return Imm.Value.ToString(CultureInfo.InvariantCulture);
+            }
+        }
+
+        private string GetValue(ShaderIrOperImmf Immf)
+        {
+            return Immf.Value.ToString(CultureInfo.InvariantCulture) + "f";
+        }
+
+        private string GetName(ShaderIrOperPred Pred)
+        {
+            return Pred.IsConst ? "true" : GetNameWithSwizzle(Decl.Preds, Pred.Index);
+        }
+
+        private string GetNameWithSwizzle(IReadOnlyDictionary<int, ShaderDeclInfo> Dict, int Index)
+        {
+            int VecIndex = Index >> 2;
+
+            if (Dict.TryGetValue(VecIndex, out ShaderDeclInfo DeclInfo))
+            {
+                if (DeclInfo.Size > 1 && Index < VecIndex + DeclInfo.Size)
+                {
+                    return DeclInfo.Name + "." + GetAttrSwizzle(Index & 3);
+                }
+            }
+
+            if (!Dict.TryGetValue(Index, out DeclInfo))
+            {
+                throw new InvalidOperationException();
+            }
+
+            return DeclInfo.Name;
+        }
+
+        private string GetAttrSwizzle(int Elem)
+        {
+            return "xyzw".Substring(Elem, 1);
+        }
+
+        private string GetAndExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&");
+
+        private string GetAsrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">>");
+
+        private string GetBandExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "&&");
+
+        private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!");
+
+        private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<");
+        private string GetCeqExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "==");
+        private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<=");
+        private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">");
+        private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!=");
+        private string GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">=");
+
+        private string GetExitExpr(ShaderIrOp Op) => "return";
+
+        private string GetFabsExpr(ShaderIrOp Op) => GetUnaryCall(Op, "abs");
+
+        private string GetFaddExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "+");
+
+        private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos");
+
+        private string GetFex2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "exp2");
+
+        private string GetFfmaExpr(ShaderIrOp Op) => GetTernaryExpr(Op, "*", "+");
+
+        private string GetFlg2Expr(ShaderIrOp Op) => GetUnaryCall(Op, "log2");
+
+        private string GetFmulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*");
+
+        private string GetFnegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-");
+
+        private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1f / ");
+
+        private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt");
+
+        private string GetFsinExpr(ShaderIrOp Op) => GetUnaryCall(Op, "sin");
+
+        private string GetIpaExpr(ShaderIrOp Op) => GetSrcExpr(Op.OperandA);
+
+        private string GetKilExpr(ShaderIrOp Op) => "discard";
+
+        private string GetLsrExpr(ShaderIrOp Op)
+        {
+            return "int(uint(" + GetOperExpr(Op, Op.OperandA) + ") >> " +
+                                 GetOperExpr(Op, Op.OperandB) + ")";
+        }
+
+        private string GetNotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "~");
+
+        private string GetOrExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "|");
+
+        private string GetStofExpr(ShaderIrOp Op)
+        {
+            return "float(" + GetOperExpr(Op, Op.OperandA) + ")";
+        }
+
+        private string GetUtofExpr(ShaderIrOp Op)
+        {
+            return "float(uint(" + GetOperExpr(Op, Op.OperandA) + "))";
+        }
+
+        private string GetXorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^");
+
+        private string GetUnaryCall(ShaderIrOp Op, string FuncName)
+        {
+            return FuncName + "(" + GetOperExpr(Op, Op.OperandA) + ")";
+        }
+
+        private string GetUnaryExpr(ShaderIrOp Op, string Opr)
+        {
+            return Opr + GetOperExpr(Op, Op.OperandA);
+        }
+
+        private string GetBinaryExpr(ShaderIrOp Op, string Opr)
+        {
+            return GetOperExpr(Op, Op.OperandA) + " " + Opr + " " +
+                   GetOperExpr(Op, Op.OperandB);
+        }
+
+        private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2)
+        {
+            return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " +
+                   GetOperExpr(Op, Op.OperandB) + " " + Opr2 + " " +
+                   GetOperExpr(Op, Op.OperandC);
+        }
+
+        private string GetTexrExpr(ShaderIrOp Op) => GetTexExpr(Op, 'r');
+        private string GetTexgExpr(ShaderIrOp Op) => GetTexExpr(Op, 'g');
+        private string GetTexbExpr(ShaderIrOp Op) => GetTexExpr(Op, 'b');
+        private string GetTexaExpr(ShaderIrOp Op) => GetTexExpr(Op, 'a');
+
+        private string GetTexExpr(ShaderIrOp Op, char Ch)
+        {
+            return $"texture({GetTexSamplerName(Op)}, {GetTexSamplerCoords(Op)}).{Ch}";
+        }
+
+        private string GetTexSamplerName(ShaderIrOp Op)
+        {
+            ShaderIrOperImm Node = (ShaderIrOperImm)Op.OperandC;
+
+            int Handle = ((ShaderIrOperImm)Op.OperandC).Value;
+
+            if (!Decl.Textures.TryGetValue(Handle, out ShaderDeclInfo DeclInfo))
+            {
+                throw new InvalidOperationException();
+            }
+
+            return DeclInfo.Name;
+        }
+
+        private string GetTexSamplerCoords(ShaderIrOp Op)
+        {
+            return "vec2(" + GetOperExpr(Op, Op.OperandA) + ", " +
+                             GetOperExpr(Op, Op.OperandB) + ")";
+        }
+
+        private string GetOperExpr(ShaderIrOp Op, ShaderIrNode Oper)
+        {
+            return GetExprWithCast(Op, Oper, GetSrcExpr(Oper));
+        }
+
+        private static string GetExprWithCast(ShaderIrNode Dst, ShaderIrNode Src, string Expr)
+        {
+            //Note: The "DstType" (of the cast) is the type that the operation
+            //uses on the source operands, while the "SrcType" is the destination
+            //type of the operand result (if it is a operation) or just the type
+            //of the variable for registers/uniforms/attributes.
+            OperType DstType = GetSrcNodeType(Dst);
+            OperType SrcType = GetDstNodeType(Src);
+
+            if (DstType != SrcType)
+            {
+                //Check for invalid casts
+                //(like bool to int/float and others).
+                if (SrcType != OperType.F32 &&
+                    SrcType != OperType.I32)
+                {
+                    throw new InvalidOperationException();
+                }
+
+                //For integer immediates being used as float,
+                //it's better (for readability) to just return the float value.
+                if (Src is ShaderIrOperImm Imm && DstType == OperType.F32)
+                {
+                    float Value = BitConverter.Int32BitsToSingle(Imm.Value);
+
+                    return Value.ToString(CultureInfo.InvariantCulture) + "f";
+                }
+
+                switch (DstType)
+                {
+                    case OperType.F32: Expr = "intBitsToFloat(" + Expr + ")"; break;
+                    case OperType.I32: Expr = "floatBitsToInt(" + Expr + ")"; break;
+                }
+            }
+
+            return Expr;
+        }
+
+        private static OperType GetDstNodeType(ShaderIrNode Node)
+        {
+            if (Node is ShaderIrOp Op)
+            {
+                switch (Op.Inst)
+                {
+                    case ShaderIrInst.Stof: return OperType.F32;
+                    case ShaderIrInst.Utof: return OperType.F32;
+                }
+            }
+
+            return GetSrcNodeType(Node);
+        }
+
+        private static OperType GetSrcNodeType(ShaderIrNode Node)
+        {
+            switch (Node)
+            {
+                case ShaderIrOperAbuf Abuf:
+                    return Abuf.Offs == GlslDecl.VertexIdAttr
+                        ? OperType.I32
+                        : OperType.F32;
+
+                case ShaderIrOperCbuf Cbuf: return OperType.F32;
+                case ShaderIrOperGpr  Gpr:  return OperType.F32;
+                case ShaderIrOperImm  Imm:  return OperType.I32;
+                case ShaderIrOperImmf Immf: return OperType.F32;
+                case ShaderIrOperPred Pred: return OperType.Bool;
+
+                case ShaderIrOp Op:
+                    if (Op.Inst > ShaderIrInst.B_Start &&
+                        Op.Inst < ShaderIrInst.B_End)
+                    {
+                        return OperType.Bool;
+                    }
+                    else if (Op.Inst > ShaderIrInst.F_Start &&
+                             Op.Inst < ShaderIrInst.F_End)
+                    {
+                        return OperType.F32;
+                    }
+                    else if (Op.Inst > ShaderIrInst.I_Start &&
+                             Op.Inst < ShaderIrInst.I_End)
+                    {
+                        return OperType.I32;
+                    }
+                    break;
+            }
+
+            throw new ArgumentException(nameof(Node));
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs
new file mode 100644
index 0000000000..729b6f1ded
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/GlslProgram.cs
@@ -0,0 +1,22 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    struct GlslProgram
+    {
+        public string Code { get; private set; }
+
+        public IEnumerable<ShaderDeclInfo> Textures { get; private set; }
+        public IEnumerable<ShaderDeclInfo> Uniforms { get; private set; }
+
+        public GlslProgram(
+            string                      Code,
+            IEnumerable<ShaderDeclInfo> Textures,
+            IEnumerable<ShaderDeclInfo> Uniforms)
+        {
+            this.Code     = Code;
+            this.Textures = Textures;
+            this.Uniforms = Uniforms;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs
new file mode 100644
index 0000000000..ef0fd78bd3
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecode.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    delegate void ShaderDecodeFunc(ShaderIrBlock Block, long OpCode);
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
new file mode 100644
index 0000000000..5c2f493e4f
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
@@ -0,0 +1,315 @@
+using System;
+
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static partial class ShaderDecode
+    {
+        public static void Fadd_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fadd);
+        }
+
+        public static void Fadd_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fadd);
+        }
+
+        public static void Fadd_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fadd);
+        }
+
+        public static void Ffma_CR(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluFfma(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void Ffma_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluFfma(Block, OpCode, ShaderOper.Immf);
+        }
+
+        public static void Ffma_RC(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluFfma(Block, OpCode, ShaderOper.RC);
+        }
+
+        public static void Ffma_RR(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluFfma(Block, OpCode, ShaderOper.RR);
+        }
+
+        public static void Fmul_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinaryF(Block, OpCode, ShaderOper.CR, ShaderIrInst.Fmul);
+        }
+
+        public static void Fmul_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinaryF(Block, OpCode, ShaderOper.Immf, ShaderIrInst.Fmul);
+        }
+
+        public static void Fmul_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinaryF(Block, OpCode, ShaderOper.RR, ShaderIrInst.Fmul);
+        }
+
+        public static void Fsetp_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitFsetp(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void Fsetp_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitFsetp(Block, OpCode, ShaderOper.Immf);
+        }
+
+        public static void Fsetp_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitFsetp(Block, OpCode, ShaderOper.RR);
+        }
+
+        public static void Ipa(ShaderIrBlock Block, long OpCode)
+        {
+            ShaderIrNode OperA = GetOperAbuf28(OpCode);
+            ShaderIrNode OperB = GetOperGpr20 (OpCode);
+
+            ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ipa, OperA, OperB);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+        }
+
+        public static void Lop32i(ShaderIrBlock Block, long OpCode)
+        {
+            int SubOp = (int)(OpCode >> 53) & 3;
+
+            bool Ia = ((OpCode >> 55) & 1) != 0;
+            bool Ib = ((OpCode >> 56) & 1) != 0;
+
+            ShaderIrInst Inst = 0;
+
+            switch (SubOp)
+            {
+                case 0: Inst = ShaderIrInst.And; break;
+                case 1: Inst = ShaderIrInst.Or;  break;
+                case 2: Inst = ShaderIrInst.Xor; break;
+            }
+
+            ShaderIrNode OperA = GetAluNot(GetOperGpr8(OpCode), Ia);
+
+            //SubOp == 3 is pass, used by the not instruction
+            //which just moves the inverted register value.
+            if (SubOp < 3)
+            {
+                ShaderIrNode OperB = GetAluNot(GetOperImm32_20(OpCode), Ib);
+
+                ShaderIrOp Op = new ShaderIrOp(Inst, OperA, OperB);
+
+                Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+            }
+            else
+            {
+                Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), OperA), OpCode));
+            }
+        }
+
+        public static void Mufu(ShaderIrBlock Block, long OpCode)
+        {
+            int SubOp = (int)(OpCode >> 20) & 7;
+
+            bool Aa = ((OpCode >> 46) & 1) != 0;
+            bool Na = ((OpCode >> 48) & 1) != 0;
+
+            ShaderIrInst Inst = 0;
+
+            switch (SubOp)
+            {
+                case 0: Inst = ShaderIrInst.Fcos; break;
+                case 1: Inst = ShaderIrInst.Fsin; break;
+                case 2: Inst = ShaderIrInst.Fex2; break;
+                case 3: Inst = ShaderIrInst.Flg2; break;
+                case 4: Inst = ShaderIrInst.Frcp; break;
+                case 5: Inst = ShaderIrInst.Frsq; break;
+
+                default: throw new NotImplementedException(SubOp.ToString());
+            }
+
+            ShaderIrNode OperA = GetOperGpr8(OpCode);
+
+            ShaderIrOp Op = new ShaderIrOp(Inst, GetAluAbsNeg(OperA, Aa, Na));
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+        }
+
+        public static void Shr_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinary(Block, OpCode, ShaderOper.CR, GetShrInst(OpCode));
+        }
+
+        public static void Shr_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinary(Block, OpCode, ShaderOper.Imm, GetShrInst(OpCode));
+        }
+
+        public static void Shr_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitAluBinary(Block, OpCode, ShaderOper.RR, GetShrInst(OpCode));
+        }
+
+        private static ShaderIrInst GetShrInst(long OpCode)
+        {
+            bool Signed = ((OpCode >> 48) & 1) != 0;
+
+            return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
+        }
+
+        private static void EmitAluBinary(
+            ShaderIrBlock Block,
+            long          OpCode,
+            ShaderOper    Oper,
+            ShaderIrInst  Inst)
+        {
+            ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:  OperB = GetOperCbuf34  (OpCode); break;
+                case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break;
+                case ShaderOper.RR:  OperB = GetOperGpr20   (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+        }
+
+        private static void EmitAluBinaryF(
+            ShaderIrBlock Block,
+            long          OpCode,
+            ShaderOper    Oper,
+            ShaderIrInst  Inst)
+        {
+            bool Nb = ((OpCode >> 45) & 1) != 0;
+            bool Aa = ((OpCode >> 46) & 1) != 0;
+            bool Na = ((OpCode >> 48) & 1) != 0;
+            bool Ab = ((OpCode >> 49) & 1) != 0;
+            bool Ad = ((OpCode >> 50) & 1) != 0;
+
+            ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+            if (Inst == ShaderIrInst.Fadd)
+            {
+                OperA = GetAluAbsNeg(OperA, Aa, Na);
+            }
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:   OperB = GetOperCbuf34   (OpCode); break;
+                case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
+                case ShaderOper.RR:   OperB = GetOperGpr20    (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            OperB = GetAluAbsNeg(OperB, Ab, Nb);
+
+            ShaderIrNode Op = new ShaderIrOp(Inst, OperA, OperB);
+
+            Op = GetAluAbs(Op, Ad);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+        }
+
+        private static void EmitAluFfma(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            bool Nb = ((OpCode >> 48) & 1) != 0;
+            bool Nc = ((OpCode >> 49) & 1) != 0;
+
+            ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC;
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:   OperB = GetOperCbuf34   (OpCode); break;
+                case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
+                case ShaderOper.RC:   OperB = GetOperGpr39    (OpCode); break;
+                case ShaderOper.RR:   OperB = GetOperGpr20    (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            OperB = GetAluNeg(OperB, Nb);
+
+            if (Oper == ShaderOper.RC)
+            {
+                OperC = GetAluNeg(GetOperCbuf34(OpCode), Nc);
+            }
+            else
+            {
+                OperC = GetAluNeg(GetOperGpr39(OpCode), Nc);
+            }
+
+            ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Ffma, OperA, OperB, OperC);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+        }
+
+        private static void EmitFsetp(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            bool Aa = ((OpCode >>  7) & 1) != 0;
+            bool Np = ((OpCode >> 42) & 1) != 0;
+            bool Na = ((OpCode >> 43) & 1) != 0;
+            bool Ab = ((OpCode >> 44) & 1) != 0;
+
+            ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:   OperB = GetOperCbuf34   (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 = GetCmp(OpCode);
+
+            ShaderIrOp Op = new ShaderIrOp(CmpInst,
+                GetAluAbsNeg(OperA, Aa, Na),
+                GetAluAbs   (OperB, Ab));
+
+            ShaderIrOperPred P0Node = GetOperPred3 (OpCode);
+            ShaderIrOperPred P1Node = GetOperPred0 (OpCode);
+            ShaderIrOperPred P2Node = GetOperPred39(OpCode);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode));
+
+            ShaderIrInst LopInst = GetBLop(OpCode);
+
+            if (LopInst == ShaderIrInst.Band && P1Node.IsConst && P2Node.IsConst)
+            {
+                return;
+            }
+
+            ShaderIrNode P2NNode = P2Node;
+
+            if (Np)
+            {
+                P2NNode = new ShaderIrOp(ShaderIrInst.Bnot, P2NNode);
+            }
+
+            Op = new ShaderIrOp(ShaderIrInst.Bnot, P0Node);
+
+            Op = new ShaderIrOp(LopInst, Op, P2NNode);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(P1Node, Op), OpCode));
+
+            Op = new ShaderIrOp(LopInst, P0Node, P2NNode);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode));
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
new file mode 100644
index 0000000000..d3feb92e56
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
@@ -0,0 +1,17 @@
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static partial class ShaderDecode
+    {
+        public static void Exit(ShaderIrBlock Block, long OpCode)
+        {
+            Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Exit), OpCode));
+        }
+
+        public static void Kil(ShaderIrBlock Block, long OpCode)
+        {
+            Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Kil), OpCode));
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs
new file mode 100644
index 0000000000..7989570dd3
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeHelper.cs
@@ -0,0 +1,211 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static class ShaderDecodeHelper
+    {
+        public static ShaderIrOperAbuf[] GetOperAbuf20(long OpCode)
+        {
+            int Abuf = (int)(OpCode >> 20) & 0x3ff;
+            int Reg  = (int)(OpCode >> 39) & 0xff;
+            int Size = (int)(OpCode >> 47) & 3;
+
+            ShaderIrOperAbuf[] Opers = new ShaderIrOperAbuf[Size + 1];
+
+            for (int Index = 0; Index <= Size; Index++)
+            {
+                Opers[Index] = new ShaderIrOperAbuf(Abuf, Reg);
+            }
+
+            return Opers;
+        }
+
+        public static ShaderIrOperAbuf GetOperAbuf28(long OpCode)
+        {
+            int Abuf = (int)(OpCode >> 28) & 0x3ff;
+            int Reg  = (int)(OpCode >> 39) & 0xff;
+
+            return new ShaderIrOperAbuf(Abuf, Reg);
+        }
+
+        public static ShaderIrOperCbuf GetOperCbuf34(long OpCode)
+        {
+            return new ShaderIrOperCbuf(
+                (int)(OpCode >> 34) & 0x1f,
+                (int)(OpCode >> 20) & 0x3fff);
+        }
+
+        public static ShaderIrOperGpr GetOperGpr8(long OpCode)
+        {
+            return new ShaderIrOperGpr((int)(OpCode >> 8) & 0xff);
+        }
+
+        public static ShaderIrOperGpr GetOperGpr20(long OpCode)
+        {
+            return new ShaderIrOperGpr((int)(OpCode >> 20) & 0xff);
+        }
+
+        public static ShaderIrOperGpr GetOperGpr39(long OpCode)
+        {
+            return new ShaderIrOperGpr((int)(OpCode >> 39) & 0xff);
+        }
+
+        public static ShaderIrOperGpr GetOperGpr0(long OpCode)
+        {
+            return new ShaderIrOperGpr((int)(OpCode >> 0) & 0xff);
+        }
+
+        public static ShaderIrOperGpr GetOperGpr28(long OpCode)
+        {
+            return new ShaderIrOperGpr((int)(OpCode >> 28) & 0xff);
+        }
+
+        public static ShaderIrNode GetOperImm19_20(long OpCode)
+        {
+            int Value = (int)(OpCode >> 20) & 0x7ffff;
+
+            bool Neg = ((OpCode >> 56) & 1) != 0;
+
+            if (Neg)
+            {
+                Value = -Value;
+            }
+
+            return new ShaderIrOperImm((int)Value);
+        }
+
+        public static ShaderIrNode GetOperImmf19_20(long OpCode)
+        {
+            uint Imm = (uint)(OpCode >> 20) & 0x7ffff;
+
+            bool Neg = ((OpCode >> 56) & 1) != 0;
+
+            Imm <<= 12;
+
+            if (Neg)
+            {
+                Imm |= 0x80000000;
+            }
+
+            float Value = BitConverter.Int32BitsToSingle((int)Imm);
+
+            return new ShaderIrOperImmf(Value);
+        }
+
+        public static ShaderIrOperImm GetOperImm13_36(long OpCode)
+        {
+            return new ShaderIrOperImm((int)(OpCode >> 36) & 0x1fff);
+        }
+
+        public static ShaderIrOperImm GetOperImm32_20(long OpCode)
+        {
+            return new ShaderIrOperImm((int)(OpCode >> 20));
+        }
+
+        public static ShaderIrOperPred GetOperPred3(long OpCode)
+        {
+            return new ShaderIrOperPred((int)(OpCode >> 3) & 7);
+        }
+
+        public static ShaderIrOperPred GetOperPred0(long OpCode)
+        {
+            return new ShaderIrOperPred((int)(OpCode >> 0) & 7);
+        }
+
+        public static ShaderIrNode GetOperPred39N(long OpCode)
+        {
+            ShaderIrNode Node = GetOperPred39(OpCode);
+
+            if (((OpCode >> 42) & 1) != 0)
+            {
+                Node = new ShaderIrOp(ShaderIrInst.Bnot, Node);
+            }
+
+            return Node;
+        }
+
+        public static ShaderIrOperPred GetOperPred39(long OpCode)
+        {
+            return new ShaderIrOperPred((int)(OpCode >> 39) & 7);
+        }
+
+        public static ShaderIrInst GetCmp(long OpCode)
+        {
+            switch ((int)(OpCode >> 48) & 0xf)
+            {
+                case 0x1: return ShaderIrInst.Clt;
+                case 0x2: return ShaderIrInst.Ceq;
+                case 0x3: return ShaderIrInst.Cle;
+                case 0x4: return ShaderIrInst.Cgt;
+                case 0x5: return ShaderIrInst.Cne;
+                case 0x6: return ShaderIrInst.Cge;
+                case 0x7: return ShaderIrInst.Cnum;
+                case 0x8: return ShaderIrInst.Cnan;
+                case 0x9: return ShaderIrInst.Cltu;
+                case 0xa: return ShaderIrInst.Cequ;
+                case 0xb: return ShaderIrInst.Cleu;
+                case 0xc: return ShaderIrInst.Cgtu;
+                case 0xd: return ShaderIrInst.Cneu;
+                case 0xe: return ShaderIrInst.Cgeu;
+            }
+
+            throw new ArgumentException(nameof(OpCode));
+        }
+
+        public static ShaderIrInst GetBLop(long OpCode)
+        {
+            switch ((int)(OpCode >> 45) & 3)
+            {
+                case 0: return ShaderIrInst.Band;
+                case 1: return ShaderIrInst.Bor;
+                case 2: return ShaderIrInst.Bxor;
+            }
+
+            throw new ArgumentException(nameof(OpCode));
+        }
+
+        public static ShaderIrNode GetPredNode(ShaderIrNode Node, long OpCode)
+        {
+            ShaderIrOperPred Pred = GetPredNode(OpCode);
+
+            if (Pred.Index != ShaderIrOperPred.UnusedIndex)
+            {
+                Node = new ShaderIrCond(Pred, Node);
+            }
+
+            return Node;
+        }
+
+        private static ShaderIrOperPred GetPredNode(long OpCode)
+        {
+            int Pred = (int)(OpCode >> 16) & 0xf;
+
+            if (Pred != 0xf)
+            {
+                Pred &= 7;
+            }
+
+            return new ShaderIrOperPred(Pred);
+        }
+
+        public static ShaderIrNode GetAluAbsNeg(ShaderIrNode Node, bool Abs, bool Neg)
+        {
+            return GetAluNeg(GetAluAbs(Node, Abs), Neg);
+        }
+
+        public static ShaderIrNode GetAluAbs(ShaderIrNode Node, bool Abs)
+        {
+            return Abs ? new ShaderIrOp(ShaderIrInst.Fabs, Node) : Node;
+        }
+
+        public static ShaderIrNode GetAluNeg(ShaderIrNode Node, bool Neg)
+        {
+            return Neg ? new ShaderIrOp(ShaderIrInst.Fneg, Node) : Node;
+        }
+
+        public static ShaderIrNode GetAluNot(ShaderIrNode Node, bool Not)
+        {
+            return Not ? new ShaderIrOp(ShaderIrInst.Not, Node) : Node;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
new file mode 100644
index 0000000000..fd18ce0771
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMem.cs
@@ -0,0 +1,59 @@
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static partial class ShaderDecode
+    {
+        public static void Ld_A(ShaderIrBlock Block, long OpCode)
+        {
+            ShaderIrNode[] Opers = GetOperAbuf20(OpCode);
+
+            int Index = 0;
+
+            foreach (ShaderIrNode OperA in Opers)
+            {
+                ShaderIrOperGpr OperD = GetOperGpr0(OpCode);
+
+                OperD.Index += Index++;
+
+                Block.AddNode(GetPredNode(new ShaderIrAsg(OperD, OperA), OpCode));
+            }
+        }
+
+        public static void St_A(ShaderIrBlock Block, long OpCode)
+        {
+            ShaderIrNode[] Opers = GetOperAbuf20(OpCode);
+
+            int Index = 0;
+
+            foreach (ShaderIrNode OperA in Opers)
+            {
+                ShaderIrOperGpr OperD = GetOperGpr0(OpCode);
+
+                OperD.Index += Index++;
+
+                Block.AddNode(GetPredNode(new ShaderIrAsg(OperA, OperD), OpCode));
+            }
+        }
+
+        public static void Texs(ShaderIrBlock Block, long OpCode)
+        {
+            //TODO: Support other formats.
+            ShaderIrNode OperA = GetOperGpr8    (OpCode);
+            ShaderIrNode OperB = GetOperGpr20   (OpCode);
+            ShaderIrNode OperC = GetOperGpr28   (OpCode);
+            ShaderIrNode OperD = GetOperImm13_36(OpCode);
+
+            for (int Ch = 0; Ch < 4; Ch++)
+            {
+                ShaderIrOp Op = new ShaderIrOp(ShaderIrInst.Texr + Ch, OperA, OperB, OperD);
+
+                ShaderIrOperGpr Dst = GetOperGpr0(OpCode);
+
+                Dst.Index += Ch;
+
+                Block.AddNode(new ShaderIrAsg(Dst, Op));
+            }            
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
new file mode 100644
index 0000000000..50c740bf9a
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeMove.cs
@@ -0,0 +1,128 @@
+using System;
+
+using static Ryujinx.Graphics.Gal.Shader.ShaderDecodeHelper;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static partial class ShaderDecode
+    {
+        private enum IntType
+        {
+            U8  = 0,
+            U16 = 1,
+            U32 = 2,
+            U64 = 3,
+            S8  = 4,
+            S16 = 5,
+            S32 = 6,
+            S64 = 7
+        }
+
+        private enum FloatType
+        {
+            F16 = 1,
+            F32 = 2,
+            F64 = 3
+        }
+
+        public static void I2f_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitI2f(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void I2f_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitI2f(Block, OpCode, ShaderOper.Imm);
+        }
+
+        public static void I2f_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitI2f(Block, OpCode, ShaderOper.RR);
+        }
+
+        private static void EmitI2f(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 Na = ((OpCode >> 45) & 1) != 0;
+            bool Aa = ((OpCode >> 49) & 1) != 0;
+
+            ShaderIrNode OperA;
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:  OperA = GetOperCbuf34  (OpCode); break;
+                case ShaderOper.Imm: OperA = GetOperImm19_20(OpCode); break;
+                case ShaderOper.RR:  OperA = GetOperGpr20   (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            OperA = GetAluAbsNeg(OperA, Aa, Na);
+
+            bool Signed = Type >= IntType.S8;
+
+            int Shift = Sel * 8;
+
+            int Size = 8 << ((int)Type & 3);
+
+            ulong Mask = ulong.MaxValue >> (64 - Size);
+
+            int Mask32 = (int)Mask;
+
+            if (Shift != 0)
+            {
+                OperA = new ShaderIrOp(ShaderIrInst.Asr, OperA, new ShaderIrOperImm(Shift));
+            }
+
+            if (Mask != uint.MaxValue)
+            {
+                OperA = new ShaderIrOp(ShaderIrInst.And, OperA, new ShaderIrOperImm(Mask32));
+            }
+
+            ShaderIrInst Inst = Signed
+                ? ShaderIrInst.Stof
+                : ShaderIrInst.Utof;
+
+            ShaderIrNode Op = new ShaderIrOp(Inst, OperA);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
+        }
+
+        public static void Mov32i(ShaderIrBlock Block, long OpCode)
+        {
+            ShaderIrOperImm Imm = GetOperImm32_20(OpCode);
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Imm), OpCode));
+        }
+
+        private static IntType GetIntType(long OpCode)
+        {
+            bool Signed = ((OpCode >> 13) & 1) != 0;
+
+            IntType Type = (IntType)((OpCode >> 10) & 3);
+
+            if (Signed)
+            {
+                Type += (int)IntType.S8;
+            }
+
+            return Type;
+        }
+
+        private static FloatType GetFloatType(long OpCode)
+        {
+            return (FloatType)((OpCode >> 8) & 3);
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
new file mode 100644
index 0000000000..779bbf9230
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
@@ -0,0 +1,41 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static class ShaderDecoder
+    {
+        public static ShaderIrBlock DecodeBasicBlock(int[] Code, int Offset, GalShaderType ShaderType)
+        {
+            ShaderIrBlock Block = new ShaderIrBlock();
+
+            while (Offset + 2 <= Code.Length)
+            {
+                uint Word0 = (uint)Code[Offset++];
+                uint Word1 = (uint)Code[Offset++];
+
+                long OpCode = Word0 | (long)Word1 << 32;
+
+                ShaderDecodeFunc Decode = ShaderOpCodeTable.GetDecoder(OpCode);
+
+                if (Decode == null)
+                {
+                    continue;
+                }
+
+                Decode(Block, OpCode);
+
+                if (Block.GetLastNode() is ShaderIrOp Op && IsFlowChange(Op.Inst))
+                {
+                    break;
+                }
+            }
+
+            Block.RunOptimizationPasses(ShaderType);
+
+            return Block;
+        }
+
+        private static bool IsFlowChange(ShaderIrInst Inst)
+        {
+            return Inst == ShaderIrInst.Exit;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs
new file mode 100644
index 0000000000..00f8f6a5e5
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrAsg.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrAsg : ShaderIrNode
+    {
+        public ShaderIrNode Dst { get; set; }
+        public ShaderIrNode Src { get; set; }
+
+        public ShaderIrAsg(ShaderIrNode Dst, ShaderIrNode Src)
+        {
+            this.Dst = Dst;
+            this.Src = Src;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
new file mode 100644
index 0000000000..1a96d3be90
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
@@ -0,0 +1,39 @@
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrBlock
+    {
+        private List<ShaderIrNode> Nodes;
+
+        public ShaderIrBlock()
+        {
+            Nodes = new List<ShaderIrNode>();
+        }
+
+        public void AddNode(ShaderIrNode Node)
+        {
+            Nodes.Add(Node);
+        }
+
+        public void RunOptimizationPasses(GalShaderType ShaderType)
+        {
+            ShaderOptExprProp.Optimize(Nodes, ShaderType);
+        }
+
+        public ShaderIrNode[] GetNodes()
+        {
+            return Nodes.ToArray();
+        }
+
+        public ShaderIrNode GetLastNode()
+        {
+            if (Nodes.Count > 0)
+            {
+                return Nodes[Nodes.Count - 1];
+            }
+
+            return null;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs
new file mode 100644
index 0000000000..d8c87b4907
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrCond.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrCond : ShaderIrNode
+    {
+        public ShaderIrNode Pred  { get; set; }
+        public ShaderIrNode Child { get; set; }
+
+        public ShaderIrCond(ShaderIrNode Pred, ShaderIrNode Child)
+        {
+            this.Pred  = Pred;
+            this.Child = Child;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
new file mode 100644
index 0000000000..b6f4e80b98
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
@@ -0,0 +1,59 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    enum ShaderIrInst
+    {
+        B_Start,
+        Band,
+        Bnot,
+        Bor,
+        Bxor,
+        Clt,
+        Ceq,
+        Cle,
+        Cgt,
+        Cne,
+        Cge,
+        Cnum,
+        Cnan,
+        Cltu,
+        Cequ,
+        Cleu,
+        Cgtu,
+        Cneu,
+        Cgeu,
+        B_End,
+
+        F_Start,
+        Fabs,
+        Fadd,
+        Fcos,
+        Fex2,
+        Ffma,
+        Flg2,
+        Fmul,
+        Fneg,
+        Frcp,
+        Frsq,
+        Fsin,
+        Ipa,
+        Texr,
+        Texg,
+        Texb,
+        Texa,
+        F_End,
+
+        I_Start,
+        And,
+        Asr,
+        Lsr,
+        Not,
+        Or,
+        Stof,
+        Utof,
+        Xor,
+        I_End,
+
+        Exit,
+        Kil
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs
new file mode 100644
index 0000000000..2648164a11
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrNode.cs
@@ -0,0 +1,4 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrNode { }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs
new file mode 100644
index 0000000000..cd2107570c
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOp.cs
@@ -0,0 +1,22 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOp : ShaderIrNode
+    {
+        public ShaderIrInst Inst     { get; private set; }
+        public ShaderIrNode OperandA { get; set; }
+        public ShaderIrNode OperandB { get; set; }
+        public ShaderIrNode OperandC { get; set; }
+
+        public ShaderIrOp(
+            ShaderIrInst Inst,
+            ShaderIrNode OperandA = null,
+            ShaderIrNode OperandB = null,
+            ShaderIrNode OperandC = null)
+        {
+            this.Inst     = Inst;
+            this.OperandA = OperandA;
+            this.OperandB = OperandB;
+            this.OperandC = OperandC;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs
new file mode 100644
index 0000000000..fa612de76a
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperAbuf.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOperAbuf : ShaderIrNode
+    {
+        public int Offs     { get; private set; }
+        public int GprIndex { get; private set; }
+
+        public ShaderIrOperAbuf(int Offs, int GprIndex)
+        {
+            this.Offs     = Offs;
+            this.GprIndex = GprIndex;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs
new file mode 100644
index 0000000000..f227205630
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperCbuf.cs
@@ -0,0 +1,14 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOperCbuf : ShaderIrNode
+    {
+        public int Index { get; private set; }
+        public int Offs  { get; private set; }
+
+        public ShaderIrOperCbuf(int Index, int Offs)
+        {
+            this.Index = Index;
+            this.Offs  = Offs;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs
new file mode 100644
index 0000000000..5c69d6a67a
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperGpr.cs
@@ -0,0 +1,16 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOperGpr : ShaderIrNode
+    {
+        public const int ZRIndex = 0xff;
+
+        public bool IsConst => Index == ZRIndex;
+
+        public int Index { get; set; }
+
+        public ShaderIrOperGpr(int Index)
+        {
+            this.Index = Index;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs
new file mode 100644
index 0000000000..ba2c2c9b24
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImm.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOperImm : ShaderIrNode
+    {
+        public int Value { get; private set; }
+
+        public ShaderIrOperImm(int Value)
+        {
+            this.Value = Value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs
new file mode 100644
index 0000000000..3c27e48361
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperImmf.cs
@@ -0,0 +1,12 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOperImmf : ShaderIrNode
+    {
+        public float Value { get; private set; }
+
+        public ShaderIrOperImmf(float Value)
+        {
+            this.Value = Value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs
new file mode 100644
index 0000000000..74cca0efef
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrOperPred.cs
@@ -0,0 +1,17 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    class ShaderIrOperPred : ShaderIrNode
+    {
+        public const int UnusedIndex  = 0x7;
+        public const int NeverExecute = 0xf;
+
+        public bool IsConst => Index >= UnusedIndex;
+
+        public int Index { get; set; }
+
+        public ShaderIrOperPred(int Index)
+        {
+            this.Index = Index;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
new file mode 100644
index 0000000000..48c3b2ee5e
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
@@ -0,0 +1,97 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static class ShaderOpCodeTable
+    {
+        private const int EncodingBits = 14;
+
+        private static ShaderDecodeFunc[] OpCodes;
+
+        static ShaderOpCodeTable()
+        {
+            OpCodes = new ShaderDecodeFunc[1 << EncodingBits];
+
+#region Instructions
+            Set("111000110000xx", ShaderDecode.Exit);
+            Set("0100110001011x", ShaderDecode.Fadd_C);
+            Set("0011100x01011x", ShaderDecode.Fadd_I);
+            Set("0101110001011x", ShaderDecode.Fadd_R);
+            Set("010010011xxxxx", ShaderDecode.Ffma_CR);
+            Set("001100101xxxxx", ShaderDecode.Ffma_I);
+            Set("010100011xxxxx", ShaderDecode.Ffma_RC);
+            Set("010110011xxxxx", ShaderDecode.Ffma_RR);
+            Set("0100110001101x", ShaderDecode.Fmul_C);
+            Set("0011100x01101x", ShaderDecode.Fmul_I);
+            Set("0101110001101x", ShaderDecode.Fmul_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("11100000xxxxxx", ShaderDecode.Ipa);
+            Set("111000110011xx", ShaderDecode.Kil);
+            Set("1110111111011x", ShaderDecode.Ld_A);
+            Set("000001xxxxxxxx", ShaderDecode.Lop32i);
+            Set("000000010000xx", ShaderDecode.Mov32i);
+            Set("0101000010000x", ShaderDecode.Mufu);
+            Set("0100110000101x", ShaderDecode.Shr_C);
+            Set("0011100x00101x", ShaderDecode.Shr_I);
+            Set("0101110000101x", ShaderDecode.Shr_R);
+            Set("1110111111110x", ShaderDecode.St_A);
+            Set("1101100xxxxxxx", ShaderDecode.Texs);
+#endregion
+        }
+
+        private static void Set(string Encoding, ShaderDecodeFunc Func)
+        {
+            if (Encoding.Length != EncodingBits)
+            {
+                throw new ArgumentException(nameof(Encoding));
+            }
+
+            int Bit   = Encoding.Length - 1;
+            int Value = 0;
+            int XMask = 0;
+            int XBits = 0;
+
+            int[] XPos = new int[Encoding.Length];
+
+            for (int Index = 0; Index < Encoding.Length; Index++, Bit--)
+            {
+                char Chr = Encoding[Index];
+
+                if (Chr == '1')
+                {
+                    Value |= 1 << Bit;
+                }
+                else if (Chr == 'x')
+                {
+                    XMask |= 1 << Bit;
+
+                    XPos[XBits++] = Bit;
+                }
+            }
+
+            XMask = ~XMask;
+
+            for (int Index = 0; Index < (1 << XBits); Index++)
+            {
+                Value &= XMask;
+
+                for (int X = 0; X < XBits; X++)
+                {
+                    Value |= ((Index >> X) & 1) << XPos[X];
+                }
+
+                OpCodes[Value] = Func;
+            }
+        }
+
+        public static ShaderDecodeFunc GetDecoder(long OpCode)
+        {
+            return OpCodes[(ulong)OpCode >> (64 - EncodingBits)];
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs
new file mode 100644
index 0000000000..7989deed1e
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOper.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    enum ShaderOper
+    {
+        CR,
+        RC,
+        RR,
+        Imm,
+        Immf
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs
new file mode 100644
index 0000000000..69457aebde
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOptExprProp.cs
@@ -0,0 +1,266 @@
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gal.Shader
+{
+    static class ShaderOptExprProp
+    {
+        private struct UseSite
+        {
+            public object Parent;
+
+            public int OperIndex;
+
+            public UseSite(object Parent, int OperIndex)
+            {
+                this.Parent    = Parent;
+                this.OperIndex = OperIndex;
+            }
+        }
+
+        private class RegUse
+        {
+            public ShaderIrAsg Asg { get; private set; }
+
+            public int AsgIndex { get; private set; }
+
+            private bool Propagate;
+
+            private List<UseSite> Sites;
+
+            public RegUse()
+            {
+                Sites = new List<UseSite>();
+            }
+
+            public void AddUseSite(UseSite Site)
+            {
+                Sites.Add(Site);
+            }
+
+            public bool TryPropagate()
+            {
+                //This happens when a untiliazied register is used,
+                //this usually indicates a decoding error, but may also
+                //be cased by bogus programs (?). In any case, we just
+                //keep the unitialized access and avoid trying to propagate
+                //the expression (since we can't propagate what doesn't yet exist).
+                if (Asg == null || !Propagate)
+                {
+                    return false;
+                }
+
+                if (Sites.Count > 0)
+                {
+                    foreach (UseSite Site in Sites)
+                    {
+                        if (Site.Parent is ShaderIrCond Cond)
+                        {
+                            switch (Site.OperIndex)
+                            {
+                                case 0: Cond.Pred  = Asg.Src; break;
+                                case 1: Cond.Child = Asg.Src; break;
+
+                                default: throw new InvalidOperationException();
+                            }
+                        }
+                        else if (Site.Parent is ShaderIrOp Op)
+                        {
+                            switch (Site.OperIndex)
+                            {
+                                case 0: Op.OperandA = Asg.Src; break;
+                                case 1: Op.OperandB = Asg.Src; break;
+                                case 2: Op.OperandC = Asg.Src; break;
+
+                                default: throw new InvalidOperationException();
+                            }
+                        }
+                        else if (Site.Parent is ShaderIrAsg SiteAsg)
+                        {
+                            SiteAsg.Src = Asg.Src;
+                        }
+                        else
+                        {
+                            throw new InvalidOperationException();
+                        }
+                    }
+                }
+
+                return true;
+            }
+
+            public void SetNewAsg(ShaderIrAsg Asg, int AsgIndex, bool Propagate)
+            {
+                this.Asg       = Asg;
+                this.AsgIndex  = AsgIndex;
+                this.Propagate = Propagate;
+
+                Sites.Clear();
+            }
+        }
+
+        public static void Optimize(List<ShaderIrNode> Nodes, GalShaderType ShaderType)
+        {
+            Dictionary<int, RegUse> Uses = new Dictionary<int, RegUse>();
+
+            RegUse GetUse(int Key)
+            {
+                RegUse Use;
+
+                if (!Uses.TryGetValue(Key, out Use))
+                {
+                    Use = new RegUse();
+
+                    Uses.Add(Key, Use);
+                }
+
+                return Use;
+            }
+
+            int GetGprKey(int GprIndex)
+            {
+                return GprIndex;
+            }
+
+            int GetPredKey(int PredIndex)
+            {
+                return PredIndex | 0x10000000;
+            }
+
+            RegUse GetGprUse(int GprIndex)
+            {
+                return GetUse(GetGprKey(GprIndex));
+            }
+
+            RegUse GetPredUse(int PredIndex)
+            {
+                return GetUse(GetPredKey(PredIndex));
+            }
+
+            void FindRegUses(List<(int, UseSite)> UseList, object Parent, ShaderIrNode Node, int OperIndex = 0)
+            {
+                if (Node is ShaderIrAsg Asg)
+                {
+                    FindRegUses(UseList, Asg, Asg.Src);
+                }
+                else if (Node is ShaderIrCond Cond)
+                {
+                    FindRegUses(UseList, Cond, Cond.Pred,  0);
+                    FindRegUses(UseList, Cond, Cond.Child, 1);
+                }
+                else if (Node is ShaderIrOp Op)
+                {
+                    FindRegUses(UseList, Op, Op.OperandA, 0);
+                    FindRegUses(UseList, Op, Op.OperandB, 1);
+                    FindRegUses(UseList, Op, Op.OperandC, 2);
+                }
+                else if (Node is ShaderIrOperGpr Gpr && Gpr.Index != ShaderIrOperGpr.ZRIndex)
+                {
+                    UseList.Add((GetGprKey(Gpr.Index), new UseSite(Parent, OperIndex)));
+                }
+                else if (Node is ShaderIrOperPred Pred)
+                {
+                    UseList.Add((GetPredKey(Pred.Index), new UseSite(Parent, OperIndex)));
+                }
+            }
+
+            void TryAddRegUseSite(ShaderIrNode Node)
+            {
+                List<(int, UseSite)> UseList = new List<(int, UseSite)>();
+
+                FindRegUses(UseList, null, Node);
+
+                foreach ((int Key, UseSite Site) in UseList)
+                {
+                    GetUse(Key).AddUseSite(Site);
+                }
+            }
+
+            bool TryPropagate(RegUse Use)
+            {
+                //We can only propagate if the registers that the expression depends
+                //on weren't assigned after the original expression assignment
+                //to a register took place. We traverse the expression tree to find
+                //all registers being used, if any of those registers was assigned
+                //after the assignment to be propagated, then we can't propagate.
+                if (Use?.Asg == null)
+                {
+                    return false;
+                }
+
+                List<(int, UseSite)> UseList = new List<(int, UseSite)>();
+
+                FindRegUses(UseList, Use.Asg, Use.Asg.Src);
+
+                foreach ((int Key, UseSite Site) in UseList)
+                {
+                    if (GetUse(Key).AsgIndex >= Use.AsgIndex)
+                    {
+                        return false;
+                    }
+                }
+
+                return Use.TryPropagate();
+            }
+
+            for (int Index = 0, AsgIndex = 0; Index < Nodes.Count; Index++, AsgIndex++)
+            {
+                ShaderIrNode Node = Nodes[Index];
+
+                bool IsConditional = Node is ShaderIrCond;
+
+                TryAddRegUseSite(Node);
+
+                while (Node is ShaderIrCond Cond)
+                {
+                    Node = Cond.Child;
+                }
+
+                if (!(Node is ShaderIrAsg Asg))
+                {
+                    continue;
+                }
+
+                RegUse Use = null;
+
+                if (Asg.Dst is ShaderIrOperGpr Gpr && Gpr.Index != ShaderIrOperGpr.ZRIndex)
+                {
+                    Use = GetGprUse(Gpr.Index);
+                }
+                else if (Asg.Dst is ShaderIrOperPred Pred)
+                {
+                    Use = GetPredUse(Pred.Index);
+                }
+
+                if (!IsConditional && TryPropagate(Use))
+                {
+                    Nodes.Remove(Use.Asg);
+
+                    Index--;
+                }
+
+                //All nodes inside conditional nodes can't be propagated,
+                //as we don't even know if they will be executed to begin with.
+                Use?.SetNewAsg(Asg, AsgIndex, !IsConditional);
+            }
+
+            foreach (RegUse Use in Uses.Values)
+            {
+                //Gprs 0-3 are the color output on fragment shaders,
+                //so we can't remove the last assignments to those registers.
+                if (ShaderType == GalShaderType.Fragment)
+                {
+                    if (Use.Asg?.Dst is ShaderIrOperGpr Gpr && Gpr.Index < 4)
+                    {
+                        continue;
+                    }
+                }
+
+                if (TryPropagate(Use))
+                {
+                    Nodes.Remove(Use.Asg);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs
new file mode 100644
index 0000000000..d400850c86
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/ShaderDeclInfo.cs
@@ -0,0 +1,27 @@
+namespace Ryujinx.Graphics.Gal
+{
+    public class ShaderDeclInfo
+    {
+        public string Name { get; private set; }
+
+        public int Index { get; private set; }
+        public int Cbuf  { get; private set; }
+        public int Size  { get; private set; }
+
+        public ShaderDeclInfo(string Name, int Index, int Cbuf = 0, int Size = 1)
+        {
+            this.Name  = Name;
+            this.Index = Index;
+            this.Cbuf  = Cbuf;
+            this.Size  = Size;
+        }
+
+        internal void Enlarge(int NewSize)
+        {
+            if (Size < NewSize)
+            {
+                Size = NewSize;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/ShaderException.cs b/Ryujinx.Graphics/Gal/ShaderException.cs
new file mode 100644
index 0000000000..9bc87ff3db
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/ShaderException.cs
@@ -0,0 +1,11 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal
+{
+    class ShaderException : Exception
+    {
+        public ShaderException() : base() { }
+
+        public ShaderException(string Message) : base(Message) { }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/BCn.cs b/Ryujinx.Graphics/Gal/Texture/BCn.cs
similarity index 85%
rename from Ryujinx.Graphics/Gpu/BCn.cs
rename to Ryujinx.Graphics/Gal/Texture/BCn.cs
index b1caf46751..f23a86c2c6 100644
--- a/Ryujinx.Graphics/Gpu/BCn.cs
+++ b/Ryujinx.Graphics/Gal/Texture/BCn.cs
@@ -1,14 +1,14 @@
 using System;
 using System.Drawing;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Graphics.Gal.Texture
 {
     static class BCn
     {
-        public static byte[] DecodeBC1(NsGpuTexture Tex, int Offset)
+        public static byte[] DecodeBC1(GalTexture Texture, int Offset)
         {
-            int W = (Tex.Width  + 3) / 4;
-            int H = (Tex.Height + 3) / 4;
+            int W = (Texture.Width  + 3) / 4;
+            int H = (Texture.Height + 3) / 4;
 
             byte[] Output = new byte[W * H * 64];
 
@@ -20,7 +20,7 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     int IOffs = Offset + Swizzle.GetSwizzledAddress64(X, Y) * 8;
 
-                    byte[] Tile = BCnDecodeTile(Tex.Data, IOffs, true);
+                    byte[] Tile = BCnDecodeTile(Texture.Data, IOffs, true);
 
                     int TOffset = 0;
 
@@ -44,10 +44,10 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        public static byte[] DecodeBC2(NsGpuTexture Tex, int Offset)
+        public static byte[] DecodeBC2(GalTexture Texture, int Offset)
         {
-            int W = (Tex.Width  + 3) / 4;
-            int H = (Tex.Height + 3) / 4;
+            int W = (Texture.Width  + 3) / 4;
+            int H = (Texture.Height + 3) / 4;
 
             byte[] Output = new byte[W * H * 64];
 
@@ -59,10 +59,10 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16;
 
-                    byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false);
+                    byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false);
 
-                    int AlphaLow  = Get32(Tex.Data, IOffs + 0);
-                    int AlphaHigh = Get32(Tex.Data, IOffs + 4);
+                    int AlphaLow  = Get32(Texture.Data, IOffs + 0);
+                    int AlphaHigh = Get32(Texture.Data, IOffs + 4);
 
                     ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32;
 
@@ -90,10 +90,10 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        public static byte[] DecodeBC3(NsGpuTexture Tex, int Offset)
+        public static byte[] DecodeBC3(GalTexture Texture, int Offset)
         {
-            int W = (Tex.Width  + 3) / 4;
-            int H = (Tex.Height + 3) / 4;
+            int W = (Texture.Width  + 3) / 4;
+            int H = (Texture.Height + 3) / 4;
 
             byte[] Output = new byte[W * H * 64];
 
@@ -105,17 +105,17 @@ namespace Ryujinx.Graphics.Gpu
                 {
                     int IOffs = Offset + Swizzle.GetSwizzledAddress128(X, Y) * 16;
 
-                    byte[] Tile = BCnDecodeTile(Tex.Data, IOffs + 8, false);
+                    byte[] Tile = BCnDecodeTile(Texture.Data, IOffs + 8, false);
 
                     byte[] Alpha = new byte[8];
 
-                    Alpha[0] = Tex.Data[IOffs + 0];
-                    Alpha[1] = Tex.Data[IOffs + 1];
+                    Alpha[0] = Texture.Data[IOffs + 0];
+                    Alpha[1] = Texture.Data[IOffs + 1];
 
                     CalculateBC3Alpha(Alpha);
 
-                    int AlphaLow  = Get32(Tex.Data, IOffs + 2);
-                    int AlphaHigh = Get16(Tex.Data, IOffs + 6);
+                    int AlphaLow  = Get32(Texture.Data, IOffs + 2);
+                    int AlphaHigh = Get16(Texture.Data, IOffs + 6);
 
                     ulong AlphaCh = (uint)AlphaLow | (ulong)AlphaHigh << 32;
 
@@ -143,10 +143,10 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        public static byte[] DecodeBC4(NsGpuTexture Tex, int Offset)
+        public static byte[] DecodeBC4(GalTexture Texture, int Offset)
         {
-            int W = (Tex.Width  + 3) / 4;
-            int H = (Tex.Height + 3) / 4;
+            int W = (Texture.Width  + 3) / 4;
+            int H = (Texture.Height + 3) / 4;
 
             byte[] Output = new byte[W * H * 64];
 
@@ -160,13 +160,13 @@ namespace Ryujinx.Graphics.Gpu
 
                     byte[] Red = new byte[8];
 
-                    Red[0] = Tex.Data[IOffs + 0];
-                    Red[1] = Tex.Data[IOffs + 1];
+                    Red[0] = Texture.Data[IOffs + 0];
+                    Red[1] = Texture.Data[IOffs + 1];
 
                     CalculateBC3Alpha(Red);
 
-                    int RedLow  = Get32(Tex.Data, IOffs + 2);
-                    int RedHigh = Get16(Tex.Data, IOffs + 6);
+                    int RedLow  = Get32(Texture.Data, IOffs + 2);
+                    int RedHigh = Get16(Texture.Data, IOffs + 6);
 
                     ulong RedCh = (uint)RedLow | (ulong)RedHigh << 32;
 
@@ -194,10 +194,10 @@ namespace Ryujinx.Graphics.Gpu
             return Output;
         }
 
-        public static byte[] DecodeBC5(NsGpuTexture Tex, int Offset, bool SNorm)
+        public static byte[] DecodeBC5(GalTexture Texture, int Offset, bool SNorm)
         {
-            int W = (Tex.Width  + 3) / 4;
-            int H = (Tex.Height + 3) / 4;
+            int W = (Texture.Width  + 3) / 4;
+            int H = (Texture.Height + 3) / 4;
 
             byte[] Output = new byte[W * H * 64];
 
@@ -212,11 +212,11 @@ namespace Ryujinx.Graphics.Gpu
                     byte[] Red   = new byte[8];
                     byte[] Green = new byte[8];
 
-                    Red[0]   = Tex.Data[IOffs + 0];
-                    Red[1]   = Tex.Data[IOffs + 1];
+                    Red[0]   = Texture.Data[IOffs + 0];
+                    Red[1]   = Texture.Data[IOffs + 1];
 
-                    Green[0] = Tex.Data[IOffs + 8];
-                    Green[1] = Tex.Data[IOffs + 9];
+                    Green[0] = Texture.Data[IOffs + 8];
+                    Green[1] = Texture.Data[IOffs + 9];
 
                     if (SNorm)
                     {
@@ -229,11 +229,11 @@ namespace Ryujinx.Graphics.Gpu
                         CalculateBC3Alpha(Green);
                     }
 
-                    int RedLow    = Get32(Tex.Data, IOffs + 2);
-                    int RedHigh   = Get16(Tex.Data, IOffs + 6);
+                    int RedLow    = Get32(Texture.Data, IOffs + 2);
+                    int RedHigh   = Get16(Texture.Data, IOffs + 6);
 
-                    int GreenLow  = Get32(Tex.Data, IOffs + 10);
-                    int GreenHigh = Get16(Tex.Data, IOffs + 14);
+                    int GreenLow  = Get32(Texture.Data, IOffs + 10);
+                    int GreenHigh = Get16(Texture.Data, IOffs + 14);
 
                     ulong RedCh   = (uint)RedLow   | (ulong)RedHigh   << 32;
                     ulong GreenCh = (uint)GreenLow | (ulong)GreenHigh << 32;
diff --git a/Ryujinx.Graphics/Gpu/SwizzleAddr.cs b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs
similarity index 98%
rename from Ryujinx.Graphics/Gpu/SwizzleAddr.cs
rename to Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs
index 08e61eb58f..b67b841bcc 100644
--- a/Ryujinx.Graphics/Gpu/SwizzleAddr.cs
+++ b/Ryujinx.Graphics/Gal/Texture/SwizzleAddr.cs
@@ -1,6 +1,6 @@
 using System;
 
-namespace Ryujinx.Graphics.Gpu
+namespace Ryujinx.Graphics.Gal.Texture
 {
     class SwizzleAddr
     {
@@ -109,7 +109,7 @@ namespace Ryujinx.Graphics.Gpu
              *     y x x x x x x y y y y x y y x y 0 0 0 0 1024 x 1024 dxt5
              *   y y x x x x x x y y y y x y y x y x 0 0 0 2048 x 2048 dxt1
              * y y y x x x x x x y y y y x y y x y x x 0 0 1024 x 1024 rgba8888
-             * 
+             *
              * Read from right to left, LSB first.
              */
             int XCnt    = XBase;
diff --git a/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs
new file mode 100644
index 0000000000..4e50db51dd
--- /dev/null
+++ b/Ryujinx.Graphics/Gal/Texture/TextureDecoder.cs
@@ -0,0 +1,19 @@
+using System;
+
+namespace Ryujinx.Graphics.Gal.Texture
+{
+    static class TextureDecoder
+    {
+        public static byte[] Decode(GalTexture Texture)
+        {
+            switch (Texture.Format)
+            {
+                case GalTextureFormat.BC1: return BCn.DecodeBC1(Texture, 0);
+                case GalTextureFormat.BC2: return BCn.DecodeBC2(Texture, 0);
+                case GalTextureFormat.BC3: return BCn.DecodeBC3(Texture, 0);
+            }
+
+            throw new NotImplementedException(Texture.Format.ToString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs b/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs
new file mode 100644
index 0000000000..d2cbb14432
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/BlockLinearSwizzle.cs
@@ -0,0 +1,57 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    class BlockLinearSwizzle : ISwizzle
+    {
+        private int BhShift;
+        private int BppShift;
+        private int BhMask;
+
+        private int XShift;
+        private int GobStride;
+
+        public BlockLinearSwizzle(int Width, int Bpp, int BlockHeight = 16)
+        {
+            BhMask = (BlockHeight * 8) - 1;
+
+            BhShift  = CountLsbZeros(BlockHeight * 8);
+            BppShift = CountLsbZeros(Bpp);
+
+            int WidthInGobs = Width * Bpp / 64;
+
+            GobStride = 512 * BlockHeight * WidthInGobs;
+
+            XShift = CountLsbZeros(512 * BlockHeight);
+        }
+
+        private int CountLsbZeros(int Value)
+        {
+            int Count = 0;
+
+            while (((Value >> Count) & 1) == 0)
+            {
+                Count++;
+            }
+
+            return Count;
+        }
+
+        public int GetSwizzleOffset(int X, int Y)
+        {
+            X <<= BppShift;
+
+            int Position = (Y >> BhShift) * GobStride;
+
+            Position += (X >> 6) << XShift;
+
+            Position += ((Y & BhMask) >> 3) << 9;
+
+            Position += ((X & 0x3f) >> 5) << 8;
+            Position += ((Y & 0x07) >> 1) << 6;
+            Position += ((X & 0x1f) >> 4) << 5;
+            Position += ((Y & 0x01) >> 0) << 4;
+            Position += ((X & 0x0f) >> 0) << 0;
+
+            return Position;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/INvGpuEngine.cs b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs
new file mode 100644
index 0000000000..17e9b435c8
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/INvGpuEngine.cs
@@ -0,0 +1,11 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    interface INvGpuEngine
+    {
+        int[] Registers { get; }
+
+        void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry);
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/ISwizzle.cs b/Ryujinx.Graphics/Gpu/ISwizzle.cs
new file mode 100644
index 0000000000..755051d0c4
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/ISwizzle.cs
@@ -0,0 +1,7 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    interface ISwizzle
+    {
+        int GetSwizzleOffset(int X, int Y);
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/LinearSwizzle.cs b/Ryujinx.Graphics/Gpu/LinearSwizzle.cs
new file mode 100644
index 0000000000..01f09f8160
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/LinearSwizzle.cs
@@ -0,0 +1,20 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    class LinearSwizzle : ISwizzle
+    {
+        private int Bpp;
+        private int Stride;
+
+        public LinearSwizzle(int Width, int Bpp)
+        {
+            this.Bpp = Bpp;
+
+            Stride = Width * Bpp;
+        }
+
+        public int GetSwizzleOffset(int X, int Y)
+        {
+            return X * Bpp + Y * Stride;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/MacroInterpreter.cs b/Ryujinx.Graphics/Gpu/MacroInterpreter.cs
new file mode 100644
index 0000000000..233baac8bd
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/MacroInterpreter.cs
@@ -0,0 +1,420 @@
+using ChocolArm64.Memory;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    class MacroInterpreter
+    {
+        private enum AssignmentOperation
+        {
+            IgnoreAndFetch                  = 0,
+            Move                            = 1,
+            MoveAndSetMaddr                 = 2,
+            FetchAndSend                    = 3,
+            MoveAndSend                     = 4,
+            FetchAndSetMaddr                = 5,
+            MoveAndSetMaddrThenFetchAndSend = 6,
+            MoveAndSetMaddrThenSendHigh     = 7
+        }
+
+        private enum AluOperation
+        {
+            AluReg                = 0,
+            AddImmediate          = 1,
+            BitfieldReplace       = 2,
+            BitfieldExtractLslImm = 3,
+            BitfieldExtractLslReg = 4,
+            ReadImmediate         = 5
+        }
+
+        private enum AluRegOperation
+        {
+            Add                = 0,
+            AddWithCarry       = 1,
+            Subtract           = 2,
+            SubtractWithBorrow = 3,
+            BitwiseExclusiveOr = 8,
+            BitwiseOr          = 9,
+            BitwiseAnd         = 10,
+            BitwiseAndNot      = 11,
+            BitwiseNotAnd      = 12
+        }
+
+        private NvGpuFifo    PFifo;
+        private INvGpuEngine Engine;
+
+        public Queue<int> Fifo { get; private set; }
+
+        private int[] Gprs;
+
+        private int MethAddr;
+        private int MethIncr;
+
+        private bool Carry;
+
+        private int OpCode;
+
+        private int PipeOp;
+
+        private long Pc;
+
+        public MacroInterpreter(NvGpuFifo PFifo, INvGpuEngine Engine)
+        {
+            this.PFifo  = PFifo;
+            this.Engine = Engine;
+
+            Fifo = new Queue<int>();
+
+            Gprs = new int[8];
+        }
+
+        public void Execute(AMemory Memory, long Position, int Param)
+        {
+            Reset();
+
+            Gprs[1] = Param;
+
+            Pc = Position;
+
+            FetchOpCode(Memory);
+
+            while (Step(Memory));
+
+            //Due to the delay slot, we still need to execute
+            //one more instruction before we actually exit.
+            Step(Memory);
+        }
+
+        private void Reset()
+        {
+            for (int Index = 0; Index < Gprs.Length; Index++)
+            {
+                Gprs[Index] = 0;
+            }
+
+            MethAddr = 0;
+            MethIncr = 0;
+
+            Carry = false;
+        }
+
+        private bool Step(AMemory Memory)
+        {
+            long BaseAddr = Pc - 4;
+
+            FetchOpCode(Memory);
+
+            if ((OpCode & 7) < 7)
+            {
+                //Operation produces a value.
+                AssignmentOperation AsgOp = (AssignmentOperation)((OpCode >> 4) & 7);
+
+                int Result = GetAluResult();
+
+                switch (AsgOp)
+                {
+                    //Fetch parameter and ignore result.
+                    case AssignmentOperation.IgnoreAndFetch:
+                    {
+                        SetDstGpr(FetchParam());
+
+                        break;
+                    }
+
+                    //Move result.
+                    case AssignmentOperation.Move:
+                    {
+                        SetDstGpr(Result);
+
+                        break;
+                    }
+
+                    //Move result and use as Method Address.
+                    case AssignmentOperation.MoveAndSetMaddr:
+                    {
+                        SetDstGpr(Result);
+
+                        SetMethAddr(Result);
+
+                        break;
+                    }
+
+                    //Fetch parameter and send result.
+                    case AssignmentOperation.FetchAndSend:
+                    {
+                        SetDstGpr(FetchParam());
+
+                        Send(Memory, Result);
+
+                        break;
+                    }
+
+                    //Move and send result.
+                    case AssignmentOperation.MoveAndSend:
+                    {
+                        SetDstGpr(Result);
+
+                        Send(Memory, Result);
+
+                        break;
+                    }
+
+                    //Fetch parameter and use result as Method Address.
+                    case AssignmentOperation.FetchAndSetMaddr:
+                    {
+                        SetDstGpr(FetchParam());
+
+                        SetMethAddr(Result);
+
+                        break;
+                    }
+
+                    //Move result and use as Method Address, then fetch and send paramter.
+                    case AssignmentOperation.MoveAndSetMaddrThenFetchAndSend:
+                    {
+                        SetDstGpr(Result);
+
+                        SetMethAddr(Result);
+
+                        Send(Memory, FetchParam());
+
+                        break;
+                    }
+
+                    //Move result and use as Method Address, then send bits 17:12 of result.
+                    case AssignmentOperation.MoveAndSetMaddrThenSendHigh:
+                    {
+                        SetDstGpr(Result);
+
+                        SetMethAddr(Result);
+
+                        Send(Memory, (Result >> 12) & 0x3f);
+
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                //Branch.
+                bool OnNotZero = ((OpCode >> 4) & 1) != 0;
+
+                bool Taken = OnNotZero
+                    ? GetGprA() != 0
+                    : GetGprA() == 0;
+
+                if (Taken)
+                {
+                    Pc = BaseAddr + (GetImm() << 2);
+
+                    bool NoDelays = (OpCode & 0x20) != 0;
+
+                    if (NoDelays)
+                    {
+                        FetchOpCode(Memory);
+                    }
+
+                    return true;
+                }
+            }
+
+            bool Exit = (OpCode & 0x80) != 0;
+
+            return !Exit;
+        }
+
+        private void FetchOpCode(AMemory Memory)
+        {
+            OpCode = PipeOp;
+
+            PipeOp = Memory.ReadInt32(Pc);
+
+            Pc += 4;
+        }
+
+        private int GetAluResult()
+        {
+            AluOperation Op = (AluOperation)(OpCode & 7);
+
+            switch (Op)
+            {
+                case AluOperation.AluReg:
+                {
+                    AluRegOperation AluOp = (AluRegOperation)((OpCode >> 17) & 0x1f);
+
+                    return GetAluResult(AluOp, GetGprA(), GetGprB());
+                }
+
+                case AluOperation.AddImmediate:
+                {
+                    return GetGprA() + GetImm();
+                }
+
+                case AluOperation.BitfieldReplace:
+                case AluOperation.BitfieldExtractLslImm:
+                case AluOperation.BitfieldExtractLslReg:
+                {
+                    int BfSrcBit = (OpCode >> 17) & 0x1f;
+                    int BfSize   = (OpCode >> 22) & 0x1f;
+                    int BfDstBit = (OpCode >> 27) & 0x1f;
+
+                    int BfMask = (1 << BfSize) - 1;
+
+                    int Dst = GetGprA();
+                    int Src = GetGprB();
+
+                    switch (Op)
+                    {
+                        case AluOperation.BitfieldReplace:
+                        {
+                            Src = (int)((uint)Src >> BfSrcBit) & BfMask;
+
+                            Dst &= ~(BfMask << BfDstBit);
+
+                            Dst |= Src << BfDstBit;
+
+                            return Dst;
+                        }
+
+                        case AluOperation.BitfieldExtractLslImm:
+                        {
+                            Src = (int)((uint)Src >> Dst) & BfMask;
+
+                            return Src << BfDstBit;
+                        }
+
+                        case AluOperation.BitfieldExtractLslReg:
+                        {
+                            Src = (int)((uint)Src >> BfSrcBit) & BfMask;
+
+                            return Src << Dst;
+                        }
+                    }
+
+                    break;
+                }
+
+                case AluOperation.ReadImmediate:
+                {
+                    return Read(GetGprA() + GetImm());
+                }
+            }
+
+            throw new ArgumentException(nameof(OpCode));
+        }
+
+        private int GetAluResult(AluRegOperation AluOp, int A, int B)
+        {
+            switch (AluOp)
+            {
+                case AluRegOperation.Add:
+                {
+                    ulong Result = (ulong)A + (ulong)B;
+
+                    Carry = Result > 0xffffffff;
+
+                    return (int)Result;
+                }
+
+                case AluRegOperation.AddWithCarry:
+                {
+                    ulong Result = (ulong)A + (ulong)B + (Carry ? 1UL : 0UL);
+
+                    Carry = Result > 0xffffffff;
+
+                    return (int)Result;
+                }
+
+                case AluRegOperation.Subtract:
+                {
+                    ulong Result = (ulong)A - (ulong)B;
+
+                    Carry = Result < 0x100000000;
+
+                    return (int)Result;
+                }
+
+                case AluRegOperation.SubtractWithBorrow:
+                {
+                    ulong Result = (ulong)A - (ulong)B - (Carry ? 0UL : 1UL);
+
+                    Carry = Result < 0x100000000;
+
+                    return (int)Result;
+                }
+
+                case AluRegOperation.BitwiseExclusiveOr: return   A ^  B;
+                case AluRegOperation.BitwiseOr:          return   A |  B;
+                case AluRegOperation.BitwiseAnd:         return   A &  B;
+                case AluRegOperation.BitwiseAndNot:      return   A & ~B;
+                case AluRegOperation.BitwiseNotAnd:      return ~(A &  B);
+            }
+
+            throw new ArgumentOutOfRangeException(nameof(AluOp));
+        }
+
+        private int GetImm()
+        {
+            //Note: The immediate is signed, the sign-extension is intended here.
+            return OpCode >> 14;
+        }
+
+        private void SetMethAddr(int Value)
+        {
+            MethAddr = (Value >>  0) & 0xfff;
+            MethIncr = (Value >> 12) & 0x3f;
+        }
+
+        private void SetDstGpr(int Value)
+        {
+            Gprs[(OpCode >> 8) & 7] = Value;
+        }
+
+        private int GetGprA()
+        {
+            return GetGprValue((OpCode >> 11) & 7);
+        }
+
+        private int GetGprB()
+        {
+            return GetGprValue((OpCode >> 14) & 7);
+        }
+
+        private int GetGprValue(int Index)
+        {
+            return Index != 0 ? Gprs[Index] : 0;
+        }
+
+        private int FetchParam()
+        {
+            int Value;
+
+            //If we don't have any parameters in the FIFO,
+            //keep running the PFIFO engine until it writes the parameters.
+            while (!Fifo.TryDequeue(out Value))
+            {
+                if (!PFifo.Step())
+                {
+                    return 0;
+                }
+            }
+
+            return Value;
+        }
+
+        private int Read(int Reg)
+        {
+            return Engine.Registers[Reg];
+        }
+
+        private void Send(AMemory Memory, int Value)
+        {
+            NsGpuPBEntry PBEntry = new NsGpuPBEntry(MethAddr, 0, Value);
+
+            Engine.CallMethod(Memory, PBEntry);
+
+            MethAddr += MethIncr;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpu.cs b/Ryujinx.Graphics/Gpu/NsGpu.cs
index 133d0af25b..5738050255 100644
--- a/Ryujinx.Graphics/Gpu/NsGpu.cs
+++ b/Ryujinx.Graphics/Gpu/NsGpu.cs
@@ -1,5 +1,5 @@
-using ChocolArm64.Memory;
 using Ryujinx.Graphics.Gal;
+using System.Threading;
 
 namespace Ryujinx.Graphics.Gpu
 {
@@ -9,7 +9,13 @@ namespace Ryujinx.Graphics.Gpu
 
         internal NsGpuMemoryMgr MemoryMgr { get; private set; }
 
-        internal NsGpuPGraph PGraph { get; private set; }
+        public NvGpuFifo Fifo;
+
+        internal NvGpuEngine3d Engine3d;
+
+        private Thread FifoProcessing;
+
+        private bool KeepRunning;
 
         public NsGpu(IGalRenderer Renderer)
         {
@@ -17,7 +23,15 @@ namespace Ryujinx.Graphics.Gpu
 
             MemoryMgr = new NsGpuMemoryMgr();
 
-            PGraph = new NsGpuPGraph(this);
+            Fifo = new NvGpuFifo(this);
+
+            Engine3d = new NvGpuEngine3d(this);
+
+            KeepRunning = true;
+
+            FifoProcessing = new Thread(ProcessFifo);            
+
+            FifoProcessing.Start();
         }
 
         public long GetCpuAddr(long Position)
@@ -35,11 +49,6 @@ namespace Ryujinx.Graphics.Gpu
             return MemoryMgr.Map(CpuAddr, GpuAddr, Size);
         }
 
-        public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory)
-        {
-            PGraph.ProcessPushBuffer(PushBuffer, Memory);
-        }
-
         public long ReserveMemory(long Size, long Align)
         {
             return MemoryMgr.Reserve(Size, Align);
@@ -49,5 +58,15 @@ namespace Ryujinx.Graphics.Gpu
         {
             return MemoryMgr.Reserve(GpuAddr, Size, Align);
         }
+
+        private void ProcessFifo()
+        {
+            while (KeepRunning)
+            {
+                Fifo.DispatchCalls();
+
+                Thread.Yield();
+            }
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs b/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs
index 8063651aa8..d405a93c6a 100644
--- a/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs
+++ b/Ryujinx.Graphics/Gpu/NsGpuPBEntry.cs
@@ -1,13 +1,11 @@
 using System;
-using System.Collections.Generic;
 using System.Collections.ObjectModel;
-using System.IO;
 
 namespace Ryujinx.Graphics.Gpu
 {
     public struct NsGpuPBEntry
     {
-        public NsGpuRegister Register { get; private set; }
+        public int Method { get; private set; }
 
         public int SubChannel { get; private set; }
 
@@ -15,65 +13,11 @@ namespace Ryujinx.Graphics.Gpu
 
         public ReadOnlyCollection<int> Arguments => Array.AsReadOnly(m_Arguments);
 
-        public NsGpuPBEntry(NsGpuRegister Register, int SubChannel, params int[] Arguments)
+        public NsGpuPBEntry(int Method, int SubChannel, params int[] Arguments)
         {
-            this.Register    = Register;
+            this.Method      = Method;
             this.SubChannel  = SubChannel;
             this.m_Arguments = Arguments;
         }
-
-        public static NsGpuPBEntry[] DecodePushBuffer(byte[] Data)
-        {
-            using (MemoryStream MS = new MemoryStream(Data))
-            {
-                BinaryReader Reader = new BinaryReader(MS);
-
-                List<NsGpuPBEntry> GpFifos = new List<NsGpuPBEntry>();
-
-                bool CanRead() => MS.Position + 4 <= MS.Length;
-
-                while (CanRead())
-                {
-                    int Packed = Reader.ReadInt32();
-
-                    int Reg  = (Packed << 2)  & 0x7ffc;
-                    int SubC = (Packed >> 13) & 7;
-                    int Args = (Packed >> 16) & 0x1fff;
-                    int Mode = (Packed >> 29) & 7;
-
-                    if (Mode == 4)
-                    {
-                        //Inline Mode.
-                        GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Args));
-                    }
-                    else
-                    {
-                        //Word mode.
-                        if (Mode == 1)
-                        {
-                            //Sequential Mode.
-                            for (int Index = 0; Index < Args && CanRead(); Index++, Reg += 4)
-                            {
-                                GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Reader.ReadInt32()));
-                            }
-                        }
-                        else
-                        {
-                            //Non-Sequential Mode.
-                            int[] Arguments = new int[Args];
-
-                            for (int Index = 0; Index < Args && CanRead(); Index++)
-                            {
-                                Arguments[Index] = Reader.ReadInt32();
-                            }
-
-                            GpFifos.Add(new NsGpuPBEntry((NsGpuRegister)Reg, SubC, Arguments));
-                        }
-                    }
-                }
-
-                return GpFifos.ToArray();
-            }
-        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs b/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs
deleted file mode 100644
index 652f3e7517..0000000000
--- a/Ryujinx.Graphics/Gpu/NsGpuPGraph.cs
+++ /dev/null
@@ -1,305 +0,0 @@
-using ChocolArm64.Memory;
-using Ryujinx.Graphics.Gal;
-using System.Collections.Generic;
-
-namespace Ryujinx.Graphics.Gpu
-{
-    class NsGpuPGraph
-    {
-        private NsGpu Gpu;
-
-        private uint[] Registers;
-
-        public NsGpuEngine[] SubChannels;
-
-        private Dictionary<long, int> CurrentVertexBuffers;
-
-        public NsGpuPGraph(NsGpu Gpu)
-        {
-            this.Gpu = Gpu;
-
-            Registers = new uint[0x1000];
-
-            SubChannels = new NsGpuEngine[8];
-
-            CurrentVertexBuffers = new Dictionary<long, int>();
-        }
-
-        public void ProcessPushBuffer(NsGpuPBEntry[] PushBuffer, AMemory Memory)
-        {
-            bool HasQuery = false;
-
-            foreach (NsGpuPBEntry Entry in PushBuffer)
-            {
-                if (Entry.Arguments.Count == 1)
-                {
-                    SetRegister(Entry.Register, (uint)Entry.Arguments[0]);
-                }
-
-                switch (Entry.Register)
-                {
-                    case NsGpuRegister.BindChannel:
-                        if (Entry.Arguments.Count > 0)
-                        {
-                            SubChannels[Entry.SubChannel] = (NsGpuEngine)Entry.Arguments[0];
-                        }
-                        break;
-
-                    case NsGpuRegister._3dVertexArray0Fetch:
-                        SendVertexBuffers(Memory);
-                        break;
-
-                    case NsGpuRegister._3dCbData0:
-                        if (GetRegister(NsGpuRegister._3dCbPos) == 0x20)
-                        {
-                            SendTexture(Memory);
-                        }
-                        break;
-
-                    case NsGpuRegister._3dQueryAddressHigh:
-                    case NsGpuRegister._3dQueryAddressLow:
-                    case NsGpuRegister._3dQuerySequence:
-                    case NsGpuRegister._3dQueryGet:
-                        HasQuery = true;
-                        break;
-
-                    case NsGpuRegister._3dSetShader:
-                        uint ShaderPrg  = (uint)Entry.Arguments[0];
-                        uint ShaderId   = (uint)Entry.Arguments[1];
-                        uint CodeAddr   = (uint)Entry.Arguments[2];
-                        uint ShaderType = (uint)Entry.Arguments[3];
-                        uint CodeEnd    = (uint)Entry.Arguments[4];
-
-                        SendShader(
-                            Memory,
-                            ShaderPrg,
-                            ShaderId,
-                            CodeAddr,
-                            ShaderType,
-                            CodeEnd);
-                        break;
-                }
-            }
-
-            if (HasQuery)
-            {
-                long Position =
-                    (long)GetRegister(NsGpuRegister._3dQueryAddressHigh) << 32 |
-                    (long)GetRegister(NsGpuRegister._3dQueryAddressLow)  << 0;
-
-                uint Seq = GetRegister(NsGpuRegister._3dQuerySequence);
-                uint Get = GetRegister(NsGpuRegister._3dQueryGet);
-
-                uint Mode = Get & 3;
-
-                if (Mode == 0)
-                {
-                    //Write
-                    Position = Gpu.MemoryMgr.GetCpuAddr(Position);
-
-                    if (Position != -1)
-                    {
-                        Gpu.Renderer.QueueAction(delegate()
-                        {
-                            Memory.WriteUInt32(Position, Seq);
-                        });
-                    }
-                }
-            }
-        }
-
-        private void SendVertexBuffers(AMemory Memory)
-        {
-            long Position =
-                (long)GetRegister(NsGpuRegister._3dVertexArray0StartHigh) << 32 |
-                (long)GetRegister(NsGpuRegister._3dVertexArray0StartLow)  << 0;
-
-            long Limit =
-                (long)GetRegister(NsGpuRegister._3dVertexArray0LimitHigh) << 32 |
-                (long)GetRegister(NsGpuRegister._3dVertexArray0LimitLow)  << 0;
-
-            int VbIndex = CurrentVertexBuffers.Count;
-
-            if (!CurrentVertexBuffers.TryAdd(Position, VbIndex))
-            {
-                VbIndex = CurrentVertexBuffers[Position];
-            }
-
-            if (Limit != 0)
-            {
-                long Size = (Limit - Position) + 1;
-
-                Position = Gpu.MemoryMgr.GetCpuAddr(Position);
-
-                if (Position != -1)
-                {
-                    byte[] Buffer = AMemoryHelper.ReadBytes(Memory, Position, Size);
-
-                    int Stride = (int)GetRegister(NsGpuRegister._3dVertexArray0Fetch) & 0xfff;
-
-                    List<GalVertexAttrib> Attribs = new List<GalVertexAttrib>();
-
-                    for (int Attr = 0; Attr < 16; Attr++)
-                    {
-                        int Packed = (int)GetRegister(NsGpuRegister._3dVertexAttrib0Format + Attr * 4);
-
-                        GalVertexAttrib Attrib = new GalVertexAttrib(Attr,
-                                                  (Packed >>  0) & 0x1f,
-                                                 ((Packed >>  6) & 0x1) != 0,
-                                                  (Packed >>  7) & 0x3fff,
-                            (GalVertexAttribSize)((Packed >> 21) & 0x3f),
-                            (GalVertexAttribType)((Packed >> 27) & 0x7),
-                                                 ((Packed >> 31) & 0x1) != 0);
-
-                        if (Attrib.Offset < Stride)
-                        {
-                            Attribs.Add(Attrib);
-                        }
-                    }
-
-                    Gpu.Renderer.QueueAction(delegate()
-                    {
-                        Gpu.Renderer.SendVertexBuffer(VbIndex, Buffer, Stride, Attribs.ToArray());
-                    });
-                }
-            }
-        }
-
-        private void SendTexture(AMemory Memory)
-        {
-            long TicPos = (long)GetRegister(NsGpuRegister._3dTicAddressHigh) << 32 |
-                          (long)GetRegister(NsGpuRegister._3dTicAddressLow)  << 0;
-
-            uint CbData = GetRegister(NsGpuRegister._3dCbData0);
-
-            uint TicIndex = (CbData >>  0) & 0xfffff;
-            uint TscIndex = (CbData >> 20) & 0xfff; //I guess?
-
-            TicPos = Gpu.MemoryMgr.GetCpuAddr(TicPos + TicIndex * 0x20);
-
-            if (TicPos != -1)
-            {
-                int Word0 = Memory.ReadInt32(TicPos + 0x0);
-                int Word1 = Memory.ReadInt32(TicPos + 0x4);
-                int Word2 = Memory.ReadInt32(TicPos + 0x8);
-                int Word3 = Memory.ReadInt32(TicPos + 0xc);
-                int Word4 = Memory.ReadInt32(TicPos + 0x10);
-                int Word5 = Memory.ReadInt32(TicPos + 0x14);
-                int Word6 = Memory.ReadInt32(TicPos + 0x18);
-                int Word7 = Memory.ReadInt32(TicPos + 0x1c);
-
-                long TexAddress = Word1;
-
-                TexAddress |= (long)(Word2 & 0xff) << 32;
-
-                TexAddress = Gpu.MemoryMgr.GetCpuAddr(TexAddress);
-
-                if (TexAddress != -1)
-                {
-                    NsGpuTextureFormat Format = (NsGpuTextureFormat)(Word0 & 0x7f);
-
-                    int Width  = (Word4 & 0xffff) + 1;
-                    int Height = (Word5 & 0xffff) + 1;
-
-                    byte[] Buffer = GetDecodedTexture(Memory, Format, TexAddress, Width, Height);
-
-                    if (Buffer != null)
-                    {
-                        Gpu.Renderer.QueueAction(delegate()
-                        {
-                            Gpu.Renderer.SendR8G8B8A8Texture(0, Buffer, Width, Height);
-                        });
-                    }
-                }
-            }
-        }
-
-        private void SendShader(
-            AMemory Memory,
-            uint    ShaderPrg,
-            uint    ShaderId,
-            uint    CodeAddr,
-            uint    ShaderType,
-            uint    CodeEnd)
-        {
-            long CodePos = Gpu.MemoryMgr.GetCpuAddr(CodeAddr);
-
-            byte[] Data = AMemoryHelper.ReadBytes(Memory, CodePos, 0x300);
-        }
-
-        private static byte[] GetDecodedTexture(
-            AMemory            Memory,
-            NsGpuTextureFormat Format,
-            long               Position,
-            int                Width,
-            int                Height)
-        {
-            byte[] Data = null;
-
-            switch (Format)
-            {
-                case NsGpuTextureFormat.BC1:
-                {
-                    int Size = (Width * Height) >> 1;
-
-                    Data = AMemoryHelper.ReadBytes(Memory, Position, Size);
-
-                    Data = BCn.DecodeBC1(new NsGpuTexture()
-                    {
-                        Width  = Width,
-                        Height = Height,
-                        Data   = Data
-                    }, 0);
-
-                    break;
-                }
-
-                case NsGpuTextureFormat.BC2:
-                {
-                    int Size = Width * Height;
-
-                    Data = AMemoryHelper.ReadBytes(Memory, Position, Size);
-
-                    Data = BCn.DecodeBC2(new NsGpuTexture()
-                    {
-                        Width  = Width,
-                        Height = Height,
-                        Data   = Data
-                    }, 0);
-
-                    break;
-                }
-
-                case NsGpuTextureFormat.BC3:
-                {
-                    int Size = Width * Height;
-
-                    Data = AMemoryHelper.ReadBytes(Memory, Position, Size);
-
-                    Data = BCn.DecodeBC3(new NsGpuTexture()
-                    {
-                        Width  = Width,
-                        Height = Height,
-                        Data   = Data
-                    }, 0);
-
-                    break;
-                }
-
-                //default: throw new NotImplementedException(Format.ToString());
-            }
-
-            return Data;
-        }
-
-        public uint GetRegister(NsGpuRegister Register)
-        {
-            return Registers[((int)Register >> 2) & 0xfff];
-        }
-
-        public void SetRegister(NsGpuRegister Register, uint Value)
-        {
-            Registers[((int)Register >> 2) & 0xfff] = Value;
-        }
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuRegister.cs b/Ryujinx.Graphics/Gpu/NsGpuRegister.cs
deleted file mode 100644
index 4642e68d67..0000000000
--- a/Ryujinx.Graphics/Gpu/NsGpuRegister.cs
+++ /dev/null
@@ -1,94 +0,0 @@
-namespace Ryujinx.Graphics.Gpu
-{
-    public enum NsGpuRegister
-    {
-        BindChannel               = 0,
-
-        _2dClipEnable             = 0x0290,
-        _2dOperation              = 0x02ac,
-
-        _3dGlobalBase             = 0x02c8,
-        _3dRt0AddressHigh         = 0x0800,
-        _3dRt0AddressLow          = 0x0804,
-        _3dRt0Horiz               = 0x0808,
-        _3dRt0Vert                = 0x080c,
-        _3dRt0Format              = 0x0810,
-        _3dRt0BlockDimensions     = 0x0814,
-        _3dRt0ArrayMode           = 0x0818,
-        _3dRt0LayerStride         = 0x081c,
-        _3dRt0BaseLayer           = 0x0820,
-        _3dViewportScaleX         = 0x0a00,
-        _3dViewportScaleY         = 0x0a04,
-        _3dViewportScaleZ         = 0x0a08,
-        _3dViewportTranslateX     = 0x0a0c,
-        _3dViewportTranslateY     = 0x0a10,
-        _3dViewportTranslateZ     = 0x0a14,
-        _3dViewportHoriz          = 0x0c00,
-        _3dViewportVert           = 0x0c04,
-        _3dDepthRangeNear         = 0x0c08,
-        _3dDepthRangeFar          = 0x0c0c,
-        _3dClearColorR            = 0x0d80,
-        _3dClearColorG            = 0x0d84,
-        _3dClearColorB            = 0x0d88,
-        _3dClearColorA            = 0x0d8c,
-        _3dScreenScissorHoriz     = 0x0ff4,
-        _3dScreenScissorVert      = 0x0ff8,
-        _3dVertexAttrib0Format    = 0x1160,
-        _3dVertexAttrib1Format    = 0x1164,
-        _3dVertexAttrib2Format    = 0x1168,
-        _3dVertexAttrib3Format    = 0x116c,
-        _3dVertexAttrib4Format    = 0x1170,
-        _3dVertexAttrib5Format    = 0x1174,
-        _3dVertexAttrib6Format    = 0x1178,
-        _3dVertexAttrib7Format    = 0x117c,
-        _3dVertexAttrib8Format    = 0x1180,
-        _3dVertexAttrib9Format    = 0x1184,
-        _3dVertexAttrib10Format   = 0x1188,
-        _3dVertexAttrib11Format   = 0x118c,
-        _3dVertexAttrib12Format   = 0x1190,
-        _3dVertexAttrib13Format   = 0x1194,
-        _3dVertexAttrib14Format   = 0x1198,
-        _3dVertexAttrib15Format   = 0x119c,
-        _3dScreenYControl         = 0x13ac,
-        _3dTscAddressHigh         = 0x155c,
-        _3dTscAddressLow          = 0x1560,
-        _3dTscLimit               = 0x1564,
-        _3dTicAddressHigh         = 0x1574,
-        _3dTicAddressLow          = 0x1578,
-        _3dTicLimit               = 0x157c,
-        _3dMultiSampleMode        = 0x15d0,
-        _3dVertexEndGl            = 0x1614,
-        _3dVertexBeginGl          = 0x1618,
-        _3dQueryAddressHigh       = 0x1b00,
-        _3dQueryAddressLow        = 0x1b04,
-        _3dQuerySequence          = 0x1b08,
-        _3dQueryGet               = 0x1b0c,
-        _3dVertexArray0Fetch      = 0x1c00,
-        _3dVertexArray0StartHigh  = 0x1c04,
-        _3dVertexArray0StartLow   = 0x1c08,
-        _3dVertexArray1Fetch      = 0x1c10, //todo: the rest
-        _3dVertexArray0LimitHigh  = 0x1f00,
-        _3dVertexArray0LimitLow   = 0x1f04,
-        _3dCbSize                 = 0x2380,
-        _3dCbAddressHigh          = 0x2384,
-        _3dCbAddressLow           = 0x2388,
-        _3dCbPos                  = 0x238c,
-        _3dCbData0                = 0x2390,
-        _3dCbData1                = 0x2394,
-        _3dCbData2                = 0x2398,
-        _3dCbData3                = 0x239c,
-        _3dCbData4                = 0x23a0,
-        _3dCbData5                = 0x23a4,
-        _3dCbData6                = 0x23a8,
-        _3dCbData7                = 0x23ac,
-        _3dCbData8                = 0x23b0,
-        _3dCbData9                = 0x23b4,
-        _3dCbData10               = 0x23b8,
-        _3dCbData11               = 0x23bc,
-        _3dCbData12               = 0x23c0,
-        _3dCbData13               = 0x23c4,
-        _3dCbData14               = 0x23c8,
-        _3dCbData15               = 0x23cc,
-        _3dSetShader              = 0x3890
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuTexture.cs b/Ryujinx.Graphics/Gpu/NsGpuTexture.cs
deleted file mode 100644
index aac4220059..0000000000
--- a/Ryujinx.Graphics/Gpu/NsGpuTexture.cs
+++ /dev/null
@@ -1,10 +0,0 @@
-namespace Ryujinx.Graphics.Gpu
-{
-    struct NsGpuTexture
-    {
-        public int Width;
-        public int Height;
-
-        public byte[] Data;
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs b/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs
deleted file mode 100644
index 2993840be7..0000000000
--- a/Ryujinx.Graphics/Gpu/NsGpuTextureFormat.cs
+++ /dev/null
@@ -1,9 +0,0 @@
-namespace Ryujinx.Graphics.Gpu
-{
-    enum NsGpuTextureFormat
-    {
-        BC1 = 0x24,
-        BC2 = 0x25,
-        BC3 = 0x26
-    }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NsGpuEngine.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine.cs
similarity index 61%
rename from Ryujinx.Graphics/Gpu/NsGpuEngine.cs
rename to Ryujinx.Graphics/Gpu/NvGpuEngine.cs
index 118e2b72a5..624915d0d4 100644
--- a/Ryujinx.Graphics/Gpu/NsGpuEngine.cs
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine.cs
@@ -1,13 +1,11 @@
 namespace Ryujinx.Graphics.Gpu
 {
-    enum NsGpuEngine
+    enum NvGpuEngine
     {
-        None    = 0,
         _2d     = 0x902d,
         _3d     = 0xb197,
         Compute = 0xb1c0,
         Kepler  = 0xa140,
-        Dma     = 0xb0b5,
-        GpFifo  = 0xb06f
+        Dma     = 0xb0b5
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
new file mode 100644
index 0000000000..f4486f46cf
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3d.cs
@@ -0,0 +1,469 @@
+using ChocolArm64.Memory;
+using Ryujinx.Graphics.Gal;
+using System;
+using System.Collections.Generic;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    class NvGpuEngine3d : INvGpuEngine
+    {
+        public int[] Registers { get; private set; }
+
+        private NsGpu Gpu;
+
+        private Dictionary<int, NvGpuMethod> Methods;
+
+        private struct ConstBuffer
+        {
+            public bool Enabled;
+            public long Position;
+            public int  Size;
+        }
+
+        private ConstBuffer[] Cbs;
+
+        private bool HasDataToRender;
+
+        public NvGpuEngine3d(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(0x585,  1, 1, VertexEndGl);
+            AddMethod(0x674,  1, 1, ClearBuffers);
+            AddMethod(0x6c3,  1, 1, QueryControl);
+            AddMethod(0x8e4, 16, 1, CbData);
+            AddMethod(0x904,  1, 1, CbBind);
+
+            Cbs = new ConstBuffer[18];
+        }
+
+        public void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (Methods.TryGetValue(PBEntry.Method, out NvGpuMethod Method))
+            {
+                Method(Memory, PBEntry);
+            }
+            else
+            {
+                WriteRegister(PBEntry);
+            }
+        }
+
+        private void VertexEndGl(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            SetFrameBuffer(0);
+
+            long[] Tags = UploadShaders(Memory);
+
+            Gpu.Renderer.BindProgram();
+
+            SetAlphaBlending();
+
+            UploadTextures(Memory, Tags);
+            UploadUniforms(Memory);
+            UploadVertexArrays(Memory);
+
+            HasDataToRender = true;
+        }
+
+        private void ClearBuffers(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (HasDataToRender)
+            {
+                HasDataToRender = false;
+
+                Gpu.Renderer.DrawFrameBuffer(0);
+            }
+
+            int Arg0 = PBEntry.Arguments[0];
+
+            int FbIndex = (Arg0 >> 6) & 0xf;
+
+            int Layer = (Arg0 >> 10) & 0x3ff;
+
+            GalClearBufferFlags Flags = (GalClearBufferFlags)(Arg0 & 0x3f);
+
+            SetFrameBuffer(0);
+
+            Gpu.Renderer.ClearBuffers(Layer, Flags);
+        }
+
+        private void SetFrameBuffer(int FbIndex)
+        {
+            int Width   = ReadRegister(NvGpuEngine3dReg.FrameBufferNWidth  + FbIndex * 0x10);
+            int Height  = ReadRegister(NvGpuEngine3dReg.FrameBufferNHeight + FbIndex * 0x10);
+
+            Gpu.Renderer.SetFb(FbIndex, Width, Height);
+            Gpu.Renderer.BindFrameBuffer(FbIndex);
+        }
+
+        private long[] UploadShaders(AMemory Memory)
+        {
+            long[] Tags = new long[5];
+
+            long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
+
+            for (int Index = 0; Index < 6; Index++)
+            {
+                int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + Index * 0x10);
+                int Offset  = ReadRegister(NvGpuEngine3dReg.ShaderNOffset  + Index * 0x10);
+
+                //Note: Vertex Program (B) is always enabled.
+                bool Enable = (Control & 1) != 0 || Index == 1;
+
+                if (!Enable)
+                {
+                    continue;
+                }
+
+                long Tag = BasePosition + (uint)Offset;
+
+                long Position = Gpu.GetCpuAddr(Tag);
+
+                //TODO: Find a better way to calculate the size.
+                int Size = 0x20000;
+
+                byte[] Code = AMemoryHelper.ReadBytes(Memory, Position, (uint)Size);
+
+                GalShaderType ShaderType = GetTypeFromProgram(Index);
+
+                Tags[(int)ShaderType] = Tag;
+
+                Gpu.Renderer.CreateShader(Tag, ShaderType, Code);
+                Gpu.Renderer.BindShader(Tag);
+            }
+
+            return Tags;
+        }
+
+        private static GalShaderType GetTypeFromProgram(int Program)
+        {
+            switch (Program)
+            {
+                case 0:
+                case 1: return GalShaderType.Vertex;
+                case 2: return GalShaderType.TessControl;
+                case 3: return GalShaderType.TessEvaluation;
+                case 4: return GalShaderType.Geometry;
+                case 5: return GalShaderType.Fragment;
+            }
+
+            throw new ArgumentOutOfRangeException(nameof(Program));
+        }
+
+        private void SetAlphaBlending()
+        {
+            bool BlendEnableMaster = (ReadRegister(NvGpuEngine3dReg.BlendEnableMaster) & 1) != 0;
+
+            Gpu.Renderer.SetBlendEnable(BlendEnableMaster);
+
+            bool BlendSeparateAlpha = (ReadRegister(NvGpuEngine3dReg.BlendSeparateAlpha) & 1) != 0;
+
+            GalBlendEquation EquationRgb = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.BlendEquationRgb);
+
+            GalBlendFactor FuncSrcRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncSrcRgb);
+            GalBlendFactor FuncDstRgb = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncDstRgb);
+
+            if (BlendSeparateAlpha)
+            {
+                GalBlendEquation EquationAlpha = (GalBlendEquation)ReadRegister(NvGpuEngine3dReg.BlendEquationAlpha);
+
+                GalBlendFactor FuncSrcAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncSrcAlpha);
+                GalBlendFactor FuncDstAlpha = (GalBlendFactor)ReadRegister(NvGpuEngine3dReg.BlendFuncDstAlpha);
+
+                Gpu.Renderer.SetBlendSeparate(
+                    EquationRgb,
+                    EquationAlpha,
+                    FuncSrcRgb,
+                    FuncDstRgb,
+                    FuncSrcAlpha,
+                    FuncDstAlpha);
+            }
+            else
+            {
+                Gpu.Renderer.SetBlend(EquationRgb, FuncSrcRgb, FuncDstRgb);
+            }
+        }
+
+        private void UploadTextures(AMemory Memory, long[] Tags)
+        {
+            long BaseShPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
+
+            int TextureCbIndex = ReadRegister(NvGpuEngine3dReg.TextureCbIndex);
+
+            long BasePosition = Cbs[TextureCbIndex].Position;
+
+            long Size = (uint)Cbs[TextureCbIndex].Size;
+
+            int TexIndex = 0;
+
+            for (int Index = 0; Index < Tags.Length; Index++)
+            {
+                foreach (ShaderDeclInfo DeclInfo in Gpu.Renderer.GetTextureUsage(Tags[Index]))
+                {
+                    long Position = BasePosition + Index * Size;
+
+                    UploadTexture(Memory, Position, TexIndex, DeclInfo.Index);
+
+                    Gpu.Renderer.SetUniform1(DeclInfo.Name, TexIndex);
+
+                    TexIndex++;
+                }
+            }
+        }
+
+        private void UploadTexture(AMemory Memory, long BasePosition, int TexIndex, int HndIndex)
+        {
+            long Position = BasePosition + HndIndex * 4;
+
+            int TextureHandle = Memory.ReadInt32(Position);
+
+            int TicIndex = (TextureHandle >>  0) & 0xfffff;
+            int TscIndex = (TextureHandle >> 20) & 0xfff;
+
+            TryGetCpuAddr(NvGpuEngine3dReg.TexHeaderPoolOffset,  out long TicPosition);
+            TryGetCpuAddr(NvGpuEngine3dReg.TexSamplerPoolOffset, out long TscPosition);
+
+            TicPosition += TicIndex * 0x20;
+            TscPosition += TscIndex * 0x20;
+
+            Gpu.Renderer.SetTexture(TexIndex, TextureFactory.MakeTexture(Gpu, Memory, TicPosition));
+            Gpu.Renderer.SetSampler(TexIndex, TextureFactory.MakeSampler(Gpu, Memory, TscPosition));
+        }
+
+        private void UploadUniforms(AMemory Memory)
+        {
+            long BasePosition = MakeInt64From2xInt32(NvGpuEngine3dReg.ShaderAddress);
+
+            for (int Index = 0; Index < 5; Index++)
+            {
+                int Control = ReadRegister(NvGpuEngine3dReg.ShaderNControl + (Index + 1) * 0x10);
+                int Offset  = ReadRegister(NvGpuEngine3dReg.ShaderNOffset  + (Index + 1) * 0x10);
+
+                //Note: Vertex Program (B) is always enabled.
+                bool Enable = (Control & 1) != 0 || Index == 0;
+
+                if (!Enable)
+                {
+                    continue;
+                }
+
+                for (int Cbuf = 0; Cbuf < Cbs.Length; Cbuf++)
+                {
+                    ConstBuffer Cb = Cbs[Cbuf];
+
+                    if (Cb.Enabled)
+                    {
+                        long CbPosition = Cb.Position + Index * Cb.Size;
+
+                        byte[] Data = AMemoryHelper.ReadBytes(Memory, CbPosition, (uint)Cb.Size);
+
+                        Gpu.Renderer.SetConstBuffer(BasePosition + (uint)Offset, Cbuf, Data);
+                    }
+                }
+            }
+        }
+
+        private void UploadVertexArrays(AMemory Memory)
+        {
+            long IndexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.IndexArrayAddress);
+
+            int IndexSize  = ReadRegister(NvGpuEngine3dReg.IndexArrayFormat);
+            int IndexFirst = ReadRegister(NvGpuEngine3dReg.IndexBatchFirst);
+            int IndexCount = ReadRegister(NvGpuEngine3dReg.IndexBatchCount);
+
+            GalIndexFormat IndexFormat = (GalIndexFormat)IndexSize;
+
+            IndexSize = 1 << IndexSize;
+
+            if (IndexSize > 4)
+            {
+                throw new InvalidOperationException();
+            }
+
+            if (IndexSize != 0)
+            {
+                IndexPosition = Gpu.GetCpuAddr(IndexPosition);
+
+                int BufferSize = IndexCount * IndexSize;
+
+                byte[] Data = AMemoryHelper.ReadBytes(Memory, IndexPosition, BufferSize);
+
+                Gpu.Renderer.SetIndexArray(Data, IndexFormat);
+            }
+
+            List<GalVertexAttrib>[] Attribs = new List<GalVertexAttrib>[32];
+
+            for (int Attr = 0; Attr < 16; Attr++)
+            {
+                int Packed = ReadRegister(NvGpuEngine3dReg.VertexAttribNFormat + Attr);
+
+                int ArrayIndex = Packed & 0x1f;
+
+                if (Attribs[ArrayIndex] == null)
+                {
+                    Attribs[ArrayIndex] = new List<GalVertexAttrib>();
+                }
+
+                Attribs[ArrayIndex].Add(new GalVertexAttrib(
+                                         ((Packed >>  6) & 0x1) != 0,
+                                          (Packed >>  7) & 0x3fff,
+                    (GalVertexAttribSize)((Packed >> 21) & 0x3f),
+                    (GalVertexAttribType)((Packed >> 27) & 0x7),
+                                         ((Packed >> 31) & 0x1) != 0));
+            }
+
+            for (int Index = 0; Index < 32; Index++)
+            {
+                int Control = ReadRegister(NvGpuEngine3dReg.VertexArrayNControl + Index * 4);
+
+                bool Enable = (Control & 0x1000) != 0;
+
+                if (!Enable)
+                {
+                    continue;
+                }
+
+                long VertexPosition = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNAddress + Index * 4);
+                long VertexEndPos   = MakeInt64From2xInt32(NvGpuEngine3dReg.VertexArrayNEndAddr + Index * 4);
+
+                long Size = (VertexEndPos - VertexPosition) + 1;
+
+                int Stride = Control & 0xfff;
+
+                VertexPosition = Gpu.GetCpuAddr(VertexPosition);
+
+                byte[] Data = AMemoryHelper.ReadBytes(Memory, VertexPosition, Size);
+
+                GalVertexAttrib[] AttribArray = Attribs[Index]?.ToArray() ?? new GalVertexAttrib[0];
+
+                Gpu.Renderer.SetVertexArray(Index, Stride, Data, AttribArray);
+
+                int PrimCtrl = ReadRegister(NvGpuEngine3dReg.VertexBeginGl);
+
+                GalPrimitiveType PrimType = (GalPrimitiveType)(PrimCtrl & 0xffff);
+
+                if (IndexCount != 0)
+                {
+                    Gpu.Renderer.DrawElements(Index, IndexFirst, PrimType);
+                }
+                else
+                {
+                    Gpu.Renderer.DrawArrays(Index, PrimType);
+                }
+            }
+        }
+
+        private void QueryControl(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (TryGetCpuAddr(NvGpuEngine3dReg.QueryAddress, out long Position))
+            {
+                int Seq  = Registers[(int)NvGpuEngine3dReg.QuerySequence];
+                int Ctrl = Registers[(int)NvGpuEngine3dReg.QueryControl];
+
+                int Mode = Ctrl & 3;
+
+                if (Mode == 0)
+                {
+                    //Write.
+                    Memory.WriteInt32(Position, Seq);
+                }
+            }
+
+            WriteRegister(PBEntry);
+        }
+
+        private void CbData(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position))
+            {
+                int Offset = ReadRegister(NvGpuEngine3dReg.ConstBufferNOffset);
+
+                foreach (int Arg in PBEntry.Arguments)
+                {
+                    Memory.WriteInt32(Position + Offset, Arg);
+
+                    Offset += 4;
+                }
+
+                WriteRegister(NvGpuEngine3dReg.ConstBufferNOffset, Offset);
+            }
+        }
+
+        private void CbBind(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            int Index = PBEntry.Arguments[0];
+
+            bool Enabled = (Index & 1) != 0;
+
+            Index = (Index >> 4) & 0x1f;
+
+            if (TryGetCpuAddr(NvGpuEngine3dReg.ConstBufferNAddress, out long Position))
+            {
+                Cbs[Index].Position = Position;
+                Cbs[Index].Enabled  = Enabled;
+
+                Cbs[Index].Size = ReadRegister(NvGpuEngine3dReg.ConstBufferNSize);
+            }
+        }
+
+        private int ReadCb(AMemory Memory, int Cbuf, int Offset)
+        {
+            long Position = Cbs[Cbuf].Position;
+
+            int Value = Memory.ReadInt32(Position + Offset);
+
+            return Value;
+        }
+
+        private bool TryGetCpuAddr(NvGpuEngine3dReg Reg, out long Position)
+        {
+            Position = MakeInt64From2xInt32(Reg);
+
+            Position = Gpu.GetCpuAddr(Position);
+
+            return Position != -1;
+        }
+
+        private long MakeInt64From2xInt32(NvGpuEngine3dReg 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(NvGpuEngine3dReg Reg)
+        {
+            return Registers[(int)Reg];
+        }
+
+        private void WriteRegister(NvGpuEngine3dReg Reg, int Value)
+        {
+            Registers[(int)Reg] = Value;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
new file mode 100644
index 0000000000..4bba9abe0d
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuEngine3dReg.cs
@@ -0,0 +1,44 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    enum NvGpuEngine3dReg
+    {
+        FrameBufferNAddress  = 0x200,
+        FrameBufferNWidth    = 0x202,
+        FrameBufferNHeight   = 0x203,
+        FrameBufferNFormat   = 0x204,
+        VertexAttribNFormat  = 0x458,
+        BlendSeparateAlpha   = 0x4cf,
+        BlendEquationRgb     = 0x4d0,
+        BlendFuncSrcRgb      = 0x4d1,
+        BlendFuncDstRgb      = 0x4d2,
+        BlendEquationAlpha   = 0x4d3,
+        BlendFuncSrcAlpha    = 0x4d4,
+        BlendFuncDstAlpha    = 0x4d6,
+        BlendEnableMaster    = 0x4d7,
+        VertexArrayElemBase  = 0x50d,
+        TexHeaderPoolOffset  = 0x55d,
+        TexSamplerPoolOffset = 0x557,
+        ShaderAddress        = 0x582,
+        VertexBeginGl        = 0x586,
+        IndexArrayAddress    = 0x5f2,
+        IndexArrayEndAddr    = 0x5f4,
+        IndexArrayFormat     = 0x5f6,
+        IndexBatchFirst      = 0x5f7,
+        IndexBatchCount      = 0x5f8,
+        QueryAddress         = 0x6c0,
+        QuerySequence        = 0x6c2,
+        QueryControl         = 0x6c3,
+        VertexArrayNControl  = 0x700,
+        VertexArrayNAddress  = 0x701,
+        VertexArrayNDivisor  = 0x703,
+        VertexArrayNEndAddr  = 0x7c0,
+        ShaderNControl       = 0x800,
+        ShaderNOffset        = 0x801,
+        ShaderNMaxGprs       = 0x803,
+        ShaderNType          = 0x804,
+        ConstBufferNSize     = 0x8e0,
+        ConstBufferNAddress  = 0x8e1,
+        ConstBufferNOffset   = 0x8e3,
+        TextureCbIndex       = 0x982
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifo.cs b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs
new file mode 100644
index 0000000000..df765895c4
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuFifo.cs
@@ -0,0 +1,171 @@
+using ChocolArm64.Memory;
+using System.Collections.Concurrent;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    public class NvGpuFifo
+    {
+        private const int MacrosCount    = 0x80;
+        private const int MacroIndexMask = MacrosCount - 1;
+
+        private NsGpu Gpu;
+
+        private ConcurrentQueue<(AMemory, NsGpuPBEntry)> BufferQueue;
+
+        private NvGpuEngine[] SubChannels;
+
+        private struct CachedMacro
+        {
+            public long Position { get; private set; }
+
+            private MacroInterpreter Interpreter;
+
+            public CachedMacro(NvGpuFifo PFifo, INvGpuEngine Engine, long Position)
+            {
+                this.Position = Position;
+
+                Interpreter = new MacroInterpreter(PFifo, Engine);
+            }
+
+            public void PushParam(int Param)
+            {
+                Interpreter?.Fifo.Enqueue(Param);
+            }
+
+            public void Execute(AMemory Memory, int Param)
+            {
+                Interpreter?.Execute(Memory, Position, Param);
+            }
+        }
+
+        private long CurrMacroPosition;
+        private int  CurrMacroBindIndex;
+
+        private CachedMacro[] Macros;
+
+        public NvGpuFifo(NsGpu Gpu)
+        {
+            this.Gpu = Gpu;
+
+            BufferQueue = new ConcurrentQueue<(AMemory, NsGpuPBEntry)>();
+
+            SubChannels = new NvGpuEngine[8];
+
+            Macros = new CachedMacro[MacrosCount];
+        }
+
+        public void PushBuffer(AMemory Memory, NsGpuPBEntry[] Buffer)
+        {
+            foreach (NsGpuPBEntry PBEntry in Buffer)
+            {
+                BufferQueue.Enqueue((Memory, PBEntry));
+            }
+        }
+
+        public void DispatchCalls()
+        {
+            while (Step());
+        }
+
+        public bool Step()
+        {
+            if (BufferQueue.TryDequeue(out (AMemory Memory, NsGpuPBEntry PBEntry) Tuple))
+            {
+                CallMethod(Tuple.Memory, Tuple.PBEntry);
+
+                return true;
+            }
+
+            return false;
+        }
+
+        private void CallMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (PBEntry.Method < 0x80)
+            {
+                switch ((NvGpuFifoMeth)PBEntry.Method)
+                {
+                    case NvGpuFifoMeth.BindChannel:
+                    {
+                        NvGpuEngine Engine = (NvGpuEngine)PBEntry.Arguments[0];
+
+                        SubChannels[PBEntry.SubChannel] = Engine;
+
+                        break;
+                    }
+
+                    case NvGpuFifoMeth.SetMacroUploadAddress:
+                    {
+                        CurrMacroPosition = (long)((ulong)PBEntry.Arguments[0] << 2);
+
+                        break;
+                    }
+
+                    case NvGpuFifoMeth.SendMacroCodeData:
+                    {
+                        long Position = Gpu.GetCpuAddr(CurrMacroPosition);
+
+                        foreach (int Arg in PBEntry.Arguments)
+                        {
+                            Memory.WriteInt32(Position, Arg);
+
+                            CurrMacroPosition += 4;
+
+                            Position += 4;
+                        }
+                        break;
+                    }
+
+                    case NvGpuFifoMeth.SetMacroBindingIndex:
+                    {
+                        CurrMacroBindIndex = PBEntry.Arguments[0];
+
+                        break;
+                    }
+
+                    case NvGpuFifoMeth.BindMacro:
+                    {
+                        long Position = (long)((ulong)PBEntry.Arguments[0] << 2);
+
+                        Position = Gpu.GetCpuAddr(Position);
+
+                        Macros[CurrMacroBindIndex] = new CachedMacro(this, Gpu.Engine3d, Position);
+
+                        break;
+                    }
+                }
+            }
+            else
+            {
+                switch (SubChannels[PBEntry.SubChannel])
+                {
+                    case NvGpuEngine._3d: Call3dMethod(Memory, PBEntry); break;
+                }
+            }
+        }
+
+        private void Call3dMethod(AMemory Memory, NsGpuPBEntry PBEntry)
+        {
+            if (PBEntry.Method < 0xe00)
+            {
+                Gpu.Engine3d.CallMethod(Memory, PBEntry);
+            }
+            else
+            {
+                int MacroIndex = (PBEntry.Method >> 1) & MacroIndexMask;
+
+                if ((PBEntry.Method & 1) != 0)
+                {
+                    foreach (int Arg in PBEntry.Arguments)
+                    {
+                        Macros[MacroIndex].PushParam(Arg);
+                    }
+                }
+                else
+                {
+                    Macros[MacroIndex].Execute(Memory, PBEntry.Arguments[0]);
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs b/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs
new file mode 100644
index 0000000000..4287e25009
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuFifoMeth.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    enum NvGpuFifoMeth
+    {
+        BindChannel           = 0,
+        SetMacroUploadAddress = 0x45,
+        SendMacroCodeData     = 0x46,
+        SetMacroBindingIndex  = 0x47,
+        BindMacro             = 0x48
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuMethod.cs b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs
new file mode 100644
index 0000000000..2923ddff0c
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuMethod.cs
@@ -0,0 +1,6 @@
+using ChocolArm64.Memory;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    delegate void NvGpuMethod(AMemory Memory, NsGpuPBEntry PBEntry);
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs b/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs
new file mode 100644
index 0000000000..8cbb3288eb
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/NvGpuPushBuffer.cs
@@ -0,0 +1,101 @@
+using System.Collections.Generic;
+using System.IO;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    public static class NvGpuPushBuffer
+    {
+        private enum SubmissionMode
+        {
+            Incrementing    = 1,
+            NonIncrementing = 3,
+            Immediate       = 4,
+            IncrementOnce   = 5
+        }
+
+        public static NsGpuPBEntry[] Decode(byte[] Data)
+        {
+            using (MemoryStream MS = new MemoryStream(Data))
+            {
+                BinaryReader Reader = new BinaryReader(MS);
+
+                List<NsGpuPBEntry> PushBuffer = new List<NsGpuPBEntry>();
+
+                bool CanRead() => MS.Position + 4 <= MS.Length;
+
+                while (CanRead())
+                {
+                    int Packed = Reader.ReadInt32();
+
+                    int Meth = (Packed >> 0)  & 0x1fff;
+                    int SubC = (Packed >> 13) & 7;
+                    int Args = (Packed >> 16) & 0x1fff;
+                    int Mode = (Packed >> 29) & 7;
+
+                    switch ((SubmissionMode)Mode)
+                    {
+                        case SubmissionMode.Incrementing:
+                        {
+                            for (int Index = 0; Index < Args && CanRead(); Index++, Meth++)
+                            {
+                                PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
+                            }
+
+                            break;
+                        }
+
+                        case SubmissionMode.NonIncrementing:
+                        {
+                            int[] Arguments = new int[Args];
+
+                            for (int Index = 0; Index < Arguments.Length; Index++)
+                            {
+                                if (!CanRead())
+                                {
+                                    break;
+                                }
+
+                                Arguments[Index] = Reader.ReadInt32();
+                            }
+
+                            PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Arguments));
+
+                            break;
+                        }
+
+                        case SubmissionMode.Immediate:
+                        {
+                            PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Args));
+
+                            break;
+                        }
+
+                        case SubmissionMode.IncrementOnce:
+                        {
+                            if (CanRead())
+                            {
+                                PushBuffer.Add(new NsGpuPBEntry(Meth, SubC, Reader.ReadInt32()));
+                            }
+    
+                            if (CanRead() && Args > 1)
+                            {
+                                int[] Arguments = new int[Args - 1];
+
+                                for (int Index = 0; Index < Arguments.Length && CanRead(); Index++)
+                                {
+                                    Arguments[Index] = Reader.ReadInt32();
+                                }
+
+                                PushBuffer.Add(new NsGpuPBEntry(Meth + 1, SubC, Arguments));
+                            }
+
+                            break;
+                        }
+                    }
+                }
+
+                return PushBuffer.ToArray();
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/Texture.cs b/Ryujinx.Graphics/Gpu/Texture.cs
new file mode 100644
index 0000000000..c8d4e5274d
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/Texture.cs
@@ -0,0 +1,34 @@
+using Ryujinx.Graphics.Gal;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    struct Texture
+    {
+        public long Position { get; private set; }
+
+        public int Width  { get; private set; }
+        public int Height { get; private set; }
+
+        public int BlockHeight { get; private set; }
+
+        public TextureSwizzle Swizzle { get; private set; }
+
+        public GalTextureFormat Format { get; private set; }
+
+        public Texture(
+            long             Position,
+            int              Width,
+            int              Height,
+            int              BlockHeight,
+            TextureSwizzle   Swizzle,
+            GalTextureFormat Format)
+        {
+            this.Position    = Position;
+            this.Width       = Width;
+            this.Height      = Height;
+            this.BlockHeight = BlockHeight;
+            this.Swizzle     = Swizzle;
+            this.Format      = Format;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/TextureFactory.cs b/Ryujinx.Graphics/Gpu/TextureFactory.cs
new file mode 100644
index 0000000000..0a0497f3fd
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/TextureFactory.cs
@@ -0,0 +1,83 @@
+using ChocolArm64.Memory;
+using Ryujinx.Graphics.Gal;
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    static class TextureFactory
+    {
+        public static GalTexture MakeTexture(NsGpu Gpu, AMemory Memory, long TicPosition)
+        {
+            int[] Tic = ReadWords(Memory, TicPosition, 8);
+
+            GalTextureFormat Format = (GalTextureFormat)(Tic[0] & 0x7f);
+
+            long TextureAddress = (uint)Tic[1];
+
+            TextureAddress |= (long)((ushort)Tic[2]) << 32;
+
+            TextureAddress = Gpu.GetCpuAddr(TextureAddress);
+
+            TextureSwizzle Swizzle = (TextureSwizzle)((Tic[2] >> 21) & 7);
+
+            int BlockHeightLog2 = (Tic[3] >> 3) & 7;
+
+            int BlockHeight = 1 << BlockHeightLog2;
+
+            int Width  = (Tic[4] & 0xffff) + 1;
+            int Height = (Tic[5] & 0xffff) + 1;
+
+            Texture Texture = new Texture(
+                TextureAddress,
+                Width,
+                Height,
+                BlockHeight,
+                Swizzle,
+                Format);
+
+            byte[] Data = TextureReader.Read(Memory, Texture);
+
+            return new GalTexture(Data, Width, Height, Format);
+        }
+
+        public static GalTextureSampler MakeSampler(NsGpu Gpu, AMemory Memory, long TscPosition)
+        {
+            int[] Tsc = ReadWords(Memory, TscPosition, 8);
+
+            GalTextureWrap AddressU = (GalTextureWrap)((Tsc[0] >> 0) & 7);
+            GalTextureWrap AddressV = (GalTextureWrap)((Tsc[0] >> 3) & 7);
+            GalTextureWrap AddressP = (GalTextureWrap)((Tsc[0] >> 6) & 7);
+
+            GalTextureFilter    MagFilter = (GalTextureFilter)   ((Tsc[1] >> 0) & 3);
+            GalTextureFilter    MinFilter = (GalTextureFilter)   ((Tsc[1] >> 4) & 3);
+            GalTextureMipFilter MipFilter = (GalTextureMipFilter)((Tsc[1] >> 6) & 3);
+
+            GalColorF BorderColor = new GalColorF(
+                BitConverter.Int32BitsToSingle(Tsc[4]),
+                BitConverter.Int32BitsToSingle(Tsc[5]),
+                BitConverter.Int32BitsToSingle(Tsc[6]),
+                BitConverter.Int32BitsToSingle(Tsc[7]));
+
+            return new GalTextureSampler(
+                AddressU,
+                AddressV,
+                AddressP,
+                MinFilter,
+                MagFilter,
+                MipFilter,
+                BorderColor);
+        }
+
+        private static int[] ReadWords(AMemory Memory, long Position, int Count)
+        {
+            int[] Words = new int[Count];
+
+            for (int Index = 0; Index < Count; Index++, Position += 4)
+            {
+                Words[Index] = Memory.ReadInt32(Position);
+            }
+
+            return Words;
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/TextureReader.cs b/Ryujinx.Graphics/Gpu/TextureReader.cs
new file mode 100644
index 0000000000..ce66e991d6
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/TextureReader.cs
@@ -0,0 +1,127 @@
+using ChocolArm64.Memory;
+using Ryujinx.Graphics.Gal;
+using System;
+
+namespace Ryujinx.Graphics.Gpu
+{
+    static class TextureReader
+    {
+        public static byte[] Read(AMemory Memory, Texture Texture)
+        {
+            switch (Texture.Format)
+            {
+                case GalTextureFormat.A8B8G8R8: return Read4Bpp    (Memory, Texture);
+                case GalTextureFormat.BC1:      return Read8Bpt4x4 (Memory, Texture);
+                case GalTextureFormat.BC2:      return Read16Bpt4x4(Memory, Texture);
+                case GalTextureFormat.BC3:      return Read16Bpt4x4(Memory, Texture);
+            }
+
+            throw new NotImplementedException(Texture.Format.ToString());
+        }
+
+        private unsafe static byte[] Read4Bpp(AMemory Memory, Texture Texture)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            byte[] Output = new byte[Width * Height * 4];
+
+            ISwizzle Swizzle = GetSwizzle(Texture.Swizzle, Width, 4, Texture.BlockHeight);
+
+            fixed (byte* BuffPtr = Output)
+            {
+                long OutOffs = 0;
+
+                for (int Y = 0; Y < Height; Y++)
+                for (int X = 0; X < Width;  X++)
+                {
+                    long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
+
+                    int Pixel = Memory.ReadInt32Unchecked(Texture.Position + Offset);
+
+                    *(int*)(BuffPtr + OutOffs) = Pixel;
+
+                    OutOffs += 4;
+                }
+            }
+
+            return Output;
+        }
+
+        private unsafe static byte[] Read8Bpt4x4(AMemory Memory, Texture Texture)
+        {
+            int Width  = (Texture.Width  + 3) / 4;
+            int Height = (Texture.Height + 3) / 4;
+
+            byte[] Output = new byte[Width * Height * 8];
+
+            ISwizzle Swizzle = GetSwizzle(Texture.Swizzle, Width, 8, Texture.BlockHeight);
+
+            fixed (byte* BuffPtr = Output)
+            {
+                long OutOffs = 0;
+
+                for (int Y = 0; Y < Height; Y++)
+                for (int X = 0; X < Width;  X++)
+                {
+                    long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
+
+                    long Tile = Memory.ReadInt64Unchecked(Texture.Position + Offset);
+
+                    *(long*)(BuffPtr + OutOffs) = Tile;
+
+                    OutOffs += 8;
+                }
+            }
+
+            return Output;
+        }
+
+        private unsafe static byte[] Read16Bpt4x4(AMemory Memory, Texture Texture)
+        {
+            int Width  = (Texture.Width  + 3) / 4;
+            int Height = (Texture.Height + 3) / 4;
+
+            byte[] Output = new byte[Width * Height * 16];
+
+            ISwizzle Swizzle = GetSwizzle(Texture.Swizzle, Width, 16, Texture.BlockHeight);
+
+            fixed (byte* BuffPtr = Output)
+            {
+                long OutOffs = 0;
+
+                for (int Y = 0; Y < Height; Y++)
+                for (int X = 0; X < Width;  X++)
+                {
+                    long Offset = (uint)Swizzle.GetSwizzleOffset(X, Y);
+
+                    long Tile0 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 0);
+                    long Tile1 = Memory.ReadInt64Unchecked(Texture.Position + Offset + 8);
+
+                    *(long*)(BuffPtr + OutOffs + 0) = Tile0;
+                    *(long*)(BuffPtr + OutOffs + 8) = Tile1;
+
+                    OutOffs += 16;
+                }
+            }
+
+            return Output;
+        }
+
+        private static ISwizzle GetSwizzle(TextureSwizzle Swizzle, int Width, int Bpp, int BlockHeight)
+        {
+            switch (Swizzle)
+            {
+                case TextureSwizzle.Pitch:
+                case TextureSwizzle.PitchColorKey:
+                     return new LinearSwizzle(Width, Bpp);
+
+                case TextureSwizzle.BlockLinear:
+                case TextureSwizzle.BlockLinearColorKey:
+                    return new BlockLinearSwizzle(Width, Bpp, BlockHeight);
+            }
+
+            throw new NotImplementedException(Swizzle.ToString());
+        }
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gpu/TextureSwizzle.cs b/Ryujinx.Graphics/Gpu/TextureSwizzle.cs
new file mode 100644
index 0000000000..2142e2c20a
--- /dev/null
+++ b/Ryujinx.Graphics/Gpu/TextureSwizzle.cs
@@ -0,0 +1,11 @@
+namespace Ryujinx.Graphics.Gpu
+{
+    enum TextureSwizzle
+    {
+        _1dBuffer           = 0,
+        PitchColorKey       = 1,
+        Pitch               = 2,
+        BlockLinear         = 3,
+        BlockLinearColorKey = 4
+    }
+}
\ No newline at end of file
diff --git a/Ryujinx/Ui/GLScreen.cs b/Ryujinx/Ui/GLScreen.cs
index 3bcef92183..d6a33edbfd 100644
--- a/Ryujinx/Ui/GLScreen.cs
+++ b/Ryujinx/Ui/GLScreen.cs
@@ -173,8 +173,6 @@ namespace Ryujinx
             Title = $"Ryujinx Screen - (Vsync: {VSync} - FPS: {Ns.Statistics.SystemFrameRate:0} - Guest FPS: " +
                 $"{Ns.Statistics.GameFrameRate:0})";
 
-            GL.Clear(ClearBufferMask.ColorBufferBit | ClearBufferMask.DepthBufferBit);
-
             Renderer.RunActions();
             Renderer.Render();