diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index 6792e1415..887015479 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -566,6 +566,7 @@ std::string OpcodeToString(Opcode opcode) { case Opcode::VCvtI16F16: return "v_cvt_i16_f16"; 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::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 64edb13f9..21c06ee78 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -151,6 +151,7 @@ enum class Opcode { VCvtI16F16, VCvtRpiI32F32, VCvtFlrI32F32, + VFrexpExpI32F32, VCvtOffF32I4, VCvtF32Ubyte0, VCvtF32Ubyte1, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 826cf6338..b1ddb635f 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -120,6 +120,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x38u, Opcode::VBfrevB32}, {0x39u, Opcode::VFfbhU32}, {0x3au, Opcode::VFfblB32}, + {0x3fu, Opcode::VFrexpExpI32F32}, {0x42u, Opcode::VMovreldB32}, {0x43u, Opcode::VMovrelsB32}, {0x50u, Opcode::VCvtF16U16}, @@ -168,6 +169,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x38u, Opcode::VBfrevB32}, {0x39u, Opcode::VFfbhU32}, {0x3au, Opcode::VFfblB32}, + {0x3fu, Opcode::VFrexpExpI32F32}, {0x42u, Opcode::VMovreldB32}, {0x43u, Opcode::VMovrelsB32}, {0x50u, Opcode::VCvtF16U16}, @@ -410,6 +412,7 @@ bool IsVop1FloatSourceOpcode(Opcode opcode) { case Opcode::VCvtI32F32: case Opcode::VCvtRpiI32F32: case Opcode::VCvtFlrI32F32: + case Opcode::VFrexpExpI32F32: case Opcode::VCvtF16F32: case Opcode::VRcpF32: case Opcode::VRcpIflagF32: diff --git a/src/graphics/shader/recompiler/frontend/translate/Convert.cpp b/src/graphics/shader/recompiler/frontend/translate/Convert.cpp index 498692eef..7ef1a4584 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Convert.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Convert.cpp @@ -109,6 +109,23 @@ bool Translator::TranslateConversion(const IR::Instruction& inst) { WriteOperand(inst.dst, convert_i32(rounded)); return true; } + case IR::Opcode::FrexpExpI32F32: { + const auto bits = ir.BitCastU32(f32(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 normal = ir.ISub(exponent, IR::U32(IR::Value(126u))); + const auto msb = IR::U32(ir.Emit(IR::ValueOpcode::FindUMsb32, {mantissa})); + const auto subnormal = ir.ISub(msb, IR::U32(IR::Value(148u))); + const auto denormal = ir.Select(ir.INotEqual(mantissa, IR::U32(IR::Value(0u))), + subnormal, IR::U32(IR::Value(0u))); + const auto finite = ir.Select( + ir.INotEqual(exponent, IR::U32(IR::Value(0xffu))), + ir.Select(ir.INotEqual(exponent, IR::U32(IR::Value(0u))), normal, denormal), + IR::U32(IR::Value(0u))); + WriteOperand(inst.dst, finite); + return true; + } case IR::Opcode::ConvertI4ToOffsetF32: { const auto nibble = IR::U32( ir.Emit(IR::ValueOpcode::BitFieldSExtract, {u32(0), IR::Value(0u), IR::Value(4u)})); diff --git a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp index 1bc913513..cedb170bf 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp +++ b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.cpp @@ -103,6 +103,7 @@ constexpr LowerMap LOWER_OPS[] = { {Decoder::Opcode::VCvtI16F16, Opcode::ConvertF16ToI16}, {Decoder::Opcode::VCvtRpiI32F32, Opcode::ConvertRoundPlusInfF32ToI32}, {Decoder::Opcode::VCvtFlrI32F32, Opcode::ConvertFloorF32ToI32}, + {Decoder::Opcode::VFrexpExpI32F32, Opcode::FrexpExpI32F32}, {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 f4cc378e2..bd32ab445 100644 --- a/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc +++ b/src/graphics/shader/recompiler/ir/opcodes/ShaderIROpcodes.inc @@ -213,6 +213,7 @@ IR_OPCODE(ConvertI16ToF16, General, Conversion) IR_OPCODE(ConvertF16ToI16, General, Conversion) IR_OPCODE(ConvertRoundPlusInfF32ToI32, General, Conversion) IR_OPCODE(ConvertFloorF32ToI32, General, Conversion) +IR_OPCODE(FrexpExpI32F32, General, Conversion) 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 a0c71f2b7..1fabcd65c 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -13527,6 +13527,44 @@ TestCase VectorFloatConversionOps() { O::SEndpgm}}; } +TestCase VectorFrexpExpI32F32Edges() { + using O = ShaderOpcode; + + const std::vector inputs = { + 0x00000000u, 0x80000000u, 0x3f000000u, 0xbf800000u, + 0x40800000u, 0xc1000000u, 0x00800000u, 0x007fffffu, + 0x00000001u, 0x7f800000u, 0x7fc00000u, + }; + std::vector code; + for (u32 i = 0; i < static_cast(inputs.size()); i++) { + AppendVMovU32(&code, 31, i * sizeof(u32)); + 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) { + 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))); + } + AppendStoreVgpr(&code, dst, i); + } + AppendEnd(&code); + + TestCase test; + test.name = "VectorFrexpExpI32F32Edges"; + 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"}; + return test; +} + TestCase CvtF32ToIntSaturatesNaNAndOutOfRange() { using O = ShaderOpcode; @@ -17842,6 +17880,7 @@ std::vector MakeCases() { AddCase(VectorMinMaxF32NanAndSignedZeroEdges); AddCase(VectorMed3F32NanUsesMin3Path); AddCase(VectorFloatConversionOps); + AddCase(VectorFrexpExpI32F32Edges); AddCase(CvtF32ToIntSaturatesNaNAndOutOfRange); AddCase(VectorSpecialF32FlushesDenormalInputs); AddCase(VectorRcpIflagF32IntegerReciprocal);