From 330889e50bd335da082855d40a07bc97ba10e620 Mon Sep 17 00:00:00 2001 From: nmzik Date: Mon, 17 Aug 2026 07:58:56 +0200 Subject: [PATCH] shader: implement RDNA2 frexp mantissa --- .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../frontend/decode/VectorAluOps.cpp | 4 ++ .../recompiler/frontend/translate/Float.cpp | 20 ++++++++ .../recompiler/ir/opcodes/ShaderIROpcodes.cpp | 1 + .../recompiler/ir/opcodes/ShaderIROpcodes.inc | 1 + tests/ShaderRecompilerComputeTests.cpp | 49 +++++++++++++------ 7 files changed, 61 insertions(+), 16 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index 887015479..12d21163f 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -567,6 +567,7 @@ std::string OpcodeToString(Opcode opcode) { case Opcode::VCvtRpiI32F32: return "v_cvt_rpi_i32_f32"; case Opcode::VCvtFlrI32F32: return "v_cvt_flr_i32_f32"; case Opcode::VFrexpExpI32F32: return "v_frexp_exp_i32_f32"; + case Opcode::VFrexpMantF32: return "v_frexp_mant_f32"; case Opcode::VCvtOffF32I4: return "v_cvt_off_f32_i4"; case Opcode::VCvtF32Ubyte0: return "v_cvt_f32_ubyte0"; case Opcode::VCvtF32Ubyte1: return "v_cvt_f32_ubyte1"; diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 21c06ee78..22e88b603 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -152,6 +152,7 @@ enum class Opcode { VCvtRpiI32F32, VCvtFlrI32F32, VFrexpExpI32F32, + VFrexpMantF32, VCvtOffF32I4, VCvtF32Ubyte0, VCvtF32Ubyte1, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index b1ddb635f..f4c621f71 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -121,6 +121,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x39u, Opcode::VFfbhU32}, {0x3au, Opcode::VFfblB32}, {0x3fu, Opcode::VFrexpExpI32F32}, + {0x40u, Opcode::VFrexpMantF32}, {0x42u, Opcode::VMovreldB32}, {0x43u, Opcode::VMovrelsB32}, {0x50u, Opcode::VCvtF16U16}, @@ -170,6 +171,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x39u, Opcode::VFfbhU32}, {0x3au, Opcode::VFfblB32}, {0x3fu, Opcode::VFrexpExpI32F32}, + {0x40u, Opcode::VFrexpMantF32}, {0x42u, Opcode::VMovreldB32}, {0x43u, Opcode::VMovrelsB32}, {0x50u, Opcode::VCvtF16U16}, @@ -413,6 +415,7 @@ bool IsVop1FloatSourceOpcode(Opcode opcode) { case Opcode::VCvtRpiI32F32: case Opcode::VCvtFlrI32F32: case Opcode::VFrexpExpI32F32: + case Opcode::VFrexpMantF32: case Opcode::VCvtF16F32: case Opcode::VRcpF32: case Opcode::VRcpIflagF32: @@ -767,6 +770,7 @@ bool IsVop1FloatResultOpcode(Opcode opcode) { case Opcode::VCeilF32: case Opcode::VRndneF32: case Opcode::VFloorF32: + case Opcode::VFrexpMantF32: case Opcode::VExpF32: case Opcode::VLogF32: case Opcode::VRsqF32: diff --git a/src/graphics/shader/recompiler/frontend/translate/Float.cpp b/src/graphics/shader/recompiler/frontend/translate/Float.cpp index f55cb1527..76f7d1a42 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Float.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Float.cpp @@ -93,6 +93,26 @@ bool Translator::TranslateFloat16Operation(const IR::Instruction& inst) { } bool Translator::TranslateFloatOperation(const IR::Instruction& inst) { + if (inst.op == IR::Opcode::FrexpMantF32) { + const auto bits = ReadU32(inst.src[0]); + const auto exponent = IR::U32( + ir.Emit(IR::ValueOpcode::BitFieldUExtract, {bits, IR::Value(23u), IR::Value(8u)})); + const auto mantissa = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x007fffffu))); + const auto sign = ir.BitwiseAnd(bits, IR::U32(IR::Value(0x80000000u))); + const auto base = ir.BitwiseOr(sign, IR::U32(IR::Value(0x3f000000u))); + const auto normal = ir.BitwiseOr(base, mantissa); + const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {mantissa})); + const auto shift = ir.ISub(IR::U32(IR::Value(23u)), msb); + const auto fraction = + ir.BitwiseAnd(ir.ShiftLeftLogical(mantissa, shift), IR::U32(IR::Value(0x007fffffu))); + const auto subnormal = ir.BitwiseOr(base, fraction); + const auto zero = ir.IEqual(mantissa, IR::U32(IR::Value(0u))); + const auto finite = ir.Select(ir.INotEqual(exponent, IR::U32(IR::Value(0u))), normal, + ir.Select(zero, bits, subnormal)); + const auto result = ir.Select(ir.IEqual(exponent, IR::U32(IR::Value(0xffu))), bits, finite); + WriteOperand(inst.dst, ir.BitCastF32(result)); + return true; + } IR::ValueOpcode opcode {}; switch (inst.op) { case IR::Opcode::RcpF32: opcode = IR::ValueOpcode::FPRecip32; break; diff --git a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp index cedb170bf..44c0bed45 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp @@ -104,6 +104,7 @@ constexpr LowerMap LOWER_OPS[] = { {Decoder::Opcode::VCvtRpiI32F32, Opcode::ConvertRoundPlusInfF32ToI32}, {Decoder::Opcode::VCvtFlrI32F32, Opcode::ConvertFloorF32ToI32}, {Decoder::Opcode::VFrexpExpI32F32, Opcode::FrexpExpI32F32}, + {Decoder::Opcode::VFrexpMantF32, Opcode::FrexpMantF32}, {Decoder::Opcode::VCvtOffF32I4, Opcode::ConvertI4ToOffsetF32}, {Decoder::Opcode::VRcpF32, Opcode::RcpF32}, {Decoder::Opcode::VRcpIflagF32, Opcode::RcpIflagF32}, diff --git a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc index bd32ab445..5738c4e77 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc @@ -214,6 +214,7 @@ IR_OPCODE(ConvertF16ToI16, General, Conversion) IR_OPCODE(ConvertRoundPlusInfF32ToI32, General, Conversion) IR_OPCODE(ConvertFloorF32ToI32, General, Conversion) IR_OPCODE(FrexpExpI32F32, General, Conversion) +IR_OPCODE(FrexpMantF32, General, Float) IR_OPCODE(ConvertI4ToOffsetF32, General, Conversion) IR_OPCODE(LdexpF32, General, Float) IR_OPCODE(PackF32ToF16Rtz, General, Conversion) diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 1fabcd65c..64f8373b5 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -13527,13 +13527,13 @@ TestCase VectorFloatConversionOps() { O::SEndpgm}}; } -TestCase VectorFrexpExpI32F32Edges() { +TestCase VectorFrexpF32Edges() { using O = ShaderOpcode; const std::vector inputs = { - 0x00000000u, 0x80000000u, 0x3f000000u, 0xbf800000u, - 0x40800000u, 0xc1000000u, 0x00800000u, 0x007fffffu, - 0x00000001u, 0x7f800000u, 0x7fc00000u, + 0x00000000u, 0x80000000u, 0x3f000000u, 0xbf800000u, 0x40800000u, + 0xc1000000u, 0x00800000u, 0x007fffffu, 0x00000001u, 0x7f800000u, + 0x7fc00000u, 0xff800000u, 0x7fa00001u, }; std::vector code; for (u32 i = 0; i < static_cast(inputs.size()); i++) { @@ -13541,27 +13541,44 @@ TestCase VectorFrexpExpI32F32Edges() { AppendBufferLoadDword(&code, i == 3u ? 23u : i, 31); } for (u32 i = 0; i < static_cast(inputs.size()); i++) { - const auto dst = 16u + i; - if (dst == 19u) { + const auto exponent_dst = i == 3u ? 19u : 32u + i; + const auto mantissa_dst = i == 3u ? 21u : 48u + i; + if (i == 3u) { code.push_back(0x7e267ef9u); // captured v_frexp_exp_i32_f32 v19, abs(v23) code.push_back(0x00260617u); } else { - code.push_back(EncodeVop1(0x3f, dst, Vgpr(i))); + code.push_back(EncodeVop1(0x3f, exponent_dst, Vgpr(i))); } - AppendStoreVgpr(&code, dst, i); + AppendStoreVgpr(&code, exponent_dst, i); + if (i == 3u) { + code.push_back(0x7e2a80f9u); // captured v_frexp_mant_f32 v21, abs(v23) + code.push_back(0x00260617u); + } else { + code.push_back(EncodeVop1(0x40, mantissa_dst, Vgpr(i))); + } + AppendStoreVgpr(&code, mantissa_dst, inputs.size() + i); } AppendEnd(&code); TestCase test; - test.name = "VectorFrexpExpI32F32Edges"; + test.name = "VectorFrexpF32Edges"; test.code = std::move(code); test.initial = inputs; - test.expected = {0u, 0u, 0u, 1u, 3u, 4u, - 0xffffff83u, 0xffffff82u, 0xffffff6cu, 0u, 0u}; - test.opcodes = {O::VMovB32, O::BufferLoadDword, O::VFrexpExpI32F32, - O::BufferStoreDword, O::SEndpgm}; - test.decoded_counts = {{"v_frexp_exp_i32_f32", inputs.size()}}; - test.required_spirv = {"FindUMsb", "OpBitFieldUExtract"}; + test.initial.resize(inputs.size() * 2u); + test.expected = { + 0u, 0u, 0u, 1u, 3u, + 4u, 0xffffff83u, 0xffffff82u, 0xffffff6cu, 0u, + 0u, 0u, 0u, 0x00000000u, 0x80000000u, + 0x3f000000u, 0x3f000000u, 0x3f000000u, 0xbf000000u, 0x3f000000u, + 0x3f7ffffeu, 0x3f000000u, 0x7f800000u, 0x7fc00000u, 0xff800000u, + 0x7fa00001u, + }; + test.opcodes = {O::VMovB32, O::BufferLoadDword, O::VFrexpExpI32F32, + O::VFrexpMantF32, O::BufferStoreDword, O::SEndpgm}; + test.decoded_counts = {{"v_frexp_exp_i32_f32", inputs.size()}, + {"v_frexp_mant_f32", inputs.size()}}; + test.required_spirv = {"FindUMsb", "OpBitFieldUExtract", + "OpShiftLeftLogical"}; return test; } @@ -17880,7 +17897,7 @@ std::vector MakeCases() { AddCase(VectorMinMaxF32NanAndSignedZeroEdges); AddCase(VectorMed3F32NanUsesMin3Path); AddCase(VectorFloatConversionOps); - AddCase(VectorFrexpExpI32F32Edges); + AddCase(VectorFrexpF32Edges); AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange); AddCase(VectorSpecialF32FlushesDenormalInputs); AddCase(VectorRcpIflagF32IntegerReciprocal);