From f43dd080641b3c2508e35bc7651fb0adcb282a2e Mon Sep 17 00:00:00 2001
From: gdkchan <gab.dark.100@gmail.com>
Date: Tue, 29 May 2018 20:37:10 -0300
Subject: [PATCH] Added support for more shader instructions and texture
 formats, fix swapped channels in RGB565 and RGBA5551? texture formats, allow
 zero values on blending registers, initial work to build CFG on the shader
 decoder, update the BRA instruction to work with it (WIP)

---
 ChocolArm64/Decoder/ABlock.cs                 |   2 +-
 ChocolArm64/Decoder/ADecoder.cs               |   2 +-
 Ryujinx.Core/Gpu/TextureReader.cs             | 197 +++++++++++++-
 Ryujinx.Graphics/Gal/GalTextureFormat.cs      |  21 +-
 .../Gal/OpenGL/OGLEnumConverter.cs            |  20 +-
 Ryujinx.Graphics/Gal/Shader/GlslDecl.cs       |  13 +-
 Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs | 253 +++++++++++++-----
 .../Gal/Shader/ShaderDecodeAlu.cs             | 164 +++++++++++-
 .../Gal/Shader/ShaderDecodeFlow.cs            |   6 +-
 Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs  | 172 ++++++++++--
 Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs  |  40 +--
 Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs   |   2 +
 Ryujinx.Graphics/Gal/Shader/ShaderIrLabel.cs  |   4 -
 .../Gal/Shader/ShaderOpCodeTable.cs           |   7 +
 14 files changed, 749 insertions(+), 154 deletions(-)
 delete mode 100644 Ryujinx.Graphics/Gal/Shader/ShaderIrLabel.cs

diff --git a/ChocolArm64/Decoder/ABlock.cs b/ChocolArm64/Decoder/ABlock.cs
index 32974c1abf..7a0fc60773 100644
--- a/ChocolArm64/Decoder/ABlock.cs
+++ b/ChocolArm64/Decoder/ABlock.cs
@@ -5,7 +5,7 @@ namespace ChocolArm64.Decoder
     class ABlock
     {
         public long Position    { get; set; }
-        public long EndPosition { get; set; }       
+        public long EndPosition { get; set; }
 
         public ABlock Next   { get; set; }
         public ABlock Branch { get; set; }
diff --git a/ChocolArm64/Decoder/ADecoder.cs b/ChocolArm64/Decoder/ADecoder.cs
index 32a68ad34d..b154a54cd2 100644
--- a/ChocolArm64/Decoder/ADecoder.cs
+++ b/ChocolArm64/Decoder/ADecoder.cs
@@ -94,7 +94,7 @@ namespace ChocolArm64.Decoder
                     }
                 }
 
-                //If we have on the tree two blocks with the same end position,
+                //If we have on the graph two blocks with the same end position,
                 //then we need to split the bigger block and have two small blocks,
                 //the end position of the bigger "Current" block should then be == to
                 //the position of the "Smaller" block.
diff --git a/Ryujinx.Core/Gpu/TextureReader.cs b/Ryujinx.Core/Gpu/TextureReader.cs
index f3e41046ae..0c1c83d5d3 100644
--- a/Ryujinx.Core/Gpu/TextureReader.cs
+++ b/Ryujinx.Core/Gpu/TextureReader.cs
@@ -10,19 +10,132 @@ namespace Ryujinx.Core.Gpu
         {
             switch (Texture.Format)
             {
-                case GalTextureFormat.A8B8G8R8: return Read4Bpp    (Memory, Texture);
-                case GalTextureFormat.A1B5G5R5: return Read2Bpp    (Memory, Texture);
-                case GalTextureFormat.B5G6R5:   return Read2Bpp    (Memory, Texture);
-                case GalTextureFormat.BC1:      return Read8Bpt4x4 (Memory, Texture);
-                case GalTextureFormat.BC2:      return Read16Bpt4x4(Memory, Texture);
-                case GalTextureFormat.BC3:      return Read16Bpt4x4(Memory, Texture);
-                case GalTextureFormat.BC4:      return Read8Bpt4x4 (Memory, Texture);
-                case GalTextureFormat.BC5:      return Read16Bpt4x4(Memory, Texture);
+                case GalTextureFormat.R32G32B32A32: return Read16Bpp   (Memory, Texture);
+                case GalTextureFormat.R16G16B16A16: return Read8Bpp    (Memory, Texture);
+                case GalTextureFormat.A8B8G8R8:     return Read4Bpp    (Memory, Texture);
+                case GalTextureFormat.R32:          return Read4Bpp    (Memory, Texture);
+                case GalTextureFormat.A1B5G5R5:     return Read5551    (Memory, Texture);
+                case GalTextureFormat.B5G6R5:       return Read565     (Memory, Texture);
+                case GalTextureFormat.G8R8:         return Read2Bpp    (Memory, Texture);
+                case GalTextureFormat.R8:           return Read1Bpp    (Memory, Texture);
+                case GalTextureFormat.BC1:          return Read8Bpt4x4 (Memory, Texture);
+                case GalTextureFormat.BC2:          return Read16Bpt4x4(Memory, Texture);
+                case GalTextureFormat.BC3:          return Read16Bpt4x4(Memory, Texture);
+                case GalTextureFormat.BC4:          return Read8Bpt4x4 (Memory, Texture);
+                case GalTextureFormat.BC5:          return Read16Bpt4x4(Memory, Texture);
             }
 
             throw new NotImplementedException(Texture.Format.ToString());
         }
 
+        private unsafe static byte[] Read1Bpp(IAMemory Memory, Texture Texture)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            byte[] Output = new byte[Width * Height];
+
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 1);
+
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
+            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);
+
+                    byte Pixel = CpuMem.ReadByteUnchecked(Position + Offset);
+
+                    *(BuffPtr + OutOffs) = Pixel;
+
+                    OutOffs++;
+                }
+            }
+
+            return Output;
+        }
+
+        private unsafe static byte[] Read5551(IAMemory Memory, Texture Texture)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            byte[] Output = new byte[Width * Height * 2];
+
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
+
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
+            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);
+
+                    uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset);
+
+                    Pixel = (Pixel & 0x001f) << 11 |
+                            (Pixel & 0x03e0) << 1  |
+                            (Pixel & 0x7c00) >> 9  |
+                            (Pixel & 0x8000) >> 15;
+
+                    *(short*)(BuffPtr + OutOffs) = (short)Pixel;
+
+                    OutOffs += 2;
+                }
+            }
+
+            return Output;
+        }
+
+        private unsafe static byte[] Read565(IAMemory Memory, Texture Texture)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            byte[] Output = new byte[Width * Height * 2];
+
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 2);
+
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
+            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);
+
+                    uint Pixel = (uint)CpuMem.ReadInt16Unchecked(Position + Offset);
+
+                    Pixel = (Pixel & 0x001f) << 11 |
+                            (Pixel & 0x07e0)       |
+                            (Pixel & 0xf800) >> 11;
+
+                    *(short*)(BuffPtr + OutOffs) = (short)Pixel;
+
+                    OutOffs += 2;
+                }
+            }
+
+            return Output;
+        }
+
         private unsafe static byte[] Read2Bpp(IAMemory Memory, Texture Texture)
         {
             int Width  = Texture.Width;
@@ -89,6 +202,74 @@ namespace Ryujinx.Core.Gpu
             return Output;
         }
 
