Skip to content

fix(shader): round v_fma_mixlo_f16 and v_fma_mixhi_f16 once to f16 - #2162

Merged
adriadam10 merged 1 commit into
boykopovar:mainfrom
CouchRiv:fix-shader-fma-mix-f16-fused
Oct 10, 2026
Merged

adriadam10 merged 1 commit into
boykopovar:mainfrom
CouchRiv:fix-shader-fma-mix-f16-fused

Conversation

@CouchRiv

@CouchRiv CouchRiv commented Oct 9, 2026

Copy link
Copy Markdown
Contributor

What

v_fma_mixlo_f16 and v_fma_mixhi_f16 can return a result one f16 step away from the hardware's:

v_fma_mixlo_f16 v10, v4, v5, v6   ; -1.5 * 2^-24 + 2^-126
hardware 0x8001, AnyPS5 0x8002

They were translated as an f32 FMA converted to f16. That rounds twice: here the f32 rounding lands exactly on an f16 tie, and the second rounding goes to even. The hardware rounds the exact product-sum once. #1878 made the f32 result fused; this does the same for the f16 results.

They now use FPInterpolateF16, the path v_interp_p2_f16 already uses to round an f32 product-sum once to f16: exact f64 product, sum rounded to odd, one rounding to f16. Their f32 sources are also flushed when the shader flushes f32 denormals, as v_fma_mix_f32 already does and as the hardware does. A product below 2^-126 is kept, as on hardware. v_fma_mix_f32 is unchanged. v_fma_f16 and v_pk_fma_f16 are left to #2000 (no overlap: its new path is for non-mix v_fma_f16). #2025 adds the FP16_OVFL clamp to the shared writeF16 call below the new mix path; it does not change the double rounding, and whichever of the two merges second should pass the sources to the mix path's writeF16 too.

Measured on RDNA2 (gfx1036, Radeon 610M) with the hardware oracle in four modes: IEEE 0/1, DX10 clamp 0/1, f32 denormals kept/flushed, each with f16 denormals kept, f16 round to nearest even and FP16_OVFL clear. The same values in all four modes unless noted:

sources a, b, c hardware before after
f32 0xbfc00000, 0x33800000, 0x00800000 0x8001 0x8002 0x8001
f16 0x62e9, 0xd159, 0x23ec 0xf89f 0xf89e 0xf89f
f32 0x477ff000, 0x3f800000, 0xa6800000 (65520 - tiny) 0x7bff 0x7c00 0x7bff
f32 0x00800000, 0x3e800000, 0x3f801000 (product below 2^-126) 0x3c01 0x3c00 0x3c01
f32 0x7f7fffff, 0x00000001, 0 (f32 denormals kept) 0x0008 0x0000 0x0008

They now need the Float64 capability, like v_interp_p2_f16 and v_dot2_f32_f16 (TechnicalDebt).

The hardware follows the f16 round and denormal fields of FLOAT_MODE here, not the f32 round field. The translation only does nearest even with f16 denormals kept, and so did the f32 FMA before, so another f16 mode now throws, as packed f16 already does, instead of giving a wrong result. Measured with IEEE=0 and f32 denormals flushed:

FLOAT_MODE f16 fields a, b, c (f32) hardware before after
denormals kept, nearest even (0xc0) 0xbfc00000, 0x33800000, 0x00800000 0x8001 0x8002 0x8001
same, f32 round toward zero (0xc3) 0xbfc00000, 0x33800000, 0x00800000 0x8001 0x8002 0x8001
denormals flushed (0x00) 0xbfc00000, 0x33800000, 0x00800000 0x8000 0x8002 throws
denormals flushed (0x00) 0x3fc00000, 0x33800000, 0 0x0000 0x0002 throws
round toward -infinity (0xc8) 0x3fc00000, 0x33800000, 0 0x0001 0x0002 throws

TechnicalDebt now also says that mixlo and mixhi always quiet a signaling NaN (0x7f812345 gives 0x7e09 at IEEE=0 and 1), unlike v_fma_mix_f32.

Which title needs it

No specific title run. This completes #1878 for the f16 results in the 0xc0 mode titles use and the other measured modes above.

Tested

CachyOS Linux, GCC, Release; RTX 4070 Ti (NVIDIA) and Radeon 610M (RADV).

  • Full ctest on main 3bd74c1 plus this change: 514/514 on NVIDIA, RADV and lavapipe (the upstream CI driver).
  • vector_fused_multiply_add now decodes and translates v_fma_mixlo_f16, v_fma_mixhi_f16 and op_sel_hi:[1,1,1], and requires no f32 FMA and one exact product-sum. agc_driver_mix_precision adds 64 hardware rows for f32, f16 and mixed sources; 60 of them differ from the hardware before the fix, on both drivers. Both fail without the fix (0xabcdf89e, expected 0xabcdf89f) and pass with it, under 1 s. vector_fused_multiply_add also checks that f16 denormals flushed or f16 round toward -infinity throw, and that a set f32 round field does not.
  • Differential runs of the hardware against the recompiler on both drivers, in the four float modes: 2,744 f32-source rows, 2,221 f16-source rows, and 358 rows built around f16 ties with f32, f16 and mixed sources (238 of them round twice before the change). After the change no row differs on RADV. On NVIDIA the only rows that differ are f16 NaN inputs, whose payload NVIDIA's f16 to f32 conversion replaces (NaN bits are left to the driver, TechnicalDebt). Before the change, RADV differed on 2 to 74 rows per mode for f32 sources and on all 238 rows that round twice.
  • check_conventions.py, git diff --check.
  • Not tested: the PS5's gfx1013 (gfx1036 is the same RDNA2 family; fix(shader): translate v_fma_mix_f32 as a fused multiply-add #1878 measured gfx1035), FP16_OVFL set. Other f16 modes were measured only to confirm they differ; they throw.

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 (NotImplemented_nid_no_patch); silent stubs are listed in TechnicalDebt
  • No notes, investigation .md files or images added to the repository (attach them to this PR)
  • New third-party code is a submodule built from source
  • Generic behaviour, not specific to one title
  • Depends on: none
  • AI-assisted: yes

🤖 Generated with Claude Code

@joelfernandes23

Copy link
Copy Markdown
Contributor

Tested the five-file change from PR head b97126ee1dcf416e90ac280fc2ce397ff66e8ad5 applied to unchanged main d70b89989473ba1f6ae13e44e079e67f1f8a44b0 on AMD Windows.

Environment: Windows 11 Pro 10.0.26200, Ryzen 7 7700X, Radeon RX 7900 XT (RDNA 3, device 0x744c), driver 32.0.31041.1004, Vulkan 1.4.349. WinLibs GCC 15.2.0 UCRT/POSIX/SEH r7, Release, BUILD_TESTING=ON, SPIRV-Tools OFF. ANYPS5_GPU=7900 XT and ANYPS5_REQUIRE_VULKAN=1.

Before applying the production fix, copied only the two updated regression test sources onto main and built their targets. Both fail:

AGC graphics: mix rounding: thread 0 (0x000062e9, 0x0000d159, 0x000023ec) result 2 is 0xabcdf89e, expected 0xabcdf89f
vop2 multiply-add: v_fma_mixlo_f16 rounds to f32 before rounding to f16

With the complete PR change on the same main revision:

This validates the translated shader execution on AMD's Windows Vulkan driver against the supplied regression expectations. No independent native-instruction hardware oracle or PS5/title testing was performed, and no changes to the PR implementation were made.

The f16 forms of v_fma_mix computed an f32 FMA and converted it to f16,
so a result that lands on an f16 tie after the f32 rounding was rounded
twice. RDNA2 rounds the exact product-sum once. They now use the exact
f64 path of v_interp_p2_f16 and flush f32 sources like v_fma_mix_f32.
They follow the f16 round and denormal fields on hardware, so another
f16 mode throws, as packed f16 does.

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

Copy link
Copy Markdown
Collaborator

Rebased onto current main (conflicts in FloatInstructions.cpp, VectorInstructions.cpp and TechnicalDebt.md after #2000 and the v_mad_legacy_f32/v_div_fixup_f32 merges): kept #2000's non-mix path and this PR's mix path, the three f32 flush cases, and the TechnicalDebt text on top of main's. Build and the scoped ctest pass, including agc_driver_mix_precision, vector_fused_multiply_add and agc_driver_float16_fma_rounding. Hardware results checked earlier on RDNA2 (gfx1035): 9278 mixlo/mixhi results match an exact single-rounding model. Thanks @joelfernandes23 for the Windows run. Merging.

@adriadam10
adriadam10 force-pushed the fix-shader-fma-mix-f16-fused branch from b97126e to 3a9da56 Compare October 10, 2026 06:43
@adriadam10
adriadam10 merged commit c20184a into boykopovar:main Oct 10, 2026
14 checks passed
SleglJan pushed a commit to SleglJan/AnyPS5 that referenced this pull request Oct 10, 2026
…16-fused

fix(shader): round v_fma_mixlo_f16 and v_fma_mixhi_f16 once to f16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* docs needs rebase Conflicts with main, rebase needed shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants