From 22610cb93ee3c3d9d3ad8a539f796f59d1931f2a Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 28 Oct 2026 22:41:29 +0100 Subject: [PATCH] shader: support unsigned 16-bit multiply-add --- CMakeLists.txt | 2 + .../frontend/decode/ShaderDecoder.h | 1 + .../frontend/decode/VectorAluOps.cpp | 9 +-- .../recompiler/frontend/translate/Integer.cpp | 10 +-- .../frontend/translate/Translator.h | 2 +- .../recompiler/frontend/translate/Vector.cpp | 3 +- tests/ShaderRecompilerComputeTests.cpp | 63 +++++++++++++++++++ 7 files changed, 79 insertions(+), 11 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 46f679a9a..bf5b9d513 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -698,6 +698,8 @@ if(BUILD_TESTING) COMMAND $ --quadmask-only) add_test(NAME shader_recompiler_integer16_ternary COMMAND $ --integer16-ternary-only) + add_test(NAME shader_recompiler_mad_u16 + COMMAND $ --mad-u16-only) add_test(NAME shader_recompiler_alignbyte COMMAND $ --alignbyte-only) add_test(NAME graphics_pipeline_rasterization diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index dc0901abb..1c5835448 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -258,6 +258,7 @@ enum class Opcode { V_CVT_PK_U8_F32, V_MAD_F32, V_MAD_I16, + V_MAD_U16, 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 0d39cb2df..c2ca80ab3 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -380,6 +380,7 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = { {0x311u, Opcode::V_PACK_B32_F16}, {0x314u, Opcode::V_LSHLREV_B16}, {0x319u, Opcode::V_SUBREV_I32}, + {0x340u, Opcode::V_MAD_U16}, {0x345u, Opcode::V_XAD_U32}, {0x346u, Opcode::V_LSHL_ADD_U32}, {0x347u, Opcode::V_ADD_LSHL_U32}, @@ -501,7 +502,7 @@ bool IsNativeVop3B16TernaryOpcode(Opcode opcode) { return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MED3_U16 || - opcode == Opcode::V_MAD_I16; + opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16; } bool IsNativeVop3B16BinaryOpcode(Opcode opcode) { @@ -1536,9 +1537,9 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) { bool SupportsNativeVop3Clamp(Opcode opcode) { return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || - opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MIN3_I16 || - opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 || - opcode == Opcode::V_MED3_U16; + opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 || + opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 || + opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16; } bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, diff --git a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp index 7aa54212a..f45332b5a 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp @@ -20,12 +20,12 @@ void Translator::Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpco Write16Bits(DestinationOperand(inst), result); } -void 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)); +void Translator::Integer16Mad(const Decoder::Instruction& inst, bool sign) { + const auto lhs = ReadU16AsU32(inst.src0, sign); + const auto rhs = ReadU16AsU32(inst.src1, sign); + auto result = ir.IAdd(ir.IMul(lhs, rhs), ReadU16AsU32(inst.src2, sign)); if (inst.dst.clamp) { - result = Saturate16(result, true); + result = Saturate16(result, sign); } Write16Bits(DestinationOperand(inst), result); } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 89eb0c4d0..8940ea769 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -167,7 +167,7 @@ private: void Integer16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic); void Integer16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); void Integer16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool sign); - void V_MAD_I16(const Decoder::Instruction& inst); + void Integer16Mad(const Decoder::Instruction& inst, bool sign); void PackedInteger16Shift(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool arithmetic); void PackedInteger16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 6024e1e48..552f1570e 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -375,7 +375,8 @@ void 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_MAD_I16: return Integer16Mad(inst, true); + case O::V_MAD_U16: return Integer16Mad(inst, false); case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true); case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false); case O::V_MIN3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMinTri32, true); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index a1862b87a..d2a2d0385 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -26752,6 +26752,60 @@ TestCase Vop3F32ClampFlushesNaN() { return test; } +TestCase Vop3MadU16(u32 wave_size) { + using O = ShaderOpcode; + namespace D = ShaderRecompiler::Decoder; + constexpr std::array, 4> inputs{{ + {0xffff0002u, 0x80000003u, 0x00010004u}, + {0xffffffffu, 0xffffffffu, 0xffffffffu}, + {0x0000ffffu, 0x00010001u, 0x00010000u}, + {0x7fff8000u, 0x80007fffu, 0x8000ffffu}}}; + TestCase test; + test.name = wave_size == 32 ? "Vop3MadU16Wave32" : "Vop3MadU16Wave64"; + for (const auto &input : inputs) test.initial.insert(test.initial.end(), input.begin(), input.end()); + test.expected = test.initial; + auto &code = test.code; + for (u32 modifier = 0; modifier < 9; ++modifier) { + const u32 words[] = { + EncodeVop3Word0(0x340, 9, modifier < 3 ? 1u << modifier : 0), + EncodeVop3Word1(Vgpr(2), Vgpr(3), Vgpr(4), modifier >= 6 ? modifier - 5 : 0, + modifier >= 3 && modifier < 6 ? 1u << (modifier - 3) : 0)}; + D::Instruction decoded; + D::DecodeInstruction(words, 0, decoded); + Require(test.name, "integer modifiers", decoded.opcode == O::UNSUPPORTED, + "unsigned 16-bit multiply-add accepted a floating-point modifier"); + } + for (u32 i = 0; i < inputs.size(); ++i) { + for (u32 variant = 0; variant < 32; ++variant) { + const u32 selectors = variant & 15u; + const bool clamp = variant >= 16; + std::array selected{}; + for (u32 operand = 0; operand < 3; ++operand) { + AppendVMovU32(&code, 30, (i * 3 + operand) * sizeof(u32)); + AppendBufferLoadDword(&code, 2 + operand, 30); + selected[operand] = (inputs[i][operand] >> (((selectors >> operand) & 1u) * 16)) & 0xffffu; + } + const u32 dst = selectors % 4 < 3 ? 2 + selectors % 4 : 9; + AppendVMovLiteral(&code, 9, 0xa5a5beefu); + AppendVop3(&code, 0x340, dst, Vgpr(2), Vgpr(3), i == 3 ? 255 : Vgpr(4), 0, + selectors, clamp); + if (i == 3) code.push_back(inputs[i][2]); + AppendStoreVgpr(&code, dst, static_cast(test.expected.size())); + const uint64_t wide = uint64_t{selected[0]} * selected[1] + selected[2]; + const u32 result = clamp && wide > 0xffffu ? 0xffffu : static_cast(wide) & 0xffffu; + const u32 old = dst == 9 ? 0xa5a5beefu : inputs[i][dst - 2]; + test.expected.push_back(selectors & 8u ? (old & 0xffffu) | (result << 16) + : (old & 0xffff0000u) | result); + } + } + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_MAD_U16, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + return test; +} + TestCase Vop3MadI16CapturedSelectorsAndSaturation() { using O = ShaderOpcode; struct MadCase { @@ -46237,6 +46291,8 @@ std::vector MakeCases() { AddCase(Vop3CvtPkI16I32Captured); AddCase(Vop3CvtPk16Saturates); AddCase(Vop3MulLoU16CapturedAndSelectors); + cases.push_back(Vop3MadU16(32)); + cases.push_back(Vop3MadU16(64)); AddCase(Vop3MadI16CapturedSelectorsAndSaturation); AddCase(Vop3F32ClampFlushesNaN); cases.push_back(Vop3Integer16Ternary(32)); @@ -52671,6 +52727,13 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); + if (argc == 2 && std::strcmp(argv[1], "--mad-u16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, Vop3MadU16(32)); + RunCase(&vulkan, Vop3MadU16(64)); + RunCase(&vulkan, Vop3MadI16CapturedSelectorsAndSaturation()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--integer16-ternary-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, Vop3Integer16Ternary(32));