diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 45a94d62..2ec7a9ed 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -228,6 +228,7 @@ enum class Opcode { V_CVT_PKRTZ_F16_F32, V_CVT_PK_U8_F32, V_MAD_F32, + V_MAD_I16, V_MAD_I32_I24, V_MAD_U32_U24, V_MAD_U64_U32, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 7e9998ff..1cf34224 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -312,6 +312,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = { {0x345u, Opcode::V_XAD_U32}, {0x346u, Opcode::V_LSHL_ADD_U32}, {0x347u, Opcode::V_ADD_LSHL_U32}, + {0x35eu, Opcode::V_MAD_I16}, {0x360u, Opcode::V_READLANE_B32}, {0x361u, Opcode::V_WRITELANE_B32}, {0x362u, Opcode::V_LDEXP_F32}, @@ -424,7 +425,7 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) { } bool IsNativeVop3I16TernaryOpcode(Opcode opcode) { - return opcode == Opcode::V_MED3_I16; + return opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MAD_I16; } bool IsNativeVop3B16BinaryOpcode(Opcode opcode) { @@ -1362,7 +1363,8 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) { } bool SupportsNativeVop3Clamp(Opcode opcode) { - return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode); + return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || + opcode == Opcode::V_MAD_I16; } bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, @@ -1383,7 +1385,7 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi return opcode != Opcode::V_FMA_F16 && (clamp != 0u || omod != 0u); } if (IsNativeVop3I16TernaryOpcode(opcode)) { - return abs != 0u || clamp != 0u || omod != 0u || neg != 0u; + return abs != 0u || (clamp != 0u && !clamp_modifier) || omod != 0u || neg != 0u; } if (IsNativeVop3B16BinaryOpcode(opcode)) { return abs != 0u || clamp != 0u || omod != 0u || neg != 0u; diff --git a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp index fb6eeb48..e16a3861 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp @@ -22,6 +22,18 @@ bool Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco return true; } +bool Translator::V_MAD_I16(const Decoder::Instruction& inst) { + const auto lhs = ReadU16AsU32(inst.src0, true); + const auto rhs = ReadU16AsU32(inst.src1, true); + auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, true)); + if (inst.dst.clamp) { + result = IR::U32(ir.Emit(IR::ValueOpcode::SMax32, {result, IR::Value(0xffff8000u)})); + result = IR::U32(ir.Emit(IR::ValueOpcode::SMin32, {result, IR::Value(0x7fffu)})); + } + Write16Bits(DestinationOperand(inst), ir.BitwiseAnd(result, IR::U32(IR::Value(0xffffu)))); + return true; +} + bool Translator::V_MED3_I16(const Decoder::Instruction& inst) { const auto result = IR::U32(ir.Emit( IR::ValueOpcode::SMedTri32, {ReadU16AsU32(inst.src0, true), ReadU16AsU32(inst.src1, true), diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 733640a8..bdf6cece 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -164,6 +164,7 @@ private: bool FloatCube(const Decoder::Instruction& inst, uint32_t result_kind); bool Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic); bool Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); + bool V_MAD_I16(const Decoder::Instruction& inst); bool V_MED3_I16(const Decoder::Instruction& inst); bool PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index f990bd81..57f18a77 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -328,6 +328,7 @@ bool Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_SUB_NC_U16: case O::V_SUB_NC_I16: return Integer16Binary(inst, IR::ValueOpcode::ISub32, false); case O::V_MUL_LO_U16: return Integer16Binary(inst, IR::ValueOpcode::IMul32, false); + case O::V_MAD_I16: return V_MAD_I16(inst); case O::V_MED3_I16: return V_MED3_I16(inst); case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true); case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index c70784e2..b4ef12ad 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -18205,6 +18205,64 @@ TestCase Vop3MulLoU16CapturedAndSelectors() { return test; } +TestCase Vop3MadI16CapturedSelectorsAndSaturation() { + using O = ShaderOpcode; + struct MadCase { + u32 lhs, rhs, addend, expected; + u32 selectors = 0; + bool clamp = false; + u32 dst = 1; + }; + constexpr u32 a = 0x8000fffdu, b = 0xfffe0005u, c = 0xfff90007u; + const std::array cases{{ + {a, b, c, 0xa5a5fff8u}, // Captured: -3 * 5 + 7 = -8. + {a, b, c, 0x8007beefu, 9}, // Source 0 high; destination high. + {a, b, c, 0xa5a5000du, 2}, // Source 1 high: -3 * -2 + 7. + {a, b, c, 0xa5a5ffeau, 4}, // Source 2 high: -3 * 5 - 7. + {a, b, c, 0xfff9beefu, 15}, // All high: -32768 * -2 - 7 wraps. + {a, b, c, 0xfff8fffdu, 8, false, 9}, // Destination aliases source 0. + {0x7fffu, 1, 1, 0xa5a58000u}, + {0x7fffu, 1, 1, 0x7fffbeefu, 8, true}, // Positive saturation. + {0x8000u, 1, 0xffffu, 0xa5a57fffu}, + {0x8000u, 1, 0xffffu, 0x8000beefu, 8, true}, // Negative saturation. + {0x8000u, 0x8000u, 0xffffu, 0xa5a5ffffu}, + {0x8000u, 0x8000u, 0xffffu, 0xa5a57fffu, 0, true}, + {200, 200, 0xd8f0u, 0xa5a57530u, 0, true}, // 40000 - 10000 = 30000. + {200, 0xff38u, 10000, 0xa5a58ad0u, 0, true}, // -40000 + 10000 = -30000. + }}; + TestCase test; + test.name = "Vop3MadI16CapturedSelectorsAndSaturation"; + for (const auto &entry : cases) { + test.initial.insert(test.initial.end(), {entry.lhs, entry.rhs, entry.addend}); + } + test.expected = test.initial; + auto &code = test.code; + for (u32 i = 0; i < cases.size(); ++i) { + const auto &entry = cases[i]; + u32 offset = i * 12u; + for (u32 reg : {9u, 6u, 15u}) { + AppendVMovU32(&code, 30, offset); + AppendBufferLoadDword(&code, reg, 30); // Runtime inputs prevent folding. + offset += 4u; + } + AppendVMovLiteral(&code, 1, 0xa5a5beefu); + if (i == 0u) { + code.insert(code.end(), {0xd75e0001u, 0x043e0d09u}); + } else { + AppendVop3(&code, 0x35e, entry.dst, Vgpr(9), Vgpr(6), Vgpr(15), 0, + entry.selectors, entry.clamp); + } + AppendStoreVgpr(&code, entry.dst, static_cast(test.initial.size()) + i); + test.expected.push_back(entry.expected); + } + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAD_I16, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"V_MAD_I16", cases.size()}}; + test.required_spirv = {"OpIMul", "OpIAdd"}; + return test; +} + TestCase Vop3Med3I16Captured() { using O = ShaderOpcode; @@ -27038,6 +27096,7 @@ std::vector MakeCases() { AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations); AddCase(Vop3CvtPkI16I32Captured); AddCase(Vop3MulLoU16CapturedAndSelectors); + AddCase(Vop3MadI16CapturedSelectorsAndSaturation); AddCase(Vop3Med3I16Captured); AddCase(Vop2SdwaMinU32PreservesWordDestination); AddCase(VectorShiftCountsMaskLowBits); @@ -32053,6 +32112,11 @@ int main(int argc, char **argv) { RunCase(&vulkan, Vop3MulLoU16CapturedAndSelectors()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--mad-i16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, Vop3MadI16CapturedSelectorsAndSaturation()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--waitcnt-depctr-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, ScalarWaitcntDepctrCapturedVmVsrc());