Skip to content

feat(shader): implement v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 - #697

Merged
adriadam10 merged 4 commits into
boykopovar:mainfrom
queeeef:feat/interp-f16
Oct 8, 2026
Merged

adriadam10 merged 4 commits into
boykopovar:mainfrom
queeeef:feat/interp-f16

Conversation

@queeeef

@queeeef queeeef commented Oct 5, 2026

Copy link
Copy Markdown
Contributor

What

Implements the three VOP3 16-bit interpolation instructions, the last unimplemented VOP3 opcodes on the progress page (shaders 1120 → 1123 of 1166).

  • Decoder: opcodes 0x342, 0x343 and 0x35a. The src0 field carries the attribute (bits 0-5), channel (6-7) and high-half select (8); the I/J VGPR is in src1; P0 (p1lv) or the first step's result (p2) is in src2. Field layout from LLVM's VOP3Interp_gfx10 and AMD's machine-readable RDNA2 ISA; encodings cross-checked with llvm-mc -mcpu=gfx1030. Modifiers LLVM does not encode (src0-slot abs/neg, op_sel, omod on the f16 result) and non-VGPR sources throw.
  • Translation: a new GetInterpolationParameterF16 IR value reads the selected 16-bit half of each vertex's parameter through VK_KHR_fragment_shader_barycentric. P10/P20 are the vertex differences rounded to f16. Each step is a multiply and an add, as in v_interp_p1_f32/v_interp_p2_f32. P2 writes through writeF16, which keeps the destination's high 16 bits.
  • Pixel inputs: FP16_INTERP_MODE, USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1 from SPI_PS_INPUT_CNTL are now read. An input whose low half is defaulted is still declared at its slot when its high half is read; before this, emission refused it ("reads a default value, not a parameter").
  • Throws: without barycentrics (fixed-function interpolation cannot split a 32-bit attribute), on an input without FP16_INTERP_MODE, and on pass-through inputs. All of this is recorded in TechnicalDebt.

Related, independent: #651 produces the high-half interpolant mapping these instructions read.

Tested

Linux (Ubuntu 24.04, GCC 13.3), lavapipe (Mesa 25.2). Full ctest: 289/289 pass.

  • agc_shader_memory: new verifyF16PixelParameterSlots covers which inputs are declared per vertex for high- and low-half reads with each half defaulted.
  • New agc_driver_interpolation_f16:
    • Offline: recompiles the test shader, and checks that the instructions throw without barycentrics, on a 32-bit input, on a pass-through input, and on five invalid encodings.
    • On a device with fragmentShaderBarycentric: draws triangles with distinct packed-f16 vertex parameters and compares every covered pixel bit for bit against the ISA formula (both halves, p1lv with both S2 halves, clamp, a negated I, defaulted high halves, preserved destination high bits, and an f16-rounded delta).
  • Every new check was made to fail first by reverting the matching change.
  • The emitted SPIR-V passes spirv-val --target-env vulkan1.1.

Not tested: lavapipe has no VK_KHR_fragment_shader_barycentric, so the draw is skipped here and in CI; it has not run on any device. No title or homebrew was tested. The f16 rounding of P10/P20 and the unfused multiply-add are assumptions, not hardware measurements.

Checklist

  • Based on current main; no other open PR implements the same functions
  • One topic per PR; follow-ups go in a new PR
  • No comments in code except technical debt
  • Unimplemented paths throw; silent stubs are listed in TechnicalDebt (none added)
  • No notes, investigation .md files or images added to the repository
  • New third-party code is a submodule built from source (none added)
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

@github-actions github-actions Bot added shader core/shader docs agc libSceAgc* labels Oct 5, 2026
@github-actions

github-actions Bot commented Oct 6, 2026 •

Copy link
Copy Markdown

Progress report (dc79e9b → 8f663d0)

📈 GPU shader instructions: 100.0% (+0.26%, +3 implemented instructions)

✅ 3 implemented
Encoding Name
VOP3 V_INTERP_P1LL_F16
VOP3 V_INTERP_P1LV_F16
VOP3 V_INTERP_P2_F16

@adriadam10

Copy link
Copy Markdown
Collaborator

