From ff61c1cecd7c19f7f047d24e1fc9df08d60d2d04 Mon Sep 17 00:00:00 2001 From: Mehmet Cambaz <52370708+MehmetCambaz@users.noreply.github.com> Date: Sun, 30 Aug 2026 00:32:49 +0300 Subject: [PATCH] shader: implement V_SIN_F16 (#399) Co-authored-by: nmzik --- .../frontend/decode/ShaderDecoder.h | 1 + .../frontend/decode/VectorAluOps.cpp | 6 +++ .../recompiler/frontend/translate/Float.cpp | 38 ++++++++++----- .../frontend/translate/Translator.h | 2 +- .../recompiler/frontend/translate/Vector.cpp | 3 +- tests/ShaderRecompilerComputeTests.cpp | 48 +++++++++++++++++++ tests/shaderCfgTests.cpp | 16 +++++++ 7 files changed, 99 insertions(+), 15 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 3abaf5cd..0b1926af 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -179,6 +179,7 @@ enum class Opcode { V_CEIL_F16, V_TRUNC_F16, V_RNDNE_F16, + V_SIN_F16, V_COS_F16, V_SIN_F32, V_COS_F32, diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 02b80af8..26f25e0e 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -137,6 +137,7 @@ constexpr OpcodeMap VOP1_OPCODE_LIST[] = { {0x5cu, Opcode::V_CEIL_F16}, {0x5du, Opcode::V_TRUNC_F16}, {0x5eu, Opcode::V_RNDNE_F16}, + {0x60u, Opcode::V_SIN_F16}, {0x61u, Opcode::V_COS_F16}, }; @@ -189,6 +190,7 @@ constexpr OpcodeMap VOP3_ENCODED_VOP1_OPCODE_LIST[] = { {0x5cu, Opcode::V_CEIL_F16}, {0x5du, Opcode::V_TRUNC_F16}, {0x5eu, Opcode::V_RNDNE_F16}, + {0x60u, Opcode::V_SIN_F16}, {0x61u, Opcode::V_COS_F16}, }; @@ -484,6 +486,7 @@ bool IsVop1FloatSourceOpcode(Opcode opcode) { case Opcode::V_CEIL_F16: case Opcode::V_TRUNC_F16: case Opcode::V_RNDNE_F16: + case Opcode::V_SIN_F16: case Opcode::V_COS_F16: case Opcode::V_SIN_F32: case Opcode::V_COS_F32: return true; @@ -551,6 +554,8 @@ constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = { SdwaSelWords() | SdwaSelFull(), false}, {Opcode::V_RNDNE_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), SdwaSelWords() | SdwaSelFull(), false}, + {Opcode::V_SIN_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), + SdwaSelWords() | SdwaSelFull(), true}, {Opcode::V_COS_F16, SdwaSelWords() | SdwaSelFull(), SdwaSelWords(), SdwaSelWords() | SdwaSelFull(), true}, {Opcode::V_CVT_U32_F32, SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(), SdwaSelFull(), false}, @@ -837,6 +842,7 @@ bool IsVop1FloatResultOpcode(Opcode opcode) { case Opcode::V_RSQ_F16: case Opcode::V_LOG_F16: case Opcode::V_EXP_F16: + case Opcode::V_SIN_F16: case Opcode::V_COS_F16: case Opcode::V_SIN_F32: case Opcode::V_COS_F32: return true; diff --git a/src/graphics/shader/recompiler/frontend/translate/Float.cpp b/src/graphics/shader/recompiler/frontend/translate/Float.cpp index ffde1e63..25e29c4f 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Float.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Float.cpp @@ -63,22 +63,34 @@ bool Translator::Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode return true; } -bool Translator::V_COS_F16(const Decoder::Instruction& inst) { +bool Translator::Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode) { const auto argument = ReadF16AsF32(inst.src0); - auto result = IR::F32(ir.Emit(IR::ValueOpcode::FPCos, {argument})); - - // The architectural quarter-cycle result is exactly zero. Evaluating cos(pi/2) with a - // rounded F32 PI can otherwise produce a half-precision subnormal instead. - const auto fraction = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {argument})); - const auto quarter = IR::U1( - ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.25f)})); - const auto three_quarters = IR::U1( - ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.75f)})); - result = IR::F32(ir.Emit(IR::ValueOpcode::SelectF32, - {ir.LogicalOr(quarter, three_quarters), IR::Value::F32(0.0f), result})); - const auto magnitude = ir.BitwiseAnd(ir.BitCastU32(argument), IR::U32(IR::Value(0x7fffffffu))); + auto result = IR::F32(ir.Emit(opcode, {argument})); + if (opcode == IR::ValueOpcode::FPSin) { + const auto fraction = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {argument})); + const auto whole = IR::U1( + ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.0f)})); + const auto half = IR::U1( + ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.5f)})); + const auto nonzero = ir.INotEqual(magnitude, IR::U32(IR::Value(0u))); + const auto cardinal = ir.LogicalOr(ir.LogicalAnd(whole, nonzero), half); + result = IR::F32(ir.Emit( + IR::ValueOpcode::SelectF32, {cardinal, IR::Value::F32(0.0f), result})); + } else { + // The architectural quarter-cycle result is exactly zero. Evaluating cos(pi/2) with a + // rounded F32 PI can otherwise produce a half-precision subnormal instead. + const auto fraction = IR::F32(ir.Emit(IR::ValueOpcode::FPFract32, {argument})); + const auto quarter = IR::U1( + ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.25f)})); + const auto three_quarters = IR::U1( + ir.Emit(IR::ValueOpcode::FPOrdEqual32, {fraction, IR::Value::F32(0.75f)})); + result = IR::F32( + ir.Emit(IR::ValueOpcode::SelectF32, + {ir.LogicalOr(quarter, three_quarters), IR::Value::F32(0.0f), result})); + } + const auto infinite = ir.IEqual(magnitude, IR::U32(IR::Value(0x7f800000u))); result = ApplyF32ResultModifiers(inst.dst, result); const auto bits = PackHalf2x16(result, IR::F32(IR::Value::F32(0.0f))); diff --git a/src/graphics/shader/recompiler/frontend/translate/Translator.h b/src/graphics/shader/recompiler/frontend/translate/Translator.h index 08dc5b41..8ccf1d3c 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translator.h +++ b/src/graphics/shader/recompiler/frontend/translate/Translator.h @@ -149,7 +149,7 @@ private: bool quiet_snan); bool Float16Unary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool invalid_negative); - bool V_COS_F16(const Decoder::Instruction& inst); + bool Float16Trig(const Decoder::Instruction& inst, IR::ValueOpcode opcode); bool Float16Binary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool reverse); bool Float16Ternary(const Decoder::Instruction& inst, IR::ValueOpcode opcode, bool accumulator, bool mix); diff --git a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp index 9c7f2e7c..e46fbfd4 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Vector.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Vector.cpp @@ -365,7 +365,8 @@ bool Translator::EmitVector(const Decoder::Instruction& inst, std::string* error case O::V_CEIL_F16: return Float16Unary(inst, IR::ValueOpcode::FPCeil32, false); case O::V_TRUNC_F16: return Float16Unary(inst, IR::ValueOpcode::FPTrunc32, false); case O::V_RNDNE_F16: return Float16Unary(inst, IR::ValueOpcode::FPRoundEven32, false); - case O::V_COS_F16: return V_COS_F16(inst); + case O::V_SIN_F16: return Float16Trig(inst, IR::ValueOpcode::FPSin); + case O::V_COS_F16: return Float16Trig(inst, IR::ValueOpcode::FPCos); case O::V_MIN3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMinTri32, false, false); case O::V_MAX3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMaxTri32, false, false); case O::V_MED3_F16: return Float16Ternary(inst, IR::ValueOpcode::FPMedTri32, false, false); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 8fdc8c43..f12c7205 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -11896,6 +11896,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::V_RSQ_F16: case Opcode::V_LOG_F16: case Opcode::V_EXP_F16: + case Opcode::V_SIN_F16: case Opcode::V_COS_F16: case Opcode::V_MAD_MIXLO_F16: case Opcode::V_MAD_MIXHI_F16: @@ -14529,6 +14530,52 @@ TestCase VectorCosF16CapturedSdwaAndEdges() { return test; } +TestCase VectorSinF16SdwaAndEdges() { + using O = ShaderOpcode; + + std::vector code; + AppendVMovLiteral(&code, 4, 0x34001234u); // high=+0.25h, low sentinel + code.push_back(0x7e08c0f9u); + code.push_back(0x00051504u); // v_sin_f16 v4.word1, v4.word1 + AppendVMovLiteral(&code, 5, 0x00008000u); // -0.0h + code.push_back(EncodeVop1(0x60, 6, Vgpr(5))); + AppendVMovLiteral(&code, 7, 0x00007bffu); // max finite + code.push_back(EncodeVop1(0x60, 8, Vgpr(7))); + AppendVMovLiteral(&code, 9, 0x0000fbffu); // minimum finite + code.push_back(EncodeVop1(0x60, 10, Vgpr(9))); + AppendVMovLiteral(&code, 11, 0x00007c00u); // +inf + code.push_back(EncodeVop1(0x60, 12, Vgpr(11))); + AppendVMovLiteral(&code, 13, 0x0000fc00u); // -inf + code.push_back(EncodeVop1(0x60, 14, Vgpr(13))); + AppendVMovLiteral(&code, 15, 0x00003800u); // +0.5h + code.push_back(EncodeVop1(0x60, 16, Vgpr(15))); + AppendVMovLiteral(&code, 17, 0x00003a00u); // +0.75h + code.push_back(EncodeVop1(0x60, 18, Vgpr(17))); + AppendVMovLiteral(&code, 19, 0x00003400u); // +0.25h + AppendVop3(&code, 0x1e0, 20, Vgpr(19), 0); + for (u32 i = 0; i < 9; i++) { + AppendStoreVgpr(&code, 4 + i * 2, i); + } + AppendEnd(&code); + + TestCase test{"VectorSinF16SdwaAndEdges", + code, + {}, + {0x3c001234u, 0x00008000u, 0x00000000u, 0x00000000u, + 0x0000fe00u, 0x0000fe00u, 0x00000000u, 0x0000bc00u, + 0x00003c00u}, + {O::V_MOV_B32, O::V_SIN_F16, O::BUFFER_STORE_DWORD, + O::S_ENDPGM}}; + test.decoded_counts = {{"V_SIN_F16", 9}, + {"V_SIN_F16 v4.sdwa(sel=5,sext=0), " + "v4.sdwa(sel=5,sext=0)", + 1}}; + test.ir_counts = {{" = FPSin ", 9}}; + test.required_spirv = {" Fract ", " Sin ", " PackHalf2x16 "}; + test.forbidden_spirv = {"OpCapability Float16"}; + return test; +} + TestCase VectorWritelaneIgnoresExecMask() { using O = ShaderOpcode; @@ -20729,6 +20776,7 @@ std::vector MakeCases() { AddCase(VectorMinMaxMed3F16Ops); AddCase(VectorSpecialF16Ops); AddCase(VectorCosF16CapturedSdwaAndEdges); + AddCase(VectorSinF16SdwaAndEdges); AddCase(VectorWritelaneIgnoresExecMask); AddCase(VectorReadlaneFromInactiveWrittenLane); AddCase(VectorLaneWave32RuntimeSelectorWraps); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index ea052569..8fa26455 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -4047,6 +4047,22 @@ void TestNewShaderDecoderArchitecture() { !cos_f16.src0.absolute, "decoder rejected the captured VOP1 SDWA V_COS_F16 instruction"); + const uint32_t sin_f16_code[] = {0x7e08c0f9u, 0x00051504u}; + Instruction sin_f16; + Check(DecodeInstruction(sin_f16_code, 0u, sin_f16, &error), error.c_str()); + Check(sin_f16.family == Family::VOP1 && + sin_f16.opcode == Opcode::V_SIN_F16 && + sin_f16.word_count == 2u && sin_f16.src_count == 1u && + sin_f16.dst.kind == OperandKind::Vgpr && sin_f16.dst.reg == 4u && + sin_f16.dst.sdwa_sel == 5u && + sin_f16.dst.sdwa_dst_unused == 2u && !sin_f16.dst.clamp && + sin_f16.dst.omod == 0u && + sin_f16.src0.kind == OperandKind::Vgpr && + sin_f16.src0.reg == 4u && sin_f16.src0.sdwa_sel == 5u && + !sin_f16.src0.sdwa_sext && !sin_f16.src0.negate && + !sin_f16.src0.absolute, + "decoder rejected the VOP1 SDWA V_SIN_F16 instruction"); + const uint32_t literal_code[] = {EncodeVop1(0x01, 2, 255u), 0x12345678u}; Instruction literal; Check(DecodeInstruction(literal_code, 0u, literal, &error), error.c_str());