From 54b937c85faefffbbb7850105d1a41276b5a3e1e Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 28 Oct 2026 22:49:21 +0100 Subject: [PATCH] shader: support high products of 24-bit integers --- CMakeLists.txt | 2 + .../frontend/decode/ShaderDecoder.h | 2 + .../frontend/decode/VectorAluOps.cpp | 9 +- .../recompiler/frontend/translate/Integer.cpp | 6 +- .../frontend/translate/Translator.h | 2 +- .../recompiler/frontend/translate/Vector.cpp | 2 + tests/ShaderRecompilerComputeTests.cpp | 109 ++++++++++++++++++ 7 files changed, 128 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index bf5b9d513..1b1cfa43e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -700,6 +700,8 @@ if(BUILD_TESTING) COMMAND $ --integer16-ternary-only) add_test(NAME shader_recompiler_mad_u16 COMMAND $ --mad-u16-only) + add_test(NAME shader_recompiler_mul_hi24 + COMMAND $ --mul-hi24-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 1c5835448..6f34d647d 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -249,7 +249,9 @@ enum class Opcode { V_MIN_I32, V_MAX_I32, V_MUL_I32_I24, + V_MUL_HI_I32_I24, V_MUL_U32_U24, + V_MUL_HI_U32_U24, V_MUL_LO_U32, V_MUL_HI_U32, V_MUL_LO_I32, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index c2ca80ab3..f43aa605c 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -35,7 +35,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = { {0x05u, Opcode::V_SUBREV_F32}, {0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32}, {0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination}, + {0x0au, Opcode::V_MUL_HI_I32_I24, Vop2SdwaProfile::IntegerPartialDestination}, {0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination}, + {0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination}, {0x0fu, Opcode::V_MIN_F32}, {0x10u, Opcode::V_MAX_F32}, {0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination}, @@ -1539,7 +1541,8 @@ bool SupportsNativeVop3Clamp(Opcode opcode) { return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || 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; + opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16 || + opcode == Opcode::V_MUL_HI_I32_I24 || opcode == Opcode::V_MUL_HI_U32_U24; } bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, @@ -1570,6 +1573,10 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi return clamp != 0u || omod != 0u || neg != 0u; } switch (opcode) { + case Opcode::V_MUL_HI_I32_I24: + case Opcode::V_MUL_HI_U32_U24: + // The high product fits in 32 bits, so clamp has no effect. + return abs != 0u || op_sel != 0u || omod != 0u || neg != 0u; case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u; case Opcode::V_CVT_F32_UBYTE0: case Opcode::V_CVT_F32_UBYTE1: diff --git a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp index f45332b5a..dfc390658 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Integer.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Integer.cpp @@ -269,7 +269,7 @@ void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) { WriteOperand(DestinationOperand(inst), result); } -void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) { +void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high) { const auto extract24 = [&](IR::U32 value) { return IR::U32( ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract, @@ -277,7 +277,9 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add }; const auto lhs = extract24(ReadU32(inst.src0)); const auto rhs = extract24(ReadU32(inst.src1)); - auto result = ir.IMul(lhs, rhs); + const auto multiply = high ? (sign ? IR::ValueOpcode::SMulHi : IR::ValueOpcode::UMulHi) + : IR::ValueOpcode::IMul32; + auto result = IR::U32(ir.Emit(multiply, {lhs, rhs})); if (addend) { result = ir.IAdd(result, ReadU32(inst.src2)); } diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 8940ea769..c30aa53fa 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -187,7 +187,7 @@ private: void V_XOR3_B32(const Decoder::Instruction& inst); void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign); void V_FFBH_32(const Decoder::Instruction& inst, bool sign); - void Integer24(const Decoder::Instruction& inst, bool sign, bool addend); + void Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high = false); void V_MAD_U64_U32(const Decoder::Instruction& inst); void V_SAD_U32(const Decoder::Instruction& inst); void V_ADD3_U32(const Decoder::Instruction& inst); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 552f1570e..180e71c61 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -586,6 +586,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) { case O::V_MAD_U32_U24: return Integer24(inst, false, true); case O::V_MUL_I32_I24: return Integer24(inst, true, false); case O::V_MUL_U32_U24: return Integer24(inst, false, false); + case O::V_MUL_HI_I32_I24: return Integer24(inst, true, false, true); + case O::V_MUL_HI_U32_U24: return Integer24(inst, false, false, true); case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst); case O::V_SAD_U32: return V_SAD_U32(inst); case O::V_ADD3_U32: return V_ADD3_U32(inst); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index d2a2d0385..202db0460 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -26425,6 +26425,102 @@ TestCase Vop2SdwaMinI32CapturedByte0(u32 wave_size) { return test; } +TestCase VectorMulHi24(u32 wave_size) { + using O = ShaderOpcode; + namespace D = ShaderRecompiler::Decoder; + constexpr std::array lhs{0x7f7fffffu, 0xa5800000u, 0x12ffffffu, 0xab000000u, + 0x00010000u, 0xffabcdefu, 0x80000001u, 0x7fffffffu}; + constexpr std::array rhs{0x00800000u, 0xcd800000u, 0x55000001u, 0x99ffffffu, + 0xab010000u, 0x00ffffffu, 0xfffffffeu, 0xffffffffu}; + TestCase test; + test.name = wave_size == 32 ? "VectorMulHi24Wave32" : "VectorMulHi24Wave64"; + for (u32 lane = 0; lane < wave_size; ++lane) test.initial.push_back(lhs[lane % lhs.size()]); + for (u32 lane = 0; lane < wave_size; ++lane) test.initial.push_back(rhs[lane % rhs.size()]); + test.expected = test.initial; + auto &code = test.code; + code.push_back(EncodeVop2(0x1a, 30, InlineU32(2), 0)); + AppendBufferLoadDword(&code, 2, 30); + AppendVMovU32(&code, 20, wave_size * sizeof(u32)); + code.push_back(EncodeVop2(0x25, 30, Vgpr(20), 30)); + AppendBufferLoadDword(&code, 3, 30); + for (bool sign : {true, false}) { + const u32 opcode = sign ? 0x0a : 0x0c; + for (u32 modifier = 0; modifier < 13; ++modifier) { + const u32 words[] = { + EncodeVop3Word0(0x100 + opcode, 7, modifier < 3 ? 1u << modifier : 0, + modifier >= 9 ? 1u << (modifier - 9) : 0), + EncodeVop3Word1(Vgpr(5), Vgpr(6), 0, + modifier >= 6 && modifier < 9 ? 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, + "24-bit high multiply accepted unsupported modifiers"); + } + for (u32 variant = 0; variant < 10; ++variant) { + code.push_back(EncodeVop1(0x01, 5, Vgpr(2))); + code.push_back(EncodeVop1(0x01, 6, Vgpr(3))); + AppendVMovLiteral(&code, 7, 0xa5a5beefu); + u32 dst = 7; + switch (variant) { + case 0: dst = 5; code.push_back(EncodeVop2(opcode, dst, Vgpr(5), 6)); break; + case 1: dst = 6; AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), Vgpr(6)); break; + case 2: AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), Vgpr(6), 0, 0, 0, true); break; + case 3: + code.push_back(EncodeVop2(opcode, dst, 255, 6)); + code.push_back(0x00ffffffu); + break; + case 4: + AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), 255); + code.push_back(0x00800000u); + break; + case 5: + AppendSMovLiteral(&code, 20, 0xffff0001u); + AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), 20); + break; + case 6: + case 7: + case 8: + code.push_back(EncodeVop2(opcode, dst, 249, 6)); + code.push_back(EncodeVop2Sdwa(5, variant == 7 ? 5 : 6, variant == 7 ? 2 : 0, + variant == 8 ? 4 : 6, 6, variant == 8)); + break; + case 9: + code.push_back(EncodeVop2(opcode, dst, 250, 6)); + code.push_back(EncodeVop2Dpp(5, 0x01b)); // Reverse each quad's source lanes. + break; + } + AppendStoreVgprAtLaneDwordOffset(&code, dst, 0, static_cast(test.expected.size())); + for (u32 lane = 0; lane < wave_size; ++lane) { + const u32 source_lane = variant == 9 ? lane ^ 3u : lane; + u32 a = lhs[source_lane % lhs.size()], b = rhs[lane % rhs.size()]; + if (variant == 3) a = 0x00ffffffu; + if (variant == 4) b = 0x00800000u; + if (variant == 5) b = 0xffff0001u; + if (variant == 8) a = (a & 0x8000u) ? a | 0xffff0000u : a & 0xffffu; + const auto extend24 = [sign](u32 value) -> int64_t { + const int64_t low = value & 0x00ffffffu; + return sign && (value & 0x00800000u) ? low - 0x01000000 : low; + }; + const uint64_t product = static_cast(extend24(a) * extend24(b)); + const u32 high = static_cast(product >> 32); + test.expected.push_back(variant == 7 ? 0xbeefu | (high << 16) : high); + } + } + } + AppendEnd(&code); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32, + O::BUFFER_LOAD_DWORD, O::V_MUL_HI_I32_I24, O::V_MUL_HI_U32_U24, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.compute_info.wave_size = wave_size; + test.compute_info.threads_num[0] = wave_size; + test.compute_info.threads_num[1] = 1; + test.compute_info.threads_num[2] = 1; + test.compute_info.thread_ids_num = 1; + test.has_compute_info = true; + return test; +} + TestCase Vop2SdwaMulI24Destinations(u32 wave_size) { using O = ShaderOpcode; constexpr std::array sources{ @@ -46282,6 +46378,8 @@ std::vector MakeCases() { cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64)); cases.push_back(Vop2SdwaMinI32CapturedByte0(32)); cases.push_back(Vop2SdwaMinI32CapturedByte0(64)); + cases.push_back(VectorMulHi24(32)); + cases.push_back(VectorMulHi24(64)); cases.push_back(Vop2SdwaMulI24Destinations(32)); cases.push_back(Vop2SdwaMulI24Destinations(64)); cases.push_back(Vop2SdwaMulU24Destinations(32)); @@ -52727,6 +52825,17 @@ int main(int argc, char **argv) { std::setvbuf(stdout, nullptr, _IONBF, 0); EnsureConfigInitialized(); CheckLeastRecentlyUsedCacheOrdering(); + if (argc == 2 && std::strcmp(argv[1], "--mul-hi24-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, VectorMulHi24(32)); + RunCase(&vulkan, VectorMulHi24(64)); + RunCase(&vulkan, Vop2SdwaMulI24Destinations(32)); + RunCase(&vulkan, Vop2SdwaMulI24Destinations(64)); + RunCase(&vulkan, Vop2SdwaMulU24Destinations(32)); + RunCase(&vulkan, Vop2SdwaMulU24Destinations(64)); + RunCase(&vulkan, VectorVop3IntegerOps()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--mad-u16-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, Vop3MadU16(32));