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 <noreply@anthropic.com>
This commit is contained in:
milescovielloandClaude Opus 5.5 committed 2026-10-13 20:37:44 +00:00
1 parent 0aa4cb1c2f
commit 2265e8b1cd
3 files changed
+22 -3

No files matched your search

@@ -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<std::uint32_t> 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;
}
@@ -35,7 +35,7 @@ alignas(256) constexpr std::array<std::uint32_t, 10> VertexCode{
alignas(256) constexpr std::array<std::uint32_t, 47> 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<std::uint32_t>(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<std::uint32_t>(highY),
HalfOf(SecondStep(Delta(HighY, 2), j, highY)),
HalfOf(Saturate(SecondStep(Delta(LowX, 2), j, lowX))),
@@ -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;