diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index a4d02e5729..dbcd237552 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -56,15 +56,18 @@ union Attribute {
         Attribute_0 = 8,
     };
 
-    constexpr Index GetIndex() const {
-        return index;
-    }
+    union {
+        BitField<22, 2, u64> element;
+        BitField<24, 6, Index> index;
+        BitField<47, 3, u64> size;
+    } fmt20;
+
+    union {
+        BitField<30, 2, u64> element;
+        BitField<32, 6, Index> index;
+    } fmt28;
 
-public:
-    BitField<24, 6, Index> index;
-    BitField<22, 2, u64> element;
     BitField<39, 8, u64> reg;
-    BitField<47, 3, u64> size;
     u64 value;
 };
 
@@ -104,6 +107,7 @@ union OpCode {
     enum class Type {
         Trivial,
         Arithmetic,
+        Ffma,
         Flow,
         Memory,
         Unknown,
@@ -210,12 +214,11 @@ union OpCode {
         info_table[Id::TEXS] = {Type::Memory, "texs"};
         info_table[Id::LD_A] = {Type::Memory, "ld_a"};
         info_table[Id::ST_A] = {Type::Memory, "st_a"};
-        info_table[Id::IPA] = {Type::Arithmetic, "ipa"};
         info_table[Id::MUFU] = {Type::Arithmetic, "mufu"};
-        info_table[Id::FFMA_IMM] = {Type::Arithmetic, "ffma_imm"};
-        info_table[Id::FFMA_CR] = {Type::Arithmetic, "ffma_cr"};
-        info_table[Id::FFMA_RC] = {Type::Arithmetic, "ffma_rc"};
-        info_table[Id::FFMA_RR] = {Type::Arithmetic, "ffma_rr"};
+        info_table[Id::FFMA_IMM] = {Type::Ffma, "ffma_imm"};
+        info_table[Id::FFMA_CR] = {Type::Ffma, "ffma_cr"};
+        info_table[Id::FFMA_RC] = {Type::Ffma, "ffma_rc"};
+        info_table[Id::FFMA_RR] = {Type::Ffma, "ffma_rr"};
         info_table[Id::FADD_R] = {Type::Arithmetic, "fadd_r"};
         info_table[Id::FADD_C] = {Type::Arithmetic, "fadd_c"};
         info_table[Id::FADD_IMM] = {Type::Arithmetic, "fadd_imm"};
@@ -225,6 +228,7 @@ union OpCode {
         info_table[Id::FSETP_C] = {Type::Arithmetic, "fsetp_c"};
         info_table[Id::FSETP_R] = {Type::Arithmetic, "fsetp_r"};
         info_table[Id::EXIT] = {Type::Trivial, "exit"};
+        info_table[Id::IPA] = {Type::Trivial, "ipa"};
         info_table[Id::KIL] = {Type::Flow, "kil"};
         return info_table;
     }
@@ -285,16 +289,31 @@ union Instruction {
     }
 
     OpCode opcode;
-    BitField<0, 8, Register> gpr1;
-    BitField<8, 8, Register> gpr2;
+    BitField<0, 8, Register> gpr0;
+    BitField<8, 8, Register> gpr8;
     BitField<16, 4, Pred> pred;
+    BitField<20, 8, Register> gpr20;
     BitField<20, 7, SubOp> sub_op;
-    BitField<39, 8, Register> gpr3;
-    BitField<45, 1, u64> nb;
-    BitField<46, 1, u64> aa;
-    BitField<48, 1, u64> na;
-    BitField<49, 1, u64> ab;
-    BitField<50, 1, u64> ad;
+    BitField<28, 8, Register> gpr28;
+    BitField<36, 13, u64> imm36;
+    BitField<39, 8, Register> gpr39;
+
+    union {
+        BitField<45, 1, u64> negate_b;
+        BitField<46, 1, u64> abs_a;
+        BitField<48, 1, u64> negate_a;
+        BitField<49, 1, u64> abs_b;
+        BitField<50, 1, u64> abs_d;
+    } alu;
+
+    union {
+        BitField<48, 1, u64> negate_b;
+        BitField<49, 1, u64> negate_c;
+    } ffma;
+
+    BitField<60, 1, u64> is_b_gpr;
+    BitField<59, 1, u64> is_c_gpr;
+
     Attribute attribute;
     Uniform uniform;
 
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index 704b24307d..792b4b12e7 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -17,6 +17,7 @@ using Tegra::Shader::Attribute;
 using Tegra::Shader::Instruction;
 using Tegra::Shader::OpCode;
 using Tegra::Shader::Register;
+using Tegra::Shader::SubOp;
 using Tegra::Shader::Uniform;
 
 constexpr u32 PROGRAM_END = MAX_PROGRAM_CODE_LENGTH;
@@ -235,27 +236,34 @@ private:
 
         switch (OpCode::GetInfo(instr.opcode).type) {
         case OpCode::Type::Arithmetic: {
-            ASSERT(!instr.nb);
-            ASSERT(!instr.aa);
-            ASSERT(!instr.na);
-            ASSERT(!instr.ab);
-            ASSERT(!instr.ad);
+            ASSERT(!instr.alu.abs_d, "unimplemented");
 
-            std::string gpr1 = GetRegister(instr.gpr1);
-            std::string gpr2 = GetRegister(instr.gpr2);
-            std::string uniform = GetUniform(instr.uniform);
+            std::string dest = GetRegister(instr.gpr0);
+            std::string op_a = instr.alu.negate_a ? "-" : "";
+            op_a += GetRegister(instr.gpr8);
+            if (instr.alu.abs_a) {
+                op_a = "abs(" + op_a + ")";
+            }
+
+            std::string op_b = instr.alu.negate_b ? "-" : "";
+            if (instr.is_b_gpr) {
+                op_b += GetRegister(instr.gpr20);
+            } else {
+                op_b += GetUniform(instr.uniform);
+            }
+            if (instr.alu.abs_b) {
+                op_b = "abs(" + op_b + ")";
+            }
 
             switch (instr.opcode.EffectiveOpCode()) {
-            case OpCode::Id::FMUL_C: {
-                SetDest(0, gpr1, gpr2 + " * " + uniform, 1, 1);
+            case OpCode::Id::FMUL_C:
+            case OpCode::Id::FMUL_R: {
+                SetDest(0, dest, op_a + " * " + op_b, 1, 1);
                 break;
             }
-            case OpCode::Id::FADD_C: {
-                SetDest(0, gpr1, gpr2 + " + " + uniform, 1, 1);
-                break;
-            }
-            case OpCode::Id::FFMA_CR: {
-                SetDest(0, gpr1, gpr2 + " * " + uniform + " + " + GetRegister(instr.gpr3), 1, 1);
+            case OpCode::Id::FADD_C:
+            case OpCode::Id::FADD_R: {
+                SetDest(0, dest, op_a + " + " + op_b, 1, 1);
                 break;
             }
             default: {
@@ -268,19 +276,48 @@ private:
             }
             break;
         }
-        case OpCode::Type::Memory: {
-            ASSERT(instr.attribute.size == 0);
+        case OpCode::Type::Ffma: {
+            ASSERT_MSG(!instr.ffma.negate_b, "untested");
+            ASSERT_MSG(!instr.ffma.negate_c, "untested");
 
-            std::string gpr1 = GetRegister(instr.gpr1);
-            const Attribute::Index attribute = instr.attribute.GetIndex();
+            std::string dest = GetRegister(instr.gpr0);
+            std::string op_a = GetRegister(instr.gpr8);
+
+            std::string op_b = instr.ffma.negate_b ? "-" : "";
+            op_b += GetUniform(instr.uniform);
+
+            std::string op_c = instr.ffma.negate_c ? "-" : "";
+            op_c += GetRegister(instr.gpr39);
+
+            switch (instr.opcode.EffectiveOpCode()) {
+            case OpCode::Id::FFMA_CR: {
+                SetDest(0, dest, op_a + " * " + op_b + " + " + op_c, 1, 1);
+                break;
+            }
+
+            default: {
+                LOG_ERROR(HW_GPU, "Unhandled arithmetic FFMA instruction: 0x%02x (%s): 0x%08x",
+                          (int)instr.opcode.EffectiveOpCode(), OpCode::GetInfo(instr.opcode).name,
+                          instr.hex);
+                throw DecompileFail("Unhandled instruction");
+                break;
+            }
+            }
+            break;
+        }
+        case OpCode::Type::Memory: {
+            std::string gpr0 = GetRegister(instr.gpr0);
+            const Attribute::Index attribute = instr.attribute.fmt20.index;
 
             switch (instr.opcode.EffectiveOpCode()) {
             case OpCode::Id::LD_A: {
-                SetDest(instr.attribute.element, gpr1, GetInputAttribute(attribute), 1, 4);
+                ASSERT(instr.attribute.fmt20.size == 0);
+                SetDest(instr.attribute.fmt20.element, gpr0, GetInputAttribute(attribute), 1, 4);
                 break;
             }
             case OpCode::Id::ST_A: {
-                SetDest(instr.attribute.element, GetOutputAttribute(attribute), gpr1, 4, 1);
+                ASSERT(instr.attribute.fmt20.size == 0);
+                SetDest(instr.attribute.fmt20.element, GetOutputAttribute(attribute), gpr0, 4, 1);
                 break;
             }
             default: {