From 2265e8b1cd3de88d2164703d47ec47427c19ffd0 Mon Sep 17 00:00:00 2001 From: milescoviello Date: Tue, 13 Oct 2026 20:37:44 +0000 Subject: [PATCH] fix(shader): decode the high-half result of v_interp_p2_f16 Forspoken (PPSA03984) has a pixel shader with v_interp_p2_f16 v1, ..., v7 op_sel:[0,0,0,1], and main throws "VOP3 interpolation modifiers are not implemented" one second after start. On GFX10, op_sel[3] of a 16-bit VOP3 result selects the high half of the destination and keeps the other half. The decoder already maps that bit to destination.sdwaSel 5 for the other 16-bit VOP3 ops, and write16Bits/writeRawU32 then insert the result into bits 31:16 and keep bits 15:0. v_interp_p2_f16 is the only 16-bit-result interpolation instruction, so only it accepts the bit; other op_sel bits and the bit on the 32-bit-result p1ll/p1lv forms are still refused. agc_vop3_interp_decode checks the decoded selector with and without the bit, and that v_interp_p1ll_f16 still refuses it. In agc_driver_interpolation_f16 the second v_interp_p2_f16 now writes the high half of a register holding 0x00001234, and the GPU result must keep 0x1234 in the low half. Co-Authored-By: Claude Opus 5.5 --- .../libSceAgcDriver/tests/Vop3InterpDecode.cpp | 17 +++++++++++++++++ .../tests/execution/InterpolationF16.cpp | 4 ++-- .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 4 +++- 3 files changed, 22 insertions(+), 3 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp b/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp index f362f67d6a..52027341ca 100644 --- a/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/Vop3InterpDecode.cpp @@ -61,6 +61,7 @@ int main() { {{0xd6000005u, 0x02020500u}, "modifiers are not implemented", "v_interp_p1_f32_e64 with the packed-half selector"}, {{0xd6000105u, 0x02020400u}, "modifiers are not implemented", "v_interp_p1_f32_e64 with an absolute source 0"}, {{0xd6020005u, 0x22000200u}, "modifiers are not implemented", "v_interp_mov_f32_e64 with a negated parameter"}, + {{0xd7424005u, 0x00020201u}, "modifiers are not implemented", "v_interp_p1ll_f16 with the high-half destination selector"}, }; int failures = 0; for (const auto& test : accepted) { @@ -104,5 +105,21 @@ int main() { ++failures; } } + for (const std::uint32_t selector : {0x0u, 0x4000u}) { + const std::vector code{0xd75a0005u | selector, 0x041e0201u, 0xbf810000u}; + try { + const auto decoded = RdnaInstructionDecoder{}.Decode(code); + const auto& instruction = decoded.instructions.at(0); + const std::uint32_t half = selector != 0u ? 5u : 6u; + if (instruction.op != RdnaOpcode::VInterpP2F16 || instruction.destination.reg != Destination || instruction.destination.sdwaSel != half || + instruction.sourceCount != 4u || instruction.source0.reg != 1u || instruction.source1.value != 1u || instruction.source3.reg != 7u) { + std::fprintf(stderr, "v_interp_p2_f16 v5, v1, attr1.x, v7%s decoded to the wrong opcode or operands\n", selector != 0u ? " op_sel:[0,0,0,1]" : ""); + ++failures; + } + } catch (const std::exception& error) { + std::fprintf(stderr, "v_interp_p2_f16 v5, v1, attr1.x, v7%s was refused: %s\n", selector != 0u ? " op_sel:[0,0,0,1]" : "", error.what()); + ++failures; + } + } return failures == 0 ? 0 : 1; } diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp index 8b519385a2..77dbe795c3 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/InterpolationF16.cpp @@ -35,7 +35,7 @@ alignas(256) constexpr std::array VertexCode{ alignas(256) constexpr std::array PixelCode{ 0x7e080f02u, 0x7e0a0f03u, 0xd5430006u, 0x0411ff05u, 0x000000c0u, 0x7e300300u, 0x7e320301u, 0xd742001au, 0x00020000u, 0x7e3602ffu, 0xabcd0000u, 0xd75a001bu, 0x046a0200u, 0xd7420008u, 0x00020100u, 0x7e3802ffu, - 0x12340000u, 0xd75a001cu, 0x04220300u, 0x7e1e02ffu, 0x4500bc00u, 0xd743001du, 0x043e0140u, 0x7e3c0280u, + 0x00001234u, 0xd75a401cu, 0x04220300u, 0x7e1e02ffu, 0x4500bc00u, 0xd743001du, 0x043e0140u, 0x7e3c0280u, 0xd75a001eu, 0x04760340u, 0x7e3e0280u, 0xd75a801fu, 0x046a0200u, 0xd7420020u, 0x00020181u, 0xd7420021u, 0x000201c1u, 0xd7420022u, 0x40020000u, 0xd7430023u, 0x043e0000u, 0xe0782000u, 0x80011806u, 0xe0782010u, 0x80011c06u, 0xe0782020u, 0x80012006u, 0x7e0e02f2u, 0xf800180fu, 0x07070707u, 0xbf810000u, @@ -235,7 +235,7 @@ void Check(std::uint32_t waveSize) { result[1], std::bit_cast(lowX), 0xabcd0000u | HalfOf(SecondStep(Delta(LowX, 2), j, lowX)), - 0x12340000u | HalfOf(SecondStep(Delta(HighX, 2), j, highX)), + 0x1234u | (HalfOf(SecondStep(Delta(HighX, 2), j, highX)) << 16u), std::bit_cast(highY), HalfOf(SecondStep(Delta(HighY, 2), j, highY)), HalfOf(Saturate(SecondStep(Delta(LowX, 2), j, lowX))), diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 1d9c25efe9..18316a6787 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -2090,7 +2090,8 @@ bool isVop3F16InterpolationOpcode(RdnaOpcode opcode) { void decodeVop3F16Interpolation(RdnaInstruction& instruction, std::uint32_t vdst, std::uint32_t src0, std::uint32_t src1, std::uint32_t src2, std::uint32_t abs, std::uint32_t opSel, std::uint32_t clamp, std::uint32_t omod, std::uint32_t neg) { const bool hasSource2 = instruction.op != RdnaOpcode::VInterpP1llF16; const bool f16Result = instruction.op == RdnaOpcode::VInterpP2F16; - if ((abs & 0x1u) != 0u || (neg & 0x1u) != 0u || opSel != 0u || (f16Result && omod != 0u) || (!hasSource2 && ((abs | neg) & 0x4u) != 0u)) { + const std::uint32_t resultHalf = f16Result ? 0x8u : 0x0u; + if ((abs & 0x1u) != 0u || (neg & 0x1u) != 0u || (opSel & ~resultHalf) != 0u || (f16Result && omod != 0u) || (!hasSource2 && ((abs | neg) & 0x4u) != 0u)) { throw std::invalid_argument("VOP3 interpolation modifiers are not implemented"); } if (src1 < 256u || (hasSource2 && src2 < 256u)) { @@ -2100,6 +2101,7 @@ void decodeVop3F16Interpolation(RdnaInstruction& instruction, std::uint32_t vdst instruction.destination = DecodeRdnaVectorGpr(vdst); instruction.destination.clamp = clamp != 0u; instruction.destination.omod = omod; + if ((opSel & resultHalf) != 0u) instruction.destination.sdwaSel = 5u; instruction.source0 = DecodeRdnaVectorGpr(src1 - 256u); instruction.source0.absolute = (abs & 0x2u) != 0u; instruction.source0.negate = (neg & 0x2u) != 0u;