+        private unsafe static byte[] Read8Bpp(IAMemory Memory, Texture Texture)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            byte[] Output = new byte[Width * Height * 8];
+
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 8);
+
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
+            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 Pixel = CpuMem.ReadInt64Unchecked(Position + Offset);
+
+                    *(long*)(BuffPtr + OutOffs) = Pixel;
+
+                    OutOffs += 8;
+                }
+            }
+
+            return Output;
+        }
+
+        private unsafe static byte[] Read16Bpp(IAMemory Memory, Texture Texture)
+        {
+            int Width  = Texture.Width;
+            int Height = Texture.Height;
+
+            byte[] Output = new byte[Width * Height * 16];
+
+            ISwizzle Swizzle = TextureHelper.GetSwizzle(Texture, Width, 16);
+
+            (AMemory CpuMem, long Position) = TextureHelper.GetMemoryAndPosition(
+                Memory,
+                Texture.Position);
+
+            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 PxLow  = CpuMem.ReadInt64Unchecked(Position + Offset + 0);
+                    long PxHigh = CpuMem.ReadInt64Unchecked(Position + Offset + 8);
+
+                    *(long*)(BuffPtr + OutOffs + 0) = PxLow;
+                    *(long*)(BuffPtr + OutOffs + 8) = PxHigh;
+
+                    OutOffs += 16;
+                }
+            }
+
+            return Output;
+        }
+
         private unsafe static byte[] Read8Bpt4x4(IAMemory Memory, Texture Texture)
         {
             int Width  = (Texture.Width  + 3) / 4;
diff --git a/Ryujinx.Graphics/Gal/GalTextureFormat.cs b/Ryujinx.Graphics/Gal/GalTextureFormat.cs
index 37291e18bf..b3d8b03d9e 100644
--- a/Ryujinx.Graphics/Gal/GalTextureFormat.cs
+++ b/Ryujinx.Graphics/Gal/GalTextureFormat.cs
@@ -2,13 +2,18 @@ namespace Ryujinx.Graphics.Gal
 {
     public enum GalTextureFormat
     {
-        A8B8G8R8 = 0x8,
-        A1B5G5R5 = 0x14,
-        B5G6R5   = 0x15,
-        BC1      = 0x24,
-        BC2      = 0x25,
-        BC3      = 0x26,
-        BC4      = 0x27,
-        BC5      = 0x28
+        R32G32B32A32 = 0x1,
+        R16G16B16A16 = 0x3,
+        A8B8G8R8     = 0x8,
+        R32          = 0xf,
+        A1B5G5R5     = 0x14,
+        B5G6R5       = 0x15,
+        G8R8         = 0x18,
+        R8           = 0x1d,
+        BC1          = 0x24,
+        BC2          = 0x25,
+        BC3          = 0x26,
+        BC4          = 0x27,
+        BC5          = 0x28
     }
 }
diff --git a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
index d266a87a8e..ee697097f0 100644
--- a/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
+++ b/Ryujinx.Graphics/Gal/OpenGL/OGLEnumConverter.cs
@@ -59,9 +59,14 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             switch (Format)
             {
-                case GalTextureFormat.A8B8G8R8: return (PixelFormat.Rgba, PixelType.UnsignedByte);
-                case GalTextureFormat.A1B5G5R5: return (PixelFormat.Rgba, PixelType.UnsignedShort5551);
-                case GalTextureFormat.B5G6R5:   return (PixelFormat.Rgb,  PixelType.UnsignedShort565);
+                case GalTextureFormat.R32G32B32A32: return (PixelFormat.Rgba, PixelType.Float);
+                case GalTextureFormat.R16G16B16A16: return (PixelFormat.Rgba, PixelType.HalfFloat);
+                case GalTextureFormat.A8B8G8R8:     return (PixelFormat.Rgba, PixelType.UnsignedByte);
+                case GalTextureFormat.R32:          return (PixelFormat.Red,  PixelType.Float);
+                case GalTextureFormat.A1B5G5R5:     return (PixelFormat.Rgba, PixelType.UnsignedShort5551);
+                case GalTextureFormat.B5G6R5:       return (PixelFormat.Rgb,  PixelType.UnsignedShort565);
+                case GalTextureFormat.G8R8:         return (PixelFormat.Rg,   PixelType.UnsignedByte);
+                case GalTextureFormat.R8:           return (PixelFormat.Red,  PixelType.UnsignedByte);
             }
 
             throw new NotImplementedException(Format.ToString());
@@ -145,20 +150,20 @@ namespace Ryujinx.Graphics.Gal.OpenGL
         {
             switch (BlendEquation)
             {
+                default:
                 case GalBlendEquation.FuncAdd:             return BlendEquationMode.FuncAdd;
                 case GalBlendEquation.FuncSubtract:        return BlendEquationMode.FuncSubtract;
                 case GalBlendEquation.FuncReverseSubtract: return BlendEquationMode.FuncReverseSubtract;
                 case GalBlendEquation.Min:                 return BlendEquationMode.Min;
                 case GalBlendEquation.Max:                 return BlendEquationMode.Max;
             }
-
-            throw new ArgumentException(nameof(BlendEquation));
         }
 
         public static BlendingFactorSrc GetBlendFactorSrc(GalBlendFactor BlendFactor)
         {
             switch (BlendFactor)
             {
+                default:
                 case GalBlendFactor.Zero:                  return BlendingFactorSrc.Zero;
                 case GalBlendFactor.One:                   return BlendingFactorSrc.One;
                 case GalBlendFactor.SrcColor:              return BlendingFactorSrc.SrcColor;
@@ -179,14 +184,13 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 case GalBlendFactor.Src1Alpha:             return BlendingFactorSrc.Src1Alpha;
                 case GalBlendFactor.OneMinusSrc1Alpha:     return BlendingFactorSrc.OneMinusSrc1Alpha;
             }
-
-            throw new ArgumentException(nameof(BlendFactor));
         }
 
         public static BlendingFactorDest GetBlendFactorDst(GalBlendFactor BlendFactor)
         {
             switch (BlendFactor)
             {
+                default:
                 case GalBlendFactor.Zero:                  return BlendingFactorDest.Zero;
                 case GalBlendFactor.One:                   return BlendingFactorDest.One;
                 case GalBlendFactor.SrcColor:              return BlendingFactorDest.SrcColor;
@@ -207,8 +211,6 @@ namespace Ryujinx.Graphics.Gal.OpenGL
                 case GalBlendFactor.Src1Alpha:             return BlendingFactorDest.Src1Alpha;
                 case GalBlendFactor.OneMinusSrc1Alpha:     return BlendingFactorDest.OneMinusSrc1Alpha;
             }
-
-            throw new ArgumentException(nameof(BlendFactor));
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
index 2650569e6c..43a04813c7 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecl.cs
@@ -4,6 +4,10 @@ namespace Ryujinx.Graphics.Gal.Shader
 {
     class GlslDecl
     {
+        public const int TessCoordAttrX  = 0x2f0;
+        public const int TessCoordAttrY  = 0x2f4;
+        public const int TessCoordAttrZ  = 0x2f8;
+        public const int InstanceIdAttr  = 0x2f8;
         public const int VertexIdAttr    = 0x2fc;
         public const int GlPositionWAttr = 0x7c;
 
@@ -48,7 +52,7 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         public GalShaderType ShaderType { get; private set; }
 
-        public GlslDecl(ShaderIrNode[] Nodes, GalShaderType ShaderType)
+        public GlslDecl(ShaderIrBlock[] Blocks, GalShaderType ShaderType)
         {
             this.ShaderType = ShaderType;
 
@@ -75,9 +79,12 @@ namespace Ryujinx.Graphics.Gal.Shader
                 m_OutAttributes.Add(7, new ShaderDeclInfo("gl_Position", -1, 0, 4));
             }
 
-            foreach (ShaderIrNode Node in Nodes)
+            foreach (ShaderIrBlock Block in Blocks)
             {
-                Traverse(null, Node);
+                foreach (ShaderIrNode Node in Block.GetNodes())
+                {
+                    Traverse(null, Node);
+                }
             }
         }
 
diff --git a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
index 88e462439c..5c4537fe05 100644
--- a/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
+++ b/Ryujinx.Graphics/Gal/Shader/GlslDecompiler.cs
@@ -25,6 +25,8 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private GlslDecl Decl;
 
+        private ShaderIrBlock[] Blocks;
+
         private StringBuilder SB;
 
         public GlslDecompiler()
@@ -37,6 +39,8 @@ namespace Ryujinx.Graphics.Gal.Shader
                 { ShaderIrInst.Asr,    GetAsrExpr    },
                 { ShaderIrInst.Band,   GetBandExpr   },
                 { ShaderIrInst.Bnot,   GetBnotExpr   },
+                { ShaderIrInst.Bor,    GetBorExpr    },
+                { ShaderIrInst.Bxor,   GetBxorExpr   },
                 { ShaderIrInst.Ceil,   GetCeilExpr   },
                 { ShaderIrInst.Ceq,    GetCeqExpr    },
                 { ShaderIrInst.Cge,    GetCgeExpr    },
@@ -50,19 +54,27 @@ namespace Ryujinx.Graphics.Gal.Shader
                 { ShaderIrInst.Fabs,   GetAbsExpr    },
                 { ShaderIrInst.Fadd,   GetAddExpr    },
                 { ShaderIrInst.Fceq,   GetCeqExpr    },
+                { ShaderIrInst.Fcequ,  GetCequExpr   },
                 { ShaderIrInst.Fcge,   GetCgeExpr    },
+                { ShaderIrInst.Fcgeu,  GetCgeuExpr   },
                 { ShaderIrInst.Fcgt,   GetCgtExpr    },
+                { ShaderIrInst.Fcgtu,  GetCgtuExpr   },
                 { ShaderIrInst.Fclamp, GetFclampExpr },
                 { ShaderIrInst.Fcle,   GetCleExpr    },
+                { ShaderIrInst.Fcleu,  GetCleuExpr   },
                 { ShaderIrInst.Fclt,   GetCltExpr    },
+                { ShaderIrInst.Fcltu,  GetCltuExpr   },
+                { ShaderIrInst.Fcnan,  GetCnanExpr   },
                 { ShaderIrInst.Fcne,   GetCneExpr    },
+                { ShaderIrInst.Fcneu,  GetCneuExpr   },
+                { ShaderIrInst.Fcnum,  GetCnumExpr   },
                 { ShaderIrInst.Fcos,   GetFcosExpr   },
                 { ShaderIrInst.Fex2,   GetFex2Expr   },
                 { ShaderIrInst.Ffma,   GetFfmaExpr   },
                 { ShaderIrInst.Flg2,   GetFlg2Expr   },
                 { ShaderIrInst.Floor,  GetFloorExpr  },
-                { ShaderIrInst.Fmax,   GetFmaxExpr   },
-                { ShaderIrInst.Fmin,   GetFminExpr   },
+                { ShaderIrInst.Fmax,   GetMaxExpr    },
+                { ShaderIrInst.Fmin,   GetMinExpr    },
                 { ShaderIrInst.Fmul,   GetMulExpr    },
                 { ShaderIrInst.Fneg,   GetNegExpr    },
                 { ShaderIrInst.Frcp,   GetFrcpExpr   },
@@ -74,6 +86,8 @@ namespace Ryujinx.Graphics.Gal.Shader
                 { ShaderIrInst.Kil,    GetKilExpr    },
                 { ShaderIrInst.Lsl,    GetLslExpr    },
                 { ShaderIrInst.Lsr,    GetLsrExpr    },
+                { ShaderIrInst.Max,    GetMaxExpr    },
+                { ShaderIrInst.Min,    GetMinExpr    },
                 { ShaderIrInst.Mul,    GetMulExpr    },
                 { ShaderIrInst.Neg,    GetNegExpr    },
                 { ShaderIrInst.Not,    GetNotExpr    },
@@ -91,11 +105,9 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         public GlslProgram Decompile(IGalMemory Memory, long Position, GalShaderType ShaderType)
         {
-            ShaderIrBlock Block = ShaderDecoder.DecodeBasicBlock(Memory, Position);
+            Blocks = ShaderDecoder.Decode(Memory, Position);
 
-            ShaderIrNode[] Nodes = Block.GetNodes();
-
-            Decl = new GlslDecl(Nodes, ShaderType);
+            Decl = new GlslDecl(Blocks, ShaderType);
 
             SB = new StringBuilder();
 
@@ -108,7 +120,7 @@ namespace Ryujinx.Graphics.Gal.Shader
             PrintDeclGprs();
             PrintDeclPreds();
 
-            PrintBlockScope(Nodes, 0, Nodes.Length, "void main()", 1);
+            PrintBlockScope(Blocks[0], null, null, "void main()", IdentationStr);
 
             string GlslCode = SB.ToString();
 
@@ -230,37 +242,75 @@ namespace Ryujinx.Graphics.Gal.Shader
         }
 
         private void PrintBlockScope(
-            ShaderIrNode[] Nodes,
-            int            Start,
-            int            Count,
-            string         ScopeName,
-            int            IdentationLevel)
+            ShaderIrBlock Block,
+            ShaderIrBlock EndBlock,
+            ShaderIrBlock LoopBlock,
+            string        ScopeName,
+            string        Identation,
+            bool          IsDoWhile = false)
         {
-            string Identation = string.Empty;
+            string UpIdent = Identation.Substring(0, Identation.Length - IdentationStr.Length);
 
-            for (int Index = 0; Index < IdentationLevel - 1; Index++)
+            if (IsDoWhile)
             {
-                Identation += IdentationStr;
+                SB.AppendLine(UpIdent + "do {");
+            }
+            else
+            {
+                SB.AppendLine(UpIdent + ScopeName + " {");
             }
 
-            if (ScopeName != string.Empty)
+            while (Block != null && Block != EndBlock)
             {
-                ScopeName += " ";
+                ShaderIrNode[] Nodes = Block.GetNodes();
+
+                Block = PrintNodes(Block, EndBlock, LoopBlock, Identation, Nodes);
             }
 
-            SB.AppendLine(Identation + ScopeName + "{");
-
-            string LastLine = Identation + "}";
-
-            if (IdentationLevel > 0)
+            if (IsDoWhile)
             {
-                Identation += IdentationStr;
+                SB.AppendLine(UpIdent + "} " + ScopeName + ";");
+            }
+            else
+            {
+                SB.AppendLine(UpIdent + "}");
+            }
+        }
+
+        private ShaderIrBlock PrintNodes(
+            ShaderIrBlock         Block,
+            ShaderIrBlock         EndBlock,
+            ShaderIrBlock         LoopBlock,
+            string                Identation,
+            params ShaderIrNode[] Nodes)
+        {
+            /*
+             * Notes about control flow and if-else/loop generation:
+             * The code assumes that the program has sane control flow,
+             * that is, there's no jumps to a location after another jump or
+             * jump target (except for the end of an if-else block), and backwards
+             * jumps to a location before the last loop dominator.
+             * Such cases needs to be transformed on a step before the GLSL code
+             * generation to ensure that we have sane graphs to work with.
+             * TODO: Such transformation is not yet implemented.
+             */
+            string NewIdent = Identation + IdentationStr;
+
+            ShaderIrBlock LoopTail = GetLoopTailBlock(Block);
+
+            if (LoopTail != null && LoopBlock != Block)
+            {
+                //Shoock! kuma shock! We have a loop here!
+                //The entire sequence needs to be inside a do-while block.
+                ShaderIrBlock LoopEnd = GetDownBlock(LoopTail);
+
+                PrintBlockScope(Block, LoopEnd, Block, "while (false)", NewIdent, IsDoWhile: true);
+
+                return LoopEnd;
             }
 
-            for (int Index = Start; Index < Start + Count; Index++)
+            foreach (ShaderIrNode Node in Nodes)
             {
-                ShaderIrNode Node = Nodes[Index];
-
                 if (Node is ShaderIrCond Cond)
                 {
                     string IfExpr = GetSrcExpr(Cond.Pred, true);
@@ -272,42 +322,41 @@ namespace Ryujinx.Graphics.Gal.Shader
 
                     if (Cond.Child is ShaderIrOp Op && Op.Inst == ShaderIrInst.Bra)
                     {
-                        ShaderIrLabel Label = (ShaderIrLabel)Op.OperandA;
+                        //Branch is a loop branch and would result in infinite recursion.
+                        if (Block.Branch.Position <= Block.Position)
+                        {
+                            SB.AppendLine(Identation + "if (" + IfExpr + ") {");
 
-                        int Target = FindLabel(Nodes, Label, Index + 1);
+                            SB.AppendLine(Identation + IdentationStr + "continue;");
 
-                        int IfCount = Target - Index - 1;
+                            SB.AppendLine(Identation + "}");
+
+                            continue;
+                        }
 
                         string SubScopeName = "if (!" + IfExpr + ")";
 
-                        if (Nodes[Index + IfCount] is ShaderIrOp LastOp && LastOp.Inst == ShaderIrInst.Bra)
+                        PrintBlockScope(Block.Next, Block.Branch, LoopBlock, SubScopeName, NewIdent);
+
+                        ShaderIrBlock IfElseEnd = GetUpBlock(Block.Branch).Branch;
+
+                        if (IfElseEnd?.Position > Block.Branch.Position)
                         {
-                            Target = FindLabel(Nodes, (ShaderIrLabel)LastOp.OperandA, Index + 1);
+                            PrintBlockScope(Block.Branch, IfElseEnd, LoopBlock, "else", NewIdent);
 
-                            int ElseCount = Target - (Index + 1 + IfCount);
-
-                            PrintBlockScope(Nodes, Index + 1, IfCount - 1, SubScopeName, IdentationLevel + 1);
-
-                            PrintBlockScope(Nodes, Index + 1 + IfCount, ElseCount, "else", IdentationLevel + 1);
-
-                            Index += IfCount + ElseCount;
+                            return IfElseEnd;
                         }
-                        else
-                        {
-                            PrintBlockScope(Nodes, Index + 1, IfCount, SubScopeName, IdentationLevel + 1);
 
-                            Index += IfCount;
-                        }
+                        return Block.Branch;
                     }
                     else
                     {
-                        string SubScopeName = "if (" + IfExpr + ")";
+                        SB.AppendLine(Identation + "if (" + IfExpr + ") {");
 
-                        ShaderIrNode[] Child = new ShaderIrNode[] { Cond.Child };
+                        PrintNodes(Block, EndBlock, LoopBlock, NewIdent, Cond.Child);
 
-                        PrintBlockScope(Child, 0, 1, SubScopeName, IdentationLevel + 1);
+                        SB.AppendLine(Identation + "}");
                     }
-
                 }
                 else if (Node is ShaderIrAsg Asg)
                 {
@@ -322,7 +371,16 @@ namespace Ryujinx.Graphics.Gal.Shader
                 }
                 else if (Node is ShaderIrOp Op)
                 {
-                    if (Op.Inst == ShaderIrInst.Exit)
+                    if (Op.Inst == ShaderIrInst.Bra)
+                    {
+                        if (Block.Branch.Position <= Block.Position)
+                        {
+                            SB.AppendLine(Identation + "continue;");
+                        }
+
+                        continue;
+                    }
+                    else if (Op.Inst == ShaderIrInst.Exit)
                     {
                         //Do everything that needs to be done before
                         //the shader ends here.
@@ -336,10 +394,6 @@ namespace Ryujinx.Graphics.Gal.Shader
 
                     SB.AppendLine(Identation + GetSrcExpr(Op, true) + ";");
                 }
-                else if (Node is ShaderIrLabel Label)
-                {
-                    //TODO: Add support for loops here.
-                }
                 else if (Node is ShaderIrCmnt Cmnt)
                 {
                     SB.AppendLine(Identation + "// " + Cmnt.Comment);
@@ -350,22 +404,35 @@ namespace Ryujinx.Graphics.Gal.Shader
                 }
             }
 
-            SB.AppendLine(LastLine);
+            return Block.Next;
         }
 
-        private int FindLabel(ShaderIrNode[] Nodes, ShaderIrLabel Label, int Start)
+        private ShaderIrBlock GetUpBlock(ShaderIrBlock Block)
         {
-            int Target;
+            return Blocks.FirstOrDefault(x => x.EndPosition == Block.Position);
+        }
 
-            for (Target = Start; Target < Nodes.Length; Target++)
+        private ShaderIrBlock GetDownBlock(ShaderIrBlock Block)
+        {
+            return Blocks.FirstOrDefault(x => x.Position == Block.EndPosition);
+        }
+
+        private ShaderIrBlock GetLoopTailBlock(ShaderIrBlock LoopHead)
+        {
+            ShaderIrBlock Tail = null;
+
+            foreach (ShaderIrBlock Block in LoopHead.Sources)
             {
-                if (Nodes[Target] == Label)
+                if (Block.Position >= LoopHead.Position)
                 {
-                    return Target;
+                    if (Tail == null || Tail.Position < Block.Position)
+                    {
+                        Tail = Block;
+                    }
                 }
             }
 
-            throw new InvalidOperationException();
+            return Tail;
         }
 
         private bool IsValidOutOper(ShaderIrNode Node)
@@ -480,9 +547,22 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private string GetName(ShaderIrOperAbuf Abuf)
         {
-            if (Abuf.Offs == GlslDecl.VertexIdAttr)
+            if (Decl.ShaderType == GalShaderType.Vertex)
             {
-                return "gl_VertexID";
+                switch (Abuf.Offs)
+                {
+                    case GlslDecl.VertexIdAttr:   return "gl_VertexID";
+                    case GlslDecl.InstanceIdAttr: return "gl_InstanceID";
+                }
+            }
+            else if (Decl.ShaderType == GalShaderType.TessEvaluation)
+            {
+                switch (Abuf.Offs)
+                {
+                    case GlslDecl.TessCoordAttrX: return "gl_TessCoord.x";
+                    case GlslDecl.TessCoordAttrY: return "gl_TessCoord.y";
+                    case GlslDecl.TessCoordAttrZ: return "gl_TessCoord.z";
+                }
             }
 
             return GetName(Decl.InAttributes, Abuf);
@@ -567,6 +647,10 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private string GetBnotExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "!");
 
+        private string GetBorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "||");
+
+        private string GetBxorExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "^^");
+
         private string GetCeilExpr(ShaderIrOp Op) => GetUnaryCall(Op, "ceil");
 
         private string GetClampsExpr(ShaderIrOp Op)
@@ -583,13 +667,34 @@ namespace Ryujinx.Graphics.Gal.Shader
                              "uint(" + GetOperExpr(Op, Op.OperandC) + ")))";
         }
 
-        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 GetCequExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "==");
+
         private string GetCgeExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">=");
 
+        private string GetCgeuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, ">=");
+
+        private string GetCgtExpr(ShaderIrOp Op) => GetBinaryExpr(Op, ">");
+
+        private string GetCgtuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, ">");
+
+        private string GetCleExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<=");
+
+        private string GetCleuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "<=");
+
+        private string GetCltExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "<");
+
+        private string GetCltuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "<");
+
+        private string GetCnanExpr(ShaderIrOp Op) => GetUnaryCall(Op, "isnan");
+
+        private string GetCneExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "!=");
+
+        private string GetCneuExpr(ShaderIrOp Op) => GetBinaryExprWithNaN(Op, "!=");
+
+        private string GetCnumExpr(ShaderIrOp Op) => GetUnaryCall(Op, "!isnan");
+
         private string GetExitExpr(ShaderIrOp Op) => "return";
 
         private string GetFcosExpr(ShaderIrOp Op) => GetUnaryCall(Op, "cos");
@@ -604,9 +709,6 @@ namespace Ryujinx.Graphics.Gal.Shader
 
         private string GetFloorExpr(ShaderIrOp Op) => GetUnaryCall(Op, "floor");
 
-        private string GetFmaxExpr(ShaderIrOp Op) => GetBinaryCall(Op, "max");
-        private string GetFminExpr(ShaderIrOp Op) => GetBinaryCall(Op, "min");
-
         private string GetFrcpExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "1 / ");
 
         private string GetFrsqExpr(ShaderIrOp Op) => GetUnaryCall(Op, "inversesqrt");
@@ -634,6 +736,9 @@ namespace Ryujinx.Graphics.Gal.Shader
                                  GetOperExpr(Op, Op.OperandB) + ")";
         }
 
+        private string GetMaxExpr(ShaderIrOp Op) => GetBinaryCall(Op, "max");
+        private string GetMinExpr(ShaderIrOp Op) => GetBinaryCall(Op, "min");
+
         private string GetMulExpr(ShaderIrOp Op) => GetBinaryExpr(Op, "*");
 
         private string GetNegExpr(ShaderIrOp Op) => GetUnaryExpr(Op, "-");
@@ -733,6 +838,18 @@ namespace Ryujinx.Graphics.Gal.Shader
                    GetOperExpr(Op, Op.OperandB);
         }
 
+        private string GetBinaryExprWithNaN(ShaderIrOp Op, string Opr)
+        {
+            string A = GetOperExpr(Op, Op.OperandA);
+            string B = GetOperExpr(Op, Op.OperandB);
+
+            string NaNCheck =
+                " || isnan(" + A + ")" +
+                " || isnan(" + B + ")";
+
+            return A + " " + Opr + " " + B + NaNCheck;
+        }
+
         private string GetTernaryExpr(ShaderIrOp Op, string Opr1, string Opr2)
         {
             return GetOperExpr(Op, Op.OperandA) + " " + Opr1 + " " +
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
index 1bb5f47808..ddd3e3e8c5 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeAlu.cs
@@ -126,6 +126,21 @@ namespace Ryujinx.Graphics.Gal.Shader
             EmitFsetp(Block, OpCode, ShaderOper.RR);
         }
 
+        public static void Imnmx_C(ShaderIrBlock Block, long OpCode)
+        {
+            EmitImnmx(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void Imnmx_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitImnmx(Block, OpCode, ShaderOper.Imm);
+        }
+
+        public static void Imnmx_R(ShaderIrBlock Block, long OpCode)
+        {
+            EmitImnmx(Block, OpCode, ShaderOper.RR);
+        }
+
         public static void Ipa(ShaderIrBlock Block, long OpCode)
         {
             ShaderIrNode OperA = GetOperAbuf28(OpCode);
@@ -265,6 +280,26 @@ namespace Ryujinx.Graphics.Gal.Shader
             return Signed ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
         }
 
+        public static void Xmad_CR(ShaderIrBlock Block, long OpCode)
+        {
+            EmitXmad(Block, OpCode, ShaderOper.CR);
+        }
+
+        public static void Xmad_I(ShaderIrBlock Block, long OpCode)
+        {
+            EmitXmad(Block, OpCode, ShaderOper.Imm);
+        }
+
+        public static void Xmad_RC(ShaderIrBlock Block, long OpCode)
+        {
+            EmitXmad(Block, OpCode, ShaderOper.RC);
+        }
+
+        public static void Xmad_RR(ShaderIrBlock Block, long OpCode)
+        {
+            EmitXmad(Block, OpCode, ShaderOper.RR);
+        }
+
         private static void EmitAluBinary(
             ShaderIrBlock Block,
             long          OpCode,
@@ -411,6 +446,16 @@ namespace Ryujinx.Graphics.Gal.Shader
         }
 
         private static void EmitFmnmx(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            EmitMnmx(Block, OpCode, true, Oper);
+        }
+
+        private static void EmitImnmx(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            EmitMnmx(Block, OpCode, false, Oper);
+        }
+
+        private static void EmitMnmx(ShaderIrBlock Block, long OpCode, bool IsFloat, ShaderOper Oper)
         {
             bool NegB = ((OpCode >> 45) & 1) != 0;
             bool AbsA = ((OpCode >> 46) & 1) != 0;
@@ -419,30 +464,48 @@ namespace Ryujinx.Graphics.Gal.Shader
 
             ShaderIrNode OperA = GetOperGpr8(OpCode), OperB;
 
-            OperA = GetAluFabsFneg(OperA, AbsA, NegA);
+            if (IsFloat)
+            {
+                OperA = GetAluFabsFneg(OperA, AbsA, NegA);
+            }
+            else
+            {
+                OperA = GetAluIabsIneg(OperA, AbsA, NegA);
+            }
 
             switch (Oper)
             {
                 case ShaderOper.CR:   OperB = GetOperCbuf34   (OpCode); break;
+                case ShaderOper.Imm:  OperB = GetOperImm19_20 (OpCode); break;
                 case ShaderOper.Immf: OperB = GetOperImmf19_20(OpCode); break;
                 case ShaderOper.RR:   OperB = GetOperGpr20    (OpCode); break;
 
                 default: throw new ArgumentException(nameof(Oper));
             }
 
-            OperB = GetAluFabsFneg(OperB, AbsB, NegB);
+            if (IsFloat)
+            {
+                OperB = GetAluFabsFneg(OperB, AbsB, NegB);
+            }
+            else
+            {
+                OperB = GetAluIabsIneg(OperB, AbsB, NegB);
+            }
 
             ShaderIrOperPred Pred = GetOperPred39(OpCode);
 
             ShaderIrOp Op;
 
+            ShaderIrInst MaxInst = IsFloat ? ShaderIrInst.Fmax : ShaderIrInst.Max;
+            ShaderIrInst MinInst = IsFloat ? ShaderIrInst.Fmin : ShaderIrInst.Min;
+
             if (Pred.IsConst)
             {
                 bool IsMax = ((OpCode >> 42) & 1) != 0;
 
                 Op = new ShaderIrOp(IsMax
-                    ? ShaderIrInst.Fmax
-                    : ShaderIrInst.Fmin, OperA, OperB);
+                    ? MaxInst
+                    : MinInst, OperA, OperB);
 
                 Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), Op), OpCode));
             }
@@ -450,8 +513,8 @@ namespace Ryujinx.Graphics.Gal.Shader
             {
                 ShaderIrNode PredN = GetOperPred39N(OpCode);
 
-                ShaderIrOp OpMax = new ShaderIrOp(ShaderIrInst.Fmax, OperA, OperB);
-                ShaderIrOp OpMin = new ShaderIrOp(ShaderIrInst.Fmin, OperA, OperB);
+                ShaderIrOp OpMax = new ShaderIrOp(MaxInst, OperA, OperB);
+                ShaderIrOp OpMin = new ShaderIrOp(MinInst, OperA, OperB);
 
                 ShaderIrAsg AsgMax = new ShaderIrAsg(GetOperGpr0(OpCode), OpMax);
                 ShaderIrAsg AsgMin = new ShaderIrAsg(GetOperGpr0(OpCode), OpMin);
@@ -646,5 +709,94 @@ namespace Ryujinx.Graphics.Gal.Shader
 
             Block.AddNode(GetPredNode(new ShaderIrAsg(P0Node, Op), OpCode));
         }
+
+        private static void EmitXmad(ShaderIrBlock Block, long OpCode, ShaderOper Oper)
+        {
+            //TODO: Confirm SignAB/C, it is just a guess.
+            //TODO: Implement Mode 3 (CSFU), what it does?
+            bool SignAB = ((OpCode >> 48) & 1) != 0;
+            bool SignC  = ((OpCode >> 49) & 1) != 0;
+            bool HighB  = ((OpCode >> 52) & 1) != 0;
+            bool HighA  = ((OpCode >> 53) & 1) != 0;
+
+            int Mode = (int)(OpCode >> 50) & 7;
+
+            ShaderIrNode OperA = GetOperGpr8(OpCode), OperB, OperC;
+
+            ShaderIrOperImm Imm16  = new ShaderIrOperImm(16);
+            ShaderIrOperImm ImmMsk = new ShaderIrOperImm(0xffff);
+
+            ShaderIrInst ShiftAB = SignAB ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
+            ShaderIrInst ShiftC  = SignC  ? ShaderIrInst.Asr : ShaderIrInst.Lsr;
+
+            if (HighA)
+            {
+                OperA = new ShaderIrOp(ShiftAB, OperA, Imm16);
+            }
+
+            switch (Oper)
+            {
+                case ShaderOper.CR:  OperB = GetOperCbuf34  (OpCode); break;
+                case ShaderOper.Imm: OperB = GetOperImm19_20(OpCode); break;
+                case ShaderOper.RC:  OperB = GetOperGpr39   (OpCode); break;
+                case ShaderOper.RR:  OperB = GetOperGpr20   (OpCode); break;
+
+                default: throw new ArgumentException(nameof(Oper));
+            }
+
+            bool ProductShiftLeft = false, Merge = false;
+
+            if (Oper == ShaderOper.RC)
+            {
+                OperC = GetOperCbuf34(OpCode);
+            }
+            else
+            {
+                OperC = GetOperGpr39(OpCode);
+
+                ProductShiftLeft = ((OpCode >> 36) & 1) != 0;
+                Merge            = ((OpCode >> 37) & 1) != 0;
+            }
+
+            switch (Mode)
+            {
+                //CLO.
+                case 1: OperC = ExtendTo32(OperC, SignC, 16); break;
+
+                //CHI.
+                case 2: OperC = new ShaderIrOp(ShiftC, OperC, Imm16); break;
+            }
+
+            ShaderIrNode OperBH = OperB;
+
+            if (HighB)
+            {
+                OperBH = new ShaderIrOp(ShiftAB, OperBH, Imm16);
+            }
+
+            ShaderIrOp MulOp = new ShaderIrOp(ShaderIrInst.Mul, OperA, OperBH);
+
+            if (ProductShiftLeft)
+            {
+                MulOp = new ShaderIrOp(ShaderIrInst.Lsl, MulOp, Imm16);
+            }
+
+            ShaderIrOp AddOp = new ShaderIrOp(ShaderIrInst.Add, MulOp, OperC);
+
+            if (Merge)
+            {
+                AddOp = new ShaderIrOp(ShaderIrInst.And, AddOp, ImmMsk);
+                OperB = new ShaderIrOp(ShaderIrInst.Lsl, OperB, Imm16);
+                AddOp = new ShaderIrOp(ShaderIrInst.Or,  AddOp, OperB);
+            }
+
+            if (Mode == 4)
+            {
+                OperB = new ShaderIrOp(ShaderIrInst.Lsl, OperB, Imm16);
+                AddOp = new ShaderIrOp(ShaderIrInst.Or,  AddOp, OperB);
+            }
+
+            Block.AddNode(GetPredNode(new ShaderIrAsg(GetOperGpr0(OpCode), AddOp), OpCode));
+        }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
index 6f48d1a8cd..89949d62ee 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecodeFlow.cs
@@ -15,11 +15,11 @@ namespace Ryujinx.Graphics.Gal.Shader
                 throw new NotImplementedException();
             }
 
-            long Target = ((int)(OpCode >> 20) << 8) >> 8;
+            int Target = ((int)(OpCode >> 20) << 8) >> 8;
 
-            Target += Block.Position + 8;
+            ShaderIrOperImm Imm = new ShaderIrOperImm(Target);
 
-            Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Bra, Block.GetLabel(Target)), OpCode));
+            Block.AddNode(GetPredNode(new ShaderIrOp(ShaderIrInst.Bra, Imm), OpCode));
         }
 
         public static void Exit(ShaderIrBlock Block, long OpCode)
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
index 024fa36489..85522ff950 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderDecoder.cs
@@ -1,19 +1,133 @@
+using System.Collections.Generic;
+
 namespace Ryujinx.Graphics.Gal.Shader
 {
     static class ShaderDecoder
     {
         private const bool AddDbgComments = true;
 
-        public static ShaderIrBlock DecodeBasicBlock(IGalMemory Memory, long Position)
+        public static ShaderIrBlock[] Decode(IGalMemory Memory, long Start)
         {
-            ShaderIrBlock Block = new ShaderIrBlock();
+            Dictionary<long, ShaderIrBlock> Visited    = new Dictionary<long, ShaderIrBlock>();
+            Dictionary<long, ShaderIrBlock> VisitedEnd = new Dictionary<long, ShaderIrBlock>();
 
-            while (true)
+            Queue<ShaderIrBlock> Blocks = new Queue<ShaderIrBlock>();
+
+            ShaderIrBlock Enqueue(long Position, ShaderIrBlock Source = null)
             {
-                Block.Position = Position;
+                if (!Visited.TryGetValue(Position, out ShaderIrBlock Output))
+                {
+                    Output = new ShaderIrBlock(Position);
 
-                Block.MarkLabel(Position);
+                    Blocks.Enqueue(Output);
 
+                    Visited.Add(Position, Output);
+                }
+
+                if (Source != null)
+                {
+                    Output.Sources.Add(Source);
+                }
+
+                return Output;
+            }
+
+            ShaderIrBlock Entry = Enqueue(Start);
+
+            while (Blocks.Count > 0)
+            {
+                ShaderIrBlock Current = Blocks.Dequeue();
+
+                FillBlock(Memory, Current);
+
+                //Set child blocks. "Branch" is the block the branch instruction
+                //points to (when taken), "Next" is the block at the next address,
+                //executed when the branch is not taken. For Unconditional Branches
+                //or end of shader, Next is null.
+                if (Current.Nodes.Count > 0)
+                {
+                    ShaderIrNode LastNode = Current.GetLastNode();
+
+                    ShaderIrOp Op = GetInnermostOp(LastNode);
+
+                    if (Op?.Inst == ShaderIrInst.Bra)
+                    {
+                        int Offset = ((ShaderIrOperImm)Op.OperandA).Value;
+
+                        long Target = Current.EndPosition + Offset;
+
+                        Current.Branch = Enqueue(Target, Current);
+                    }
+
+                    if (NodeHasNext(LastNode))
+                    {
+                        Current.Next = Enqueue(Current.EndPosition);
+                    }
+                }
+
+                //If we have on the graph two blocks with the same end position,
+                //then we need to split the bigger block and have two small blocks,
+                //the end position of the bigger "Current" block should then be == to
+                //the position of the "Smaller" block.
+                while (VisitedEnd.TryGetValue(Current.EndPosition, out ShaderIrBlock Smaller))
+                {
+                    if (Current.Position > Smaller.Position)
+                    {
+                        ShaderIrBlock Temp = Smaller;
+
+                        Smaller = Current;
+                        Current = Temp;
+                    }
+
+                    Current.EndPosition = Smaller.Position;
+                    Current.Next        = Smaller;
+                    Current.Branch      = null;
+
+                    Current.Nodes.RemoveRange(
+                        Current.Nodes.Count - Smaller.Nodes.Count,
+                        Smaller.Nodes.Count);
+
+                    VisitedEnd[Smaller.EndPosition] = Smaller;
+                }
+
+                VisitedEnd.Add(Current.EndPosition, Current);
+            }
+
+            //Make and sort Graph blocks array by position.
+            ShaderIrBlock[] Graph = new ShaderIrBlock[Visited.Count];
+
+            while (Visited.Count > 0)
+            {
+                ulong FirstPos = ulong.MaxValue;
+
+                foreach (ShaderIrBlock Block in Visited.Values)
+                {
+                    if (FirstPos > (ulong)Block.Position)
+                        FirstPos = (ulong)Block.Position;
+                }
+
+                ShaderIrBlock Current = Visited[(long)FirstPos];
+
+                do
+                {
+                    Graph[Graph.Length - Visited.Count] = Current;
+
+                    Visited.Remove(Current.Position);
+
+                    Current = Current.Next;
+                }
+                while (Current != null);
+            }
+
+            return Graph;
+        }
+
+        private static void FillBlock(IGalMemory Memory, ShaderIrBlock Block)
+        {
+            long Position = Block.Position;
+
+            do
+            {
                 //Ignore scheduling instructions, which are written every 32 bytes.
                 if ((Position & 0x1f) == 0)
                 {
@@ -33,9 +147,20 @@ namespace Ryujinx.Graphics.Gal.Shader
 
                 if (AddDbgComments)
                 {
-                    string DbgOpCode = $"0x{Position:x16}: 0x{OpCode:x16} ";
+                    string DbgOpCode = $"0x{(Position - 8):x16}: 0x{OpCode:x16} ";
 
-                    Block.AddNode(new ShaderIrCmnt(DbgOpCode + (Decode?.Method.Name ?? "???")));
+                    DbgOpCode += (Decode?.Method.Name ?? "???");
+
+                    if (Decode == ShaderDecode.Bra)
+                    {
+                        int Offset = ((int)(OpCode >> 20) << 8) >> 8;
+
+                        long Target = Position + Offset;
+
+                        DbgOpCode += " (0x" + Target.ToString("x16") + ")";
+                    }
+
+                    Block.AddNode(new ShaderIrCmnt(DbgOpCode));
                 }
 
                 if (Decode == null)
@@ -44,19 +169,36 @@ namespace Ryujinx.Graphics.Gal.Shader
                 }
 
                 Decode(Block, OpCode);
-
-                if (Block.GetLastNode() is ShaderIrOp Op && Op.Inst == ShaderIrInst.Exit)
-                {
-                    break;
-                }
             }
+            while (!IsFlowChange(Block.GetLastNode()));
 
-            return Block;
+            Block.EndPosition = Position;
         }
 
-        private static bool IsFlowChange(ShaderIrInst Inst)
+        private static bool IsFlowChange(ShaderIrNode Node)
         {
-            return Inst == ShaderIrInst.Exit;
+            return !NodeHasNext(GetInnermostOp(Node));
+        }
+
+        private static ShaderIrOp GetInnermostOp(ShaderIrNode Node)
+        {
+            if (Node is ShaderIrCond Cond)
+            {
+                Node = Cond.Child;
+            }
+
+            return Node is ShaderIrOp Op ? Op : null;
+        }
+
+        private static bool NodeHasNext(ShaderIrNode Node)
+        {
+            if (!(Node is ShaderIrOp Op))
+            {
+                return true;
+            }
+
+            return Op.Inst != ShaderIrInst.Exit &&
+                   Op.Inst != ShaderIrInst.Bra;
         }
     }
 }
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
index 31d7216964..50e563b846 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrBlock.cs
@@ -4,17 +4,23 @@ namespace Ryujinx.Graphics.Gal.Shader
 {
     class ShaderIrBlock
     {
-        private List<ShaderIrNode> Nodes;
+        public long Position    { get; set; }
+        public long EndPosition { get; set; }
 
-        private Dictionary<long, ShaderIrLabel> LabelsToInsert;
+        public ShaderIrBlock Next   { get; set; }
+        public ShaderIrBlock Branch { get; set; }
 
-        public long Position;
+        public List<ShaderIrBlock> Sources { get; private set; }
 
-        public ShaderIrBlock()
+        public List<ShaderIrNode> Nodes { get; private set; }
+
+        public ShaderIrBlock(long Position)
         {
-            Nodes = new List<ShaderIrNode>();
+            this.Position = Position;
 
-            LabelsToInsert = new Dictionary<long, ShaderIrLabel>();
+            Sources = new List<ShaderIrBlock>();
+
+            Nodes = new List<ShaderIrNode>();
         }
 
         public void AddNode(ShaderIrNode Node)
@@ -22,28 +28,6 @@ namespace Ryujinx.Graphics.Gal.Shader
             Nodes.Add(Node);
         }
 
-        public ShaderIrLabel GetLabel(long Position)
-        {
-            if (LabelsToInsert.TryGetValue(Position, out ShaderIrLabel Label))
-            {
-                return Label;
-            }
-
-            Label = new ShaderIrLabel();
-
-            LabelsToInsert.Add(Position, Label);
-
-            return Label;
-        }
-
-        public void MarkLabel(long Position)
-        {
-            if (LabelsToInsert.TryGetValue(Position, out ShaderIrLabel Label))
-            {
-                Nodes.Add(Label);
-            }
-        }
-
         public ShaderIrNode[] GetNodes()
         {
             return Nodes.ToArray();
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
index af2ccc3b3b..2de50a4a78 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderIrInst.cs
@@ -65,6 +65,8 @@ namespace Ryujinx.Graphics.Gal.Shader
         Cne,
         Lsl,
         Lsr,
+        Max,
+        Min,
         Mul,
         Neg,
         Not,
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderIrLabel.cs b/Ryujinx.Graphics/Gal/Shader/ShaderIrLabel.cs
deleted file mode 100644
index f5b3585af8..0000000000
--- a/Ryujinx.Graphics/Gal/Shader/ShaderIrLabel.cs
+++ /dev/null
@@ -1,4 +0,0 @@
-namespace Ryujinx.Graphics.Gal.Shader
-{
-    class ShaderIrLabel : ShaderIrNode { }
-}
\ No newline at end of file
diff --git a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
index acfcc147c0..51197fd482 100644
--- a/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
+++ b/Ryujinx.Graphics/Gal/Shader/ShaderOpCodeTable.cs
@@ -63,6 +63,9 @@ namespace Ryujinx.Graphics.Gal.Shader
             Set("0100110011100x", ShaderDecode.I2i_C);
             Set("0011100x11100x", ShaderDecode.I2i_I);
             Set("0101110011100x", ShaderDecode.I2i_R);
+            Set("0100110000100x", ShaderDecode.Imnmx_C);
+            Set("0011100x00100x", ShaderDecode.Imnmx_I);
+            Set("0101110000100x", ShaderDecode.Imnmx_R);
             Set("11100000xxxxxx", ShaderDecode.Ipa);
             Set("0100110000011x", ShaderDecode.Iscadd_C);
             Set("0011100x00011x", ShaderDecode.Iscadd_I);
@@ -89,6 +92,10 @@ namespace Ryujinx.Graphics.Gal.Shader
             Set("1101111101001x", ShaderDecode.Texq);
             Set("1101100xxxxxxx", ShaderDecode.Texs);
             Set("1101101xxxxxxx", ShaderDecode.Tlds);
+            Set("0100111xxxxxxx", ShaderDecode.Xmad_CR);
+            Set("0011011x00xxxx", ShaderDecode.Xmad_I);
+            Set("010100010xxxxx", ShaderDecode.Xmad_RC);
+            Set("0101101100xxxx", ShaderDecode.Xmad_RR);
 #endregion
         }