I measured these three on RDNA2 hardware (gfx1035, same VOP3 encodings as gfx1013). v_interp_* also run in a compute kernel and read the parameters from LDS (M0 = 0, 12 dwords per attribute: P0.x, P10.x, P0.y, P10.y, P0.z, P10.z, P0.w, P10.w, P20.x..w), so the arithmetic can be checked against chosen P0/P10/P20 halves and I/J values:

  1. v_interp_p1ll_f16 and v_interp_p1lv_f16 are a fused multiply-add with a single f32 rounding, not a multiply then an add: 1280/1280 random rows match fma(P10, I, P0) for p1ll, 1280/1280 for p1lv; the separate multiply and add matches about 85% of them.
  2. v_interp_p2_f16 rounds once, straight to f16: of 2560 rows built so the candidates differ (2553 do), f16(P20 * J + S2) computed exactly matches 2560, the f32-then-f16 double rounding 1766, and the separate f32 multiply and add (this PR) 101.
  3. What the PR does right, confirmed: high also selects the high half of p1lv's VGPR P0, and p2 keeps the destination's high 16 bits.

As both steps need an exact fused result, emitting SPIR-V Fma isn't enough either: the Vulkan precision table lets a driver split it into a multiply and an add. Products of an f16 and an f32 fit in 35 bits, so computing them exactly (e.g. in f64, or with the split used by the f64 v_fma_f64 emulation on main) before a single rounding would make it driver-independent. The "neither is confirmed on hardware" line in TechnicalDebt could then go for this part (the P10/P20 rounding done by the SPI is not covered by this measurement).

queeeef added a commit to queeeef/AnyPS5 that referenced this pull request Oct 6, 2026
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kSh1x5fPGrREAeRnUPKMP
@adriadam10

Copy link
Copy Markdown
Collaborator

Thanks for the quick rework. I ran it against 384 more hardware rows (gfx1035), this time with specials: ±0, ±inf, quiet and signaling NaNs of both signs, f32 max, f16 65504, denormals, and products that overflow f16 or f32. Finite results, infinities, overflow to inf and the sign of zero all match your model (282/282). Two NaN cases don't:

  1. Invalid operations give the negative default NaN. inf × 0 and inf − inf return 0xffc00000 for p1ll/p1lv and 0xfe00 for p2 (35/35 rows). The PR returns 0x7fc00000 / 0x7e00.
  2. An invalid product beats a NaN addend. With P10/P20 × I/J = inf × 0 and P0 (or S2) a NaN, the hardware returns the invalid NaN above, not the quieted addend, e.g. p1ll(P10 = -inf, I = 0, P0 = 0xfe01) = 0xffc00000; p2(P20 = -0, J = -inf, S2 = 0x7fc00000) = 0xfe00. Everywhere else, the first NaN input in the order P10/P20, I/J, P0/S2, quieted, is what the hardware returns (441/451 rows).

So the order is: a NaN in the delta or the coordinate, quieted; else an invalid product or sum gives 0xffc00000/0xfe00; else a NaN addend, quieted. Rows from the run, if you want them as test vectors (inputs as raw bits: P10/P20 f16, I/J f32, P0 f16 or S2 f32 → result):

op P10/P20 I/J P0/S2 hardware
p1ll 0x0000 0x7f800000 0x7bff 0xffc00000
p1ll 0xfc00 0x00000000 0xfe01 0xffc00000
p1ll 0x7c00 0x7f7fffff 0xfc00 0xffc00000
p2 0x0000 0x7f800000 0x4f000000 0xfe00
p2 0x8000 0xff800000 0x7fc00000 0xfe00
p2 0xfe01 0x3f800000 0x47800000 0xfe01

queeeef added a commit to queeeef/AnyPS5 that referenced this pull request Oct 6, 2026
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012kSh1x5fPGrREAeRnUPKMP
@queeeef

queeeef commented Oct 6, 2026 via email

Copy link
Copy Markdown
Contributor Author

queeeef added a commit to queeeef/AnyPS5 that referenced this pull request Oct 6, 2026
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
queeeef added a commit to queeeef/AnyPS5 that referenced this pull request Oct 6, 2026
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@adriadam10

Copy link
Copy Markdown
Collaborator

Heads-up: this PR is on hold, not rejected. It relies on how v_interp_*_f16 behaves under the shader float mode (IEEE_MODE, DX10_CLAMP, the denormal bits of FLOAT_MODE in RSRC1). Real titles set that mode per shader, and the recompiler currently assumes a single one; see #981. Until that is settled, no float-mode dependent shader PR gets reviewed or merged. Nothing to do on your side for now. Once #981 lands, this will probably need tests for each mode value it relies on.

Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
…terp_p2_f16

