From 17290a44160d577e1b0d494496ab816c916432b5 Mon Sep 17 00:00:00 2001
From: ReinUsesLisp <reinuseslisp@airmail.cc>
Date: Thu, 11 Oct 2018 03:56:14 -0300
Subject: [PATCH] gl_shader_decompiler: Implement VMAD

---
 src/video_core/engines/shader_bytecode.h      | 36 ++++++++
 .../renderer_opengl/gl_shader_decompiler.cpp  | 82 +++++++++++++++++++
 2 files changed, 118 insertions(+)

diff --git a/src/video_core/engines/shader_bytecode.h b/src/video_core/engines/shader_bytecode.h
index 550ab11486..9a59b65b33 100644
--- a/src/video_core/engines/shader_bytecode.h
+++ b/src/video_core/engines/shader_bytecode.h
@@ -214,6 +214,18 @@ enum class IMinMaxExchange : u64 {
     XHi = 3,
 };
 
+enum class VmadType : u64 {
+    Size16_Low = 0,
+    Size16_High = 1,
+    Size32 = 2,
+    Invalid = 3,
+};
+
+enum class VmadShr : u64 {
+    Shr7 = 1,
+    Shr15 = 2,
+};
+
 enum class XmadMode : u64 {
     None = 0,
     CLo = 1,
@@ -452,6 +464,7 @@ union Instruction {
     BitField<48, 16, u64> opcode;
 
     union {
+        BitField<20, 16, u64> imm20_16;
         BitField<20, 19, u64> imm20_19;
         BitField<20, 32, s64> imm20_32;
         BitField<45, 1, u64> negate_b;
@@ -493,6 +506,10 @@ union Instruction {
             }
         } lop3;
 
+        u16 GetImm20_16() const {
+            return static_cast<u16>(imm20_16);
+        }
+
         u32 GetImm20_19() const {
             u32 imm{static_cast<u32>(imm20_19)};
             imm <<= 12;
@@ -1016,6 +1033,23 @@ union Instruction {
         BitField<47, 2, IsberdShift> shift;
     } isberd;
 
+    union {
+        BitField<48, 1, u64> signed_a;
+        BitField<38, 1, u64> is_byte_chunk_a;
+        BitField<36, 2, VmadType> type_a;
+        BitField<36, 2, u64> byte_height_a;
+
+        BitField<49, 1, u64> signed_b;
+        BitField<50, 1, u64> use_register_b;
+        BitField<30, 1, u64> is_byte_chunk_b;
+        BitField<28, 2, VmadType> type_b;
+        BitField<28, 2, u64> byte_height_b;
+
+        BitField<51, 2, VmadShr> shr;
+        BitField<55, 1, u64> saturate; // Saturates the result (a * b + c)
+        BitField<47, 1, u64> cc;
+    } vmad;
+
     union {
         BitField<20, 16, u64> imm20_16;
         BitField<36, 1, u64> product_shift_left;
@@ -1083,6 +1117,7 @@ public:
         IPA,
         OUT_R, // Emit vertex/primitive
         ISBERD,
+        VMAD,
         FFMA_IMM, // Fused Multiply and Add
         FFMA_CR,
         FFMA_RC,
@@ -1320,6 +1355,7 @@ private:
             INST("11100000--------", Id::IPA, Type::Trivial, "IPA"),
             INST("1111101111100---", Id::OUT_R, Type::Trivial, "OUT_R"),
             INST("1110111111010---", Id::ISBERD, Type::Trivial, "ISBERD"),
+            INST("01011111--------", Id::VMAD, Type::Trivial, "VMAD"),
             INST("0011001-1-------", Id::FFMA_IMM, Type::Ffma, "FFMA_IMM"),
             INST("010010011-------", Id::FFMA_CR, Type::Ffma, "FFMA_CR"),
             INST("010100011-------", Id::FFMA_RC, Type::Ffma, "FFMA_RC"),
diff --git a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
index c82a0dcfa5..8dfb495073 100644
--- a/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
+++ b/src/video_core/renderer_opengl/gl_shader_decompiler.cpp
@@ -2953,6 +2953,88 @@ private:
                 LOG_WARNING(HW_GPU, "DEPBAR instruction is stubbed");
                 break;
             }
+            case OpCode::Id::VMAD: {
+                const bool signed_a = instr.vmad.signed_a == 1;
+                const bool signed_b = instr.vmad.signed_b == 1;
+                const bool result_signed = signed_a || signed_b;
+                boost::optional<std::string> forced_result;
+
+                auto Unpack = [&](const std::string& op, bool is_chunk, bool is_signed,
+                                  Tegra::Shader::VmadType type, u64 byte_height) {
+                    const std::string value = [&]() {
+                        if (!is_chunk) {
+                            const auto offset = static_cast<u32>(byte_height * 8);
+                            return "((" + op + " >> " + std::to_string(offset) + ") & 0xff)";
+                        }
+                        const std::string zero = "0";
+
+                        switch (type) {
+                        case Tegra::Shader::VmadType::Size16_Low:
+                            return '(' + op + " & 0xffff)";
+                        case Tegra::Shader::VmadType::Size16_High:
+                            return '(' + op + " >> 16)";
+                        case Tegra::Shader::VmadType::Size32:
+                            // TODO(Rodrigo): From my hardware tests it becomes a bit "mad" when
+                            // this type is used (1 * 1 + 0 == 0x5b800000). Until a better
+                            // explanation is found: assert.
+                            UNREACHABLE_MSG("Unimplemented");
+                            return zero;
+                        case Tegra::Shader::VmadType::Invalid:
+                            // Note(Rodrigo): This flag is invalid according to nvdisasm. From my
+                            // testing (even though it's invalid) this makes the whole instruction
+                            // assign zero to target register.
+                            forced_result = boost::make_optional(zero);
+                            return zero;
+                        default:
+                            UNREACHABLE();
+                            return zero;
+                        }
+                    }();
+
+                    if (is_signed) {
+                        return "int(" + value + ')';
+                    }
+                    return value;
+                };
+
+                const std::string op_a = Unpack(regs.GetRegisterAsInteger(instr.gpr8, 0, false),
+                                                instr.vmad.is_byte_chunk_a != 0, signed_a,
+                                                instr.vmad.type_a, instr.vmad.byte_height_a);
+
+                std::string op_b;
+                if (instr.vmad.use_register_b) {
+                    op_b = Unpack(regs.GetRegisterAsInteger(instr.gpr20, 0, false),
+                                  instr.vmad.is_byte_chunk_b != 0, signed_b, instr.vmad.type_b,
+                                  instr.vmad.byte_height_b);
+                } else {
+                    op_b = '(' +
+                           std::to_string(signed_b ? static_cast<s16>(instr.alu.GetImm20_16())
+                                                   : instr.alu.GetImm20_16()) +
+                           ')';
+                }
+
+                const std::string op_c = regs.GetRegisterAsInteger(instr.gpr39, 0, result_signed);
+
+                std::string result;
+                if (forced_result) {
+                    result = *forced_result;
+                } else {
+                    result = '(' + op_a + " * " + op_b + " + " + op_c + ')';
+
+                    switch (instr.vmad.shr) {
+                    case Tegra::Shader::VmadShr::Shr7:
+                        result = '(' + result + " >> 7)";
+                        break;
+                    case Tegra::Shader::VmadShr::Shr15:
+                        result = '(' + result + " >> 15)";
+                        break;
+                    }
+                }
+                regs.SetRegisterToInteger(instr.gpr0, result_signed, 1, result, 1, 1,
+                                          instr.vmad.saturate == 1, 0, Register::Size::Word,
+                                          instr.vmad.cc);
+                break;
+            }
             default: {
                 LOG_CRITICAL(HW_GPU, "Unhandled instruction: {}", opcode->GetName());
                 UNREACHABLE();