Decode the three VOP3 16-bit interpolation instructions (attribute, channel
and high-half select in the src0 field, the I/J VGPR in src1, P0 or the first
step's result in src2) and translate them with a new
GetInterpolationParameterF16 IR value. It reads the selected 16-bit half of
each vertex's parameter through VK_KHR_fragment_shader_barycentric, so P10 and
P20 are vertex differences rounded to f16; both steps are a multiply and an
add, as in the f32 forms.

An input in FP16_INTERP_MODE whose low half is defaulted is still declared at
its slot when its high half is read, and the high half takes its default from
USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1. Without barycentrics, on an input
without FP16_INTERP_MODE or on a pass-through input the instructions throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit 73777c7)
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit 6534b25)
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit bd0a0e5)
Sanjaysrinivas added a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Replay six published gfx1035 tuples through the interpolation arithmetic emitters with runtime buffer inputs. Rotate the rows for a second dispatch and check inputs and guards. This runs without fragment barycentrics.

Oracle: boykopovar#697 (comment)

Use exact expanded FP32 arguments to isolate arithmetic from guest half conversion, SPI, LDS and guest mode handling. No new opcode semantics or native RDNA2/PS5 execution are claimed.
@EdouardSence

Copy link
Copy Markdown
Contributor

The device draw in agc_driver_interpolation_f16 (the part lavapipe skips) ran on two GPUs with fragmentShaderBarycentric. Head bd0a0e5, merged onto main b3fcd6d, Linux, Mesa 26.2.4 RADV:

Device agc_driver_interpolation_f16
RX 9070 XT (GFX1201) 16-bit interpolation tests passed (draw ran, not skipped)
Ryzen 7 7800X3D iGPU (Raphael, gfx1036, RDNA2), ANYPS5_GPU=RAPHAEL 16-bit interpolation tests passed

Full ctest on the 9070 XT: 459/459 (main alone 458/458).

This only says the emitted SPIR-V matches the PR's reference model on these drivers. It doesn't settle the float-mode questions from #981.

Tested and written by Claude (AI assistant) on a contributor's machine.

Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
…terp_p2_f16

Decode the three VOP3 16-bit interpolation instructions (attribute, channel
and high-half select in the src0 field, the I/J VGPR in src1, P0 or the first
step's result in src2) and translate them with a new
GetInterpolationParameterF16 IR value. It reads the selected 16-bit half of
each vertex's parameter through VK_KHR_fragment_shader_barycentric, so P10 and
P20 are vertex differences rounded to f16; both steps are a multiply and an
add, as in the f32 forms.

An input in FP16_INTERP_MODE whose low half is defaulted is still declared at
its slot when its high half is read, and the high half takes its default from
USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1. Without barycentrics, on an input
without FP16_INTERP_MODE or on a pass-through input the instructions throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit 73777c7)
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit 6534b25)
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit bd0a0e5)
Sanjaysrinivas added a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Replay six published gfx1035 tuples through the interpolation arithmetic emitters with runtime buffer inputs. Rotate the rows for a second dispatch and check inputs and guards. This runs without fragment barycentrics.

Oracle: boykopovar#697 (comment)

Use exact expanded FP32 arguments to isolate arithmetic from guest half conversion, SPI, LDS and guest mode handling. No new opcode semantics or native RDNA2/PS5 execution are claimed.
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
…terp_p2_f16

Decode the three VOP3 16-bit interpolation instructions (attribute, channel
and high-half select in the src0 field, the I/J VGPR in src1, P0 or the first
step's result in src2) and translate them with a new
GetInterpolationParameterF16 IR value. It reads the selected 16-bit half of
each vertex's parameter through VK_KHR_fragment_shader_barycentric, so P10 and
P20 are vertex differences rounded to f16; both steps are a multiply and an
add, as in the f32 forms.

An input in FP16_INTERP_MODE whose low half is defaulted is still declared at
its slot when its high half is read, and the high half takes its default from
USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1. Without barycentrics, on an input
without FP16_INTERP_MODE or on a pass-through input the instructions throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit 73777c7)
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit 6534b25)
Sanjaysrinivas pushed a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

Prerequisite from boykopovar#697; retain original author and feature scope.
(cherry picked from commit bd0a0e5)
Sanjaysrinivas added a commit to Sanjaysrinivas/AnyPS5 that referenced this pull request Oct 8, 2026
Replay six published gfx1035 tuples through the interpolation arithmetic emitters with runtime buffer inputs. Rotate the rows for a second dispatch and check inputs and guards. This runs without fragment barycentrics.

Oracle: boykopovar#697 (comment)

Use exact expanded FP32 arguments to isolate arithmetic from guest half conversion, SPI, LDS and guest mode handling. No new opcode semantics or native RDNA2/PS5 execution are claimed.
adriadam10 pushed a commit to adriadam10/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
adriadam10 pushed a commit to adriadam10/AnyPS5 that referenced this pull request Oct 8, 2026
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
queeeef and others added 4 commits October 8, 2026 23:36
…terp_p2_f16

Decode the three VOP3 16-bit interpolation instructions (attribute, channel
and high-half select in the src0 field, the I/J VGPR in src1, P0 or the first
step's result in src2) and translate them with a new
GetInterpolationParameterF16 IR value. It reads the selected 16-bit half of
each vertex's parameter through VK_KHR_fragment_shader_barycentric, so P10 and
P20 are vertex differences rounded to f16; both steps are a multiply and an
add, as in the f32 forms.

An input in FP16_INTERP_MODE whose low half is defaulted is still declared at
its slot when its high half is read, and the high half takes its default from
USE_DEFAULT_ATTR1 and DEFAULT_VAL_ATTR1. Without barycentrics, on an input
without FP16_INTERP_MODE or on a pass-through input the instructions throw.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on RDNA2 in boykopovar#697: v_interp_p1ll_f16 and v_interp_p1lv_f16 are a
fused multiply-add rounded once to f32, and v_interp_p2_f16 rounds the exact
P20 * J + S2 once to f16. The separate multiply and add matched about 85% and
4% of the measured rows.

Both steps now use FPInterpolateF32 and FPInterpolateF16: the f16 delta times
the f32 coordinate is exact in f64, the sum is rounded to odd and then once to
nearest even at the target precision, so no driver can split or reround it.
Infinite and NaN operands take the unbounded f64 result.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Measured on RDNA2 in boykopovar#697: an invalid product or sum (inf * 0, inf - inf)
gives the negative default NaN, 0xffc00000 or 0xfe00, and beats a NaN
addend. A NaN delta, then a NaN coordinate, still wins, quieted; a NaN
addend is returned quieted only when the product is valid.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
@adriadam10

adriadam10 commented Oct 8, 2026 •

Copy link
Copy Markdown
Collaborator

Taking this off hold for #981. I rebased it on main (it now includes #1816 and #1826) and pushed one commit on top. Your three commits are unchanged, and the original head is backed up.

The float mode now drives the interpolation. I measured v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 on RDNA2 (gfx1035) with the HW oracle, in all 8 combinations of IEEE on/off, f32 denormals kept/flushed and f16 denormals kept/flushed. The inputs were 18432 special operands (±0, ±denormal, ±inf, quiet and signaling NaNs, values next to the f16 limits) plus 12288 random ones. The model below matches all of them:

  • A NaN result is quieted when IEEE=1 or when the result is f16.
  • With f16 denormals flushed, the f16 vertex delta is flushed. With f32 denormals flushed, the f32 coordinate is flushed. The base is flushed under the mode of its own width (f32 for p2, f16 for p1), and so is the result.
  • Rounding other than nearest-even, and denormal modes 1 and 2 (flush only the inputs or only the outputs), throw because they weren't measured.
  • With no known mode the shader is translated as IEEE=0 with denormals kept, the mode titles use. TechnicalDebt says so.

FPInterpolateF32/FPInterpolateF16 take the mode as an immediate fifth argument, so the emitter adds flushes only for the modes that need them.

Tests. The new agc_driver_interpolation_modes checks 32 hardware rows × 3 ops × 8 modes bit for bit. It runs the IR ops from a compute shader, so it runs on lavapipe and in CI, not only on devices with barycentrics. InterpolationF16 now pins the IEEE=1 mode its expectations assume. Two negative checks each fail the new test: dropping the flushes, and quieting every NaN at IEEE=0. Dropping the half-result quieting rule doesn't, because no hardware row needs it at IEEE=0. It is kept because it matches the measurements, but the test doesn't pin it.

Tested: RADV and CI's lavapipe, full ctest 492/492 after the rebase.

@adriadam10
adriadam10 merged commit ae91d97 into boykopovar:main Oct 8, 2026
6 checks passed
@MarcZX10

MarcZX10 commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Post-merge main Build 37850204774 on ae91d978 failed Windows CTest on the second repeat of param_json_parser (0xc0000409); its first repeat passed in 0.18 s. The PR does not touch the parser or its test, so this may be an unrelated Windows test flake. I have not reproduced it locally; noting the exact run for triage.

tibzejoker pushed a commit to tibzejoker/AnyPS5 that referenced this pull request Oct 16, 2026
…s without Float64

v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 round the exact
P10 * I + P0 once to f32 and P20 * J + S2 once to f16 (boykopovar#697), and
v_fma_mixlo_f16 and v_fma_mixhi_f16 reuse the f16 step in every f16
rounding mode. The exact sum was computed in f64, so on a device without
Float64 every shader using them failed to compile:

  64-bit float instructions need the Float64 capability, which the
  device lacks

Apple GPUs have no f64; with MoltenVK 1.4.2 on Apple M2 this stops
ProsperoStore (PPSA99000), PokeMMO Prospero (PPSA98001), Regie5
(PPSA99520) and PS5 OpenGL Showcase (PPSA99005) at their first such
pixel shader.

Without Float64 these operations are now computed in 64-bit integers,
which the shader device profile already requires. The inputs are
flushed as before and decoded to integer significands. The 24 by 24 bit
product is exact in 64 bits. Both terms are normalized to bit 62, the
smaller one is shifted down with the bits it loses kept as a sticky bit,
and the exact sum is rounded once to f32 to nearest even, or to f16 in
the f16 rounding mode, with f32 and f16 subnormal steps. Overflow,
signed zeros, the round-down zero sign, NaN quieting and the order in
which NaN operands win follow the f64 path, without its f64 arithmetic.
The module then declares Int64 instead of Float64. Devices with Float64
keep the f64 path.

(cherry picked from commit f13e05e)
ycarogabriel-lgtm pushed a commit to ycarogabriel-lgtm/AnyPS5 that referenced this pull request Oct 17, 2026
…s without Float64

v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 round the exact
P10 * I + P0 once to f32 and P20 * J + S2 once to f16 (boykopovar#697), and
v_fma_mixlo_f16 and v_fma_mixhi_f16 reuse the f16 step in every f16
rounding mode. The exact sum was computed in f64, so on a device without
Float64 every shader using them failed to compile:

  64-bit float instructions need the Float64 capability, which the
  device lacks

Apple GPUs have no f64; with MoltenVK 1.4.2 on Apple M2 this stops
ProsperoStore (PPSA99000), PokeMMO Prospero (PPSA98001), Regie5
(PPSA99520) and PS5 OpenGL Showcase (PPSA99005) at their first such
pixel shader.

Without Float64 these operations are now computed in 64-bit integers,
which the shader device profile already requires. The inputs are
flushed as before and decoded to integer significands. The 24 by 24 bit
product is exact in 64 bits. Both terms are normalized to bit 62, the
smaller one is shifted down with the bits it loses kept as a sticky bit,
and the exact sum is rounded once to f32 to nearest even, or to f16 in
the f16 rounding mode, with f32 and f16 subnormal steps. Overflow,
signed zeros, the round-down zero sign, NaN quieting and the order in
which NaN operands win follow the f64 path, without its f64 arithmetic.
The module then declares Int64 instead of Float64. Devices with Float64
keep the f64 path.
adriadam10 pushed a commit to DavidSamir/AnyPS5 that referenced this pull request Oct 19, 2026
…s without Float64

v_interp_p1ll_f16, v_interp_p1lv_f16 and v_interp_p2_f16 round the exact
P10 * I + P0 once to f32 and P20 * J + S2 once to f16 (boykopovar#697), and
v_fma_mixlo_f16 and v_fma_mixhi_f16 reuse the f16 step in every f16
rounding mode. The exact sum was computed in f64, so on a device without
Float64 every shader using them failed to compile:

  64-bit float instructions need the Float64 capability, which the
  device lacks

Apple GPUs have no f64; with MoltenVK 1.4.2 on Apple M2 this stops
ProsperoStore (PPSA99000), PokeMMO Prospero (PPSA98001), Regie5
(PPSA99520) and PS5 OpenGL Showcase (PPSA99005) at their first such
pixel shader.

Without Float64 these operations are now computed in 64-bit integers,
which the shader device profile already requires. The inputs are
flushed as before and decoded to integer significands. The 24 by 24 bit
product is exact in 64 bits. Both terms are normalized to bit 62, the
smaller one is shifted down with the bits it loses kept as a sticky bit,
and the exact sum is rounded once to f32 to nearest even, or to f16 in
the f16 rounding mode, with f32 and f16 subnormal steps. Overflow,
signed zeros, the round-down zero sign, NaN quieting and the order in
which NaN operands win follow the f64 path, without its f64 arithmetic.
The module then declares Int64 instead of Float64. Devices with Float64
keep the f64 path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants