Skip to content

perf(shader): drop f16 NaN payload rebuilds that the result never uses - #11368

Merged
oneandonlydean merged 3 commits into
boykopovar:mainfrom
tibzejoker:perf/f16-special-values
Nov 2, 2026
Merged

oneandonlydean merged 3 commits into
boykopovar:mainfrom
tibzejoker:perf/f16-special-values

Conversation

@tibzejoker

@tibzejoker tibzejoker commented Oct 26, 2026 •

Copy link
Copy Markdown
Contributor

What

Step 4 of #11183. Packed f16 ops computed in f32 and f16 unary ops no longer rebuild RDNA's NaN payload in conversions whose NaN the result throws away.

v_pk_fma_f16, v_pk_fmac_f16 (and v_pk_add_f16 / v_pk_mul_f16 when they are not native, e.g. with clamp or flushed denormals) widen each f16 lane to f32, compute, and pack the two results back with PackHalf2x16. On a device with Float16, RoundingModeRTE 16 and DenormPreserve 16, each widening rebuilds the NaN payload after UnpackHalf2x16 (6 instructions) and each packed lane rebuilds it after OpFConvert (11 instructions). Then packedFloat16 applies RDNA's NaN rule to every lane, from the raw f16 source bits:

  • a NaN source gives the quieted source (first NaN in source order; the addend is skipped on inf * 0);
  • otherwise a NaN result gives 0xfe00;
  • with clamp and DX10_CLAMP, the rule is skipped because the clamp already turns a NaN into 0.

So the payload of every one of those conversions is overwritten:

  • widening: if a source is NaN, its lane is replaced by that source's bits (or, on inf * 0, the product is NaN whatever the addend is). If no source is NaN, the conversion is exact and has no NaN to rebuild. Only NaN-ness can still matter, through the DX10_CLAMP path, and UnpackHalf2x16 keeps it: today's code already relies on it, since the payload rebuild is selected by OpIsNan on that same result, and OnlyCompared uses the plain unpack already;
  • packing: only NaN-ness reaches the rule (0xfe00 when no source is NaN). The packed lane is now isNaN(f32) ? 0x7e00 : OpFConvert, tested on the f32 bits, so it does not depend on what OpFConvert returns for a NaN.

f16 unary ops (v_rcp_f16, v_sqrt_f16, v_rsq_f16, v_log_f16, v_exp_f16, v_fract_f16, v_sin_f16, v_cos_f16) replace the result of a NaN input with the quieted input bits, so their input widening does not need the payload either. Their f32 -> f16 rounding keeps today's code: the result of a non-NaN input is not always overwritten.

The translator marks those conversions with FloatConvertFlags::nanPayloadUnused (IR value flags, like MemoryFlags); EmitConvertF32F16 then emits the plain unpack and PackHalf2x16 the NaN-ness-only lane. The flags are part of the redundant value key, so a marked conversion is never merged with an unmarked one. No device property, probe or switch: the change is the same on every device, and devices without native f16 conversions keep the software path unchanged.

Nothing else moves: no guard branches (#5057's lesson), the NaN rule itself, the scalar paths and the f16 <-> f32 conversion probe of #11351 are untouched (the scalar v_fma_f16 keeps the host NaN when a source is NaN, so its payloads are used).

Where the f16 special-value words are (measured first)

On main fdb3925940, 79 compute requests of Grand Theft Auto V (agc_shader_replay --spv, RTX 4090 target, 1,835,101 words). Each rule was removed alone with a local, non-exact knock-out, to size it:

rule words share
packed f16 NaN rule (packedFloat16, source pick + 0xfe00) 68,110 3.7 %
scalar f16 0xfe00 on an invalid operation (writeF16) 54,388 3.0 %
packed f16 min/max NaN and signalling-NaN rule 15,012 0.8 %
scalar f16 min/max/med3 NaN rule 11,188 0.6 %
f16 unary NaN / invalid select 1,620 0.1 %
FP16_OVFL 0 (no request sets it)
all of them 139,564 7.6 %

The rules themselves are already short selects on the raw bits; I found no exact rewrite with fewer instructions (a SIMD-within-a-register version of the packed rule costs more than two per-lane selects). The words this PR removes are the NaN rebuilds of the conversions around the packed rule, which the measurement above does not count.

Tested

Windows 11 Pro, MinGW-w64 GCC 15.2.0 (WinLibs r7), Ninja, Release, BUILD_TESTING=ON, ANYPS5_ENABLE_SPIRV_TOOLS=ON (spirv-val on every module), on main 4e43321bd6 (the size and timing numbers below were taken on fdb3925940; nothing under core/shader/recompiler changed between the two).

  • New test agc_driver_float16_unused_nan_payloads (tests/execution/Float16UnusedNanPayloads.cpp): each program is compiled twice, for the device and for a software target (no Float16, float16DenormPreserve off, where the flags change nothing and every conversion keeps RDNA's NaN payload, as on main). All results are compared bit for bit:

    • the 8 packed ops of agc_driver_packed_float16_native (add, mul, fma, fmac, op_sel, neg, clamp), each source in turn taking all 65,536 halves while the others are drawn from 26 special values (quiet and signalling NaNs of both signs and several payloads, infinities, zeros, denormals, largest finite) or random halves, under 9 float modes (DX10_CLAMP, IEEE_MODE, FP16_OVFL combinations, flushed f16 denormals);
    • the 8 unary ops over all 65,536 halves under 4 float modes;
    • 20,971,520 results, 6,152,589 NaN halves, all equal on both GPUs;
    • each mode runs one dispatch per program and pass (512 or 1,024 workgroups of 64), 80 dispatches in all, and every invocation must write its results: 1.2 s on the RTX 4090, 1.5 s on the UHD 770 in the focused runs. It is registered like every tests/execution source (the glob in libSceAgcDriver/CMakeLists.txt), as agc_driver_float16_unused_nan_payloads. On a device without native f16 conversions (lavapipe, per perf(shader): keep native f16 conversion NaN results when a startup probe matches RDNA #11351's RADV/lavapipe run) it reports a skip.

    It also checks that a DX10_CLAMP packed program has no OpIsNan left (the payload rebuild of the widening), which fails on main. A mutation that makes the packed lane lose its NaN-ness (0x7c00 instead of 0x7e00) fails the comparison at once.

  • Focused tests, main 4e43321bd6 (with the new test file added) vs this PR (103cf586a9), ctest -R "^agc.*(f16|float16|pack|fma|minmax|med3|clamp)" -E "^agc_driver_bda_device$" -V, VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation with VK_LOADER_DEBUG=layer (69 Insert instance layer "VK_LAYER_KHRONOS_validation" lines per run, no was not found but was requested by env var VK_INSTANCE_LAYERS), ANYPS5_NO_SHADER_CACHE=1, ANYPS5_REQUIRE_VULKAN=1:

    GPU main this PR VUID- lines (main / PR)
    RTX 4090 (NVIDIA 616.92) 73/74, the new test fails 74/74 0 / 0
    UHD 770 (Intel 101.6129) 68/74, 3 skipped, 3 failed (new test + 2) 69/74, 3 skipped, 2 failed 0 / 0

    The 2 Intel failures are the same on both (agc_driver_packed_unorm4444_partial_write, agc_driver_packed_unorm4444_multisample_partial_write); the skips are agc_driver_f64_fma_subnormals (no Float64), agc_driver_float16_dot (no dot2) and agc_driver_image_sample_lod_clamp. agc_driver_packed_float16_native, agc_driver_nan_quieting, agc_driver_fp16_overflow and the f16 denormal tests pass on both.

  • Full agc suite on the RTX 4090, on fdb3925940 before the rebase (ctest -R "^agc" -E "^agc_driver_bda_device$" -j12): 1065 of 1067 passed; the 2 failures (agc_host_import_cut_lifetime, agc_driver_depth_plane_partial_store) also fail on main.

  • Size, Grand Theft Auto V compute shaders (the 79 requests of perf(shader): compute v_pk_fma_f16 and v_pk_fmac_f16 with OpFmaKHR #11265, agc_shader_replay --spv, RTX 4090 target):

    main this PR change
    all 79 1,835,101 1,790,105 -44,996 (-2.5 %)
    0x25b390800 503,525 463,457 -8.0 %
    0x25b393000 (RCAS) 90,558 85,630 -5.4 %

    The other 77 shaders have no packed f16 math computed in f32 and do not change. The UHD 770 has the same native f16 conversions, so the same programs shrink there.

  • Pipeline creation, NVIDIA 616.92: the two pipelines that change were captured from a cold Grand Theft Auto V run of each build (APS5_DUMP_COMPUTE_PIPELINES, APS5_DUMP_COMPUTE_MIN_WORDS=50000, empty ANYPS5_SHADER_CACHE_DIR; the two other captured pipelines are identical in both runs) and compiled with agc_compute_pipeline_replay, 5 times each, each capture salted with a distinct OpSourceExtension so the driver's own cache misses:

    | pipeline | words | pipeline_create ms (5 runs) | median |
    |---|---|---|---|
    | 0x25b390800, main | 551,841 | 5696 5728 5753 5748 5746 | 5746 |
    | 0x25b390800, this PR | 512,714 | 4995 5093 5065 5128 5061 | 5065 (-11.9 %) |
    | 0x25b393000, main | 97,303 | 590 596 597 586 592 | 592 |
    | 0x25b393000, this PR | 92,485 | 557 560 564 553 560 | 560 (-5.4 %) |

    0x25b390800 is compiled when the open world first loads, on the shader preparation thread.

  • In game (performance form), main fdb3925940 vs this PR on it (468733630d, the same change before the rebase), RTX 4090: the open-world load from the same save, one run per build, empty ANYPS5_SHADER_CACHE_DIR, APS5_TRACE_SECONDS=40:80, same machine, both after the capture runs above. Traces, summaries, digests (from current main's trace_digest.py) and the replay results: fork release perf-f16-special-values-20261026.

    • main: seconds 40:80 from present 447: 2139 presents, frame p50 17.4 / p90 45.3 / p99 265.0 / max 2395.0 ms, GPU busy 6.0 ms per present
    • this PR: seconds 40:80 from present 445: 2131 presents, frame p50 17.1 / p90 44.7 / p99 251.7 / max 2630.8 ms, GPU busy 6.3 ms per present
    • Same within run-to-run noise, and no long Vulkan.ComputePipeline slice in either window: the capture runs had already compiled both 0x25b390800 modules on this machine, and the NVIDIA driver serves them from its own cache (which ANYPS5_SHADER_CACHE_DIR does not reach, and which I don't clear). The compile-time gain shows on a module's first compile per driver, which the salted replays measure; as in perf(shader): compute v_pk_fma_f16 and v_pk_fmac_f16 with OpFmaKHR #11265, this pair checks that nothing else moves.

{"commit":"fdb3925940c1","dirty":false,"env":{"APS5_MIMALLOC":"1","APS5_TRACE_DIR":"","APS5_TRACE_SECONDS":"40:80"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 616.92","os":"Windows 10.0.26200","cpu":"13th Gen Intel(R) Core(TM) i9-13900K","cpu_threads":32,"memory_mib":65295,"title_id":"PPSA04264","title":"Grand Theft Auto V","content_version":"01.005.000","resolution":"3840x2160","window":{"spec":"seconds 40:80","first_present":447,"ms":80122.125},"presents":2139,"frame_ms":{"p50":17.384,"p90":45.289,"p99":265.010,"max":2395.008},"gpu_busy_ms_per_present":6.003,"worker_busy_pct":{"queue worker 0x0":73.335,"queue worker 0x28":8.183,"queue worker 0x20":13.191,"queue worker 0x38":13.050,"queue worker 0x30":5.452},"top_phases_ms_per_frame":{"Driver.Packet":41.413,"VideoOut.Flip":16.342,"Driver.Packet.memory_wait":14.905,"Graphics.Draw":13.322,"Driver.Packet.draw":12.134,"Driver.Draw":12.039,"Graphics.Draw.draw_and_resource_release":10.873,"Driver.Packet.rendering_wait":8.780},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":53.397,"events_lost":874828,"totals_full":false},"draw committer":{"events_full_after_s":65.989,"events_lost":320641,"totals_full":false}},"trace":"aps5-trace-fdb3925940c1-28652.json.gz"}
{"commit":"468733630da2","dirty":false,"env":{"APS5_MIMALLOC":"1","APS5_TRACE_DIR":"","APS5_TRACE_SECONDS":"40:80"},"gpu":"NVIDIA GeForce RTX 4090","driver":"NVIDIA 616.92","os":"Windows 10.0.26200","cpu":"13th Gen Intel(R) Core(TM) i9-13900K","cpu_threads":32,"memory_mib":65295,"title_id":"PPSA04264","title":"Grand Theft Auto V","content_version":"01.005.000","resolution":"3840x2160","window":{"spec":"seconds 40:80","first_present":445,"ms":80078.358},"presents":2131,"frame_ms":{"p50":17.137,"p90":44.708,"p99":251.680,"max":2630.836},"gpu_busy_ms_per_present":6.299,"worker_busy_pct":{"queue worker 0x0":73.516,"queue worker 0x38":8.786,"queue worker 0x30":5.217,"queue worker 0x20":13.877,"queue worker 0x28":11.596},"top_phases_ms_per_frame":{"Driver.Packet":41.576,"VideoOut.Flip":16.862,"Driver.Packet.memory_wait":14.848,"Graphics.Draw":13.503,"Driver.Packet.draw":12.181,"Driver.Draw":12.088,"VideoOut.Flip.vblank_wait":11.391,"Graphics.Draw.draw_and_resource_release":11.052},"trace_dropped":true,"trace_dropped_threads":{"queue worker 0x0":{"events_full_after_s":56.086,"events_lost":858961,"totals_full":false},"draw committer":{"events_full_after_s":66.367,"events_lost":307778,"totals_full":false}},"trace":"aps5-trace-468733630da2-30284.json.gz"}


AMD Windows driver (run by savvaniss, RX 9070 XT, driver 26.8.1, `32_BIT_ONLY`, validation on, 3 alternating runs per tree, [comment](https://github.com/boykopovar/AnyPS5/pull/11368)): on `main` alone the new test skips (no kept f16 denormals until #11483). On `main` + #11483 it fails without this PR, 3 of 3 (109/110), and passes with it, 3 of 3 (110/110). 0 `VUID-` lines in every run.

Not tested: AMD RADV, MoltenVK, Linux.

### Checklist
- [x] Based on current `main`; no other open PR implements the same functions
- [x] One topic per PR; follow-ups go in a new PR
- [x] No comments in code except [technical debt](https://github.com/boykopovar/AnyPS5/blob/main/docs/dev/CONVENTIONS.md)
- [x] Unimplemented paths throw (`NotImplemented_nid_no_patch`); silent stubs are listed under Silent stubs in their library's [technical debt](https://github.com/boykopovar/AnyPS5/tree/main/docs/dev/debt) file
- [x] No notes, investigation `.md` files or images added to the repository (attach them to this PR)
- [x] New third-party code is a submodule built from source
- [x] Generic behaviour, not specific to one title
- [x] Depends on: none (touches the line next to #11351's `float16NarrowNans` return in `EmitNativeF32ToF16BitsRte`: whichever merges second rebases)
- [x] Fixes: none
- [x] Unblocks: none
- [x] GPUs tested: NVIDIA GeForce RTX 4090 (616.92, Windows 11), Intel UHD Graphics 770 (101.6129, Windows 11); AMD RX 9070 XT (26.8.1, Windows 11, by savvaniss, with #11483)
- [x] AI-assisted: yes (Claude Code)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01PHJtpJ4hw3KWJhobipK8ia

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reviewed exact head aa3682a15825f3cfef0034ed5652d222295baa5b. I found a blocking test-integration gap:

The new tests/execution/Float16UnusedNanPayloads.cpp is not referenced anywhere in the PR's core/libs/prx/libSceAgcDriver/CMakeLists.txt; neither a target nor a CTest entry is added. Existing AGC tests are explicitly registered there. As submitted, CI does not compile or run the test described in this PR, so the 20,971,520-result comparison is not an enforced regression test. Please register the new executable and test in CMake with the appropriate GPU-test environment/skip handling, then verify the test is listed and runs through CTest.

The test currently performs about 45,000 dispatch-and-wait cycles (packed and unary cases each run both compiled variants). Repository guidance requires tests to finish within 30 seconds on CI runners, including lavapipe; please report its runtime on that path and batch/reduce work if it exceeds the limit.

The branch is based on cbee224804625f2416993f94b2bc1f8c58b1d4bb, 44 main commits behind current 172dcf29014b90340f6a7ae2fda284e4f5518e41. The current-main merge preview is clean and no touched path changed, but the repository asks branches to stay current. After addressing the test gap, sync to current main and rerun conventions, overlap, and the relevant focused checks. I could not run a Vulkan test locally because this environment has no /dev/dri device.

@github-actions

github-actions Bot commented Oct 26, 2026 •

Copy link
Copy Markdown

Some comparisons could not be completed because a pull request conflicts with the base branch. Review these shared files manually before concluding that there is no overlap.

Pull request Shared files not compared
PR 5021 core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
core/shader/recompiler/Translation/src/FloatInstructions.cpp
PR 6751 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 7392 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 7410 core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp
core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp
PR 7642 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 9508 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 10193 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 12324 core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp
PR 12548 core/shader/recompiler/IntermediateRepresentation/include/IntermediateRepresentation/IrOpcode.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterInstructions.hpp
core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 13928 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp
PR 13929 core/shader/recompiler/SpirvBackend/src/SpirvFlowEmitter.cpp

tibzejoker and others added 2 commits October 26, 2026 10:16
v_pk_fma_f16 and the other packed f16 ops computed in f32 replace every
NaN lane with the quieted NaN source or 0xfe00, and f16 unary ops replace
the result of a NaN input the same way. Their f16 -> f32 input conversions
and their f32 -> f16 packing no longer rebuild RDNA's NaN payload: the
widening is a plain UnpackHalf2x16 and the packing only keeps the result a
NaN. The conversions carry FloatConvertFlags so the emitter knows.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PHJtpJ4hw3KWJhobipK8ia
…h per pass

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PHJtpJ4hw3KWJhobipK8ia
@tibzejoker
tibzejoker force-pushed the perf/f16-special-values branch from aa3682a to 103cf58 Compare October 26, 2026 09:20
@tibzejoker

Copy link
Copy Markdown
Contributor Author

@MarcZX10 thanks. On registration: tests/execution/*.cpp are picked up by the file(GLOB agcExecutionTestSources ...) loop in core/libs/prx/libSceAgcDriver/CMakeLists.txt (around L2174), which builds agc_driver_float16_unused_nan_payloads_tests and registers the CTest entry agc_driver_float16_unused_nan_payloads; it is test #901 in the focused runs, and it fails on main and passes here. On runtime you were right: it did one 64-invocation dispatch per batch and built its failure message for every result, 16-18 s on hardware. The new commit 103cf586a9 runs each pass in one dispatch (80 in all), checks that every invocation wrote, and builds messages only on a mismatch: 1.2 s on the RTX 4090, 1.5 s on the UHD 770, same 20,971,520 results. It still fails on main on both GPUs. On lavapipe it reports a skip (no native f16 conversions there). Rebased on main 4e43321bd6; conventions pass and the focused set was rerun on that head (table updated, same results as before).

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I re-reviewed exact head 103cf586a9fb6cca773d1b77a696b148006f853c and current main 4e43321bd6ceccdc1ae28568bbbc35d9a6446464. I was wrong about test registration in my previous review: tests/execution/*.cpp are picked up by the CMake glob, then turned into executables and CTest entries. I withdraw that finding. The updated test also rebatches the work and the branch is now based on current main.

One test-coverage issue remains in Float16UnusedNanPayloads.cpp: the std::ranges::all_of(..., word == 0xdeadbeefu) check only fails when all eight output words for an invocation remain untouched. If one of the two four-word stores is missing, the other store makes all_of false; the untouched words remain the sentinel in both fast and software, compare equal, and that half of the operation set is silently accepted. Please verify every output slot was written in both buffers (or add explicit per-result write markers) so an omitted packed or unary result cannot pass as a matching sentinel.

Validation on this host: conventions pass, git diff --check passes, current main is an ancestor of this head, and the merge preview with open #11351 is clean. I downloaded the two release traces and summaries and recomputed both trace_digest.py reports; they match the attached reports after normalizing CRLF. I could not run the Vulkan test here because /dev/dri is absent. I read the author's reported NVIDIA/Intel test runs and the current hosted policy checks; product/GPU build jobs are skipped by path policy.

An output word that neither program stores kept the same sentinel in both
buffers and compared equal, so a missing store could pass. The device and
software runs now start from different words.

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

Copy link
Copy Markdown
Contributor Author

@MarcZX10 right, a missing four-word store would have passed. 2a9175b6a6 fills the two output buffers with different markers (0xdeadbeef for the device form, 0xfeedface for the software form), so any slot that either program leaves unwritten compares unequal; the all-of check is gone. Mutation check: moving the second buffer_store_dwordx4 onto the first one's offset (results 4-7 never written) now fails at once on both GPUs (v_pk_mul_f16 ... clamp ... is 0xdeadbeef, the software conversions give ...); restored, the test passes in 1.2 s (RTX 4090) / 1.1 s (UHD 770). Only the test file changed; conventions pass.

@MarcZX10 MarcZX10 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved after re-reviewing exact head 2a9175b6a6a2cea63eb7dce6aa62a4d77ea9bee1 against current main 4e43321bd6ceccdc1ae28568bbbc35d9a6446464.

The new per-program output markers close the remaining test gap: a word left unwritten by either implementation now differs from the other buffer, and the author's mutation check that omits the second four-word store fails on both RTX 4090 and UHD 770. The author reports the restored exact-head test passing on both GPUs; the previously reported focused validation-layer runs and no-new-VUID results cover the unchanged production implementation. My current-head conventions and git diff --check pass. I did not independently run the GPU test because this host has no /dev/dri device. I also re-ran tools/trace_digest.py for both attached performance traces earlier; both digests matched the published reports after newline normalization.

AI-assisted review.

@tibzejoker

Copy link
Copy Markdown
Contributor Author

Thanks @MarcZX10 for the reviews and for rerunning the digests.

@imellag imellag left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

macOS / MoltenVK data point. Apple M4 Pro, macOS 27.2, MoltenVK 1.4.2, x86_64 Release under Rosetta. main doesn't run titles on macOS yet, so the base is a support branch, not main: main 750cf6cf48 + only the macOS boot PRs #7631 #10552 #11249 #11447 #11554 #10669 #7804, + #12529 (the revert of #10337, which roughly doubles the GPU time on this GPU and would hide smaller changes). Change: that base + eight open shader performance PRs merged together (#12287 #11952 #12299 #12280 #11368 #11265 #8716 #8958, all clean merges). I did not measure each PR alone.

Two alternated runs per side, warm shader cache, APS5_TRACE_SUMMARY_EVERY=10:

base with the eight PRs
God of War Sons of Sparta (PPSA28997), fps from 30 s 51.5 / 51.8 51.6 / 51.8
GoW GPU ms/present, gameplay before the fight / in the fight 13.6 / 19.6 13.5 / 19.5
Planet of Lana (PPSA19450), fps from 85 s 32.2 / 32.7 32.5 / 32.6

Together they don't raise the GPU time on MoltenVK. I'm checking open shader performance PRs on this GPU because #10337 was measured on RADV and doubled it here.

Focused tests below ran with ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation on the same two trees; the layer was found and both logs have the same VUID- lines (12 VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605, 20 VUID-vkCreateDescriptorSetLayout-support-09582, from tests that fail the same way on the base).

This PR on MoltenVK: the change is inert here. MoltenVK has no native f16 conversions with RTE rounding and kept f16 denormals, so the new agc_driver_float16_unused_nan_payloads and agc_driver_packed_float16_native are skipped (the device has no native f16 conversions with RTE rounding and kept f16 denormals), as are agc_driver_float16_dot and agc_driver_fp16_overflow. The software paths it leaves alone pass on the base and with the change: agc_driver_nan_quieting, agc_driver_packed_alu, agc_driver_f16_fma_denormal_modes, agc_driver_f16_fma_invalid_nan, agc_driver_float16_fma_rounding, agc_driver_fma_mix_f16_denormal_modes. No GPU regression in the group. Nothing on macOS for or against the change itself.

AI-assisted: yes (Claude Code ran the builds, runs and tests and drafted this review)

@TechLevelZero

TechLevelZero commented Oct 30, 2026 •

Copy link
Copy Markdown
Contributor

✅ NVIDIA GPU test: passed

PR head 2a9175b merged onto main 93dbd0f · tested 2026-11-02 07:20 UTC · 35 min

GPU NVIDIA RTX 2000 Ada Generation, driver 595.99.02, Vulkan 1.4.329
Host Ubuntu 26.04.1 LTS, kernel 7.0.0-38-generic, container ubuntu:24.04
Logs build and test logs for this run
Build ✅ passed (Release, -DANYPS5_ENABLE_SPIRV_TOOLS=ON)
Tests ✅ 2,448 passed · 5 failed (0 new, 3 also fail on main, 2 unstable on main) · 45 skipped (ANYPS5_REQUIRE_VULKAN=1)
Test setup CPU-only tests without a GPU; GPU tests (and any CPU-only test that failed or skipped without one) once on the GPU with VK_LAYER_KHRONOS_validation and ANYPS5_NO_SHADER_CACHE=1; main ran the same way
Test selection 2,498 of 3,165 tests run: the ones this change can reach, chosen by tools/select_tests.py from main's build graph
Vulkan validation ✅ no new validation IDs vs main (933 tests run on the GPU)
Slow tests (>30 s, GPU tests timed with validation layers) ⚠️ agc_driver_pipeline_journal 120.2 s, ⚠️ agc_driver_graphics_maps_fallback 31.2 s, ⚠️ agc_driver_graphics 30.6 s
Unstable on main (2, these also failed in recent main runs on this machine; not counted against this PR)
agc_driver_job_manager_compute_capacity: failed in 1 of the last 10 main runs
agc_driver_pipeline_journal: failed in 1 of the last 10 main runs
Failing on main too (3, not counted against this PR)
agc_driver_image_table
guest_mmap_executable
guest_socket_options_multicast
Validated but not compared (1, not in the main baseline's validation pass)
agc_driver_float16_unused_nan_payloads
Validation IDs already present on main (5 IDs in 483 tests)
VUID-VkDeviceCreateInfo-pNext-pNext (483 tests): agc_driver, agc_driver_address_passes, agc_driver_alias_copy_label, agc_driver_alpha_to_coverage, agc_driver_array_layer_target, agc_driver_bc_block_copy, agc_driver_bda_anchor_reads, agc_driver_bda_span_reads, and 475 more
VUID-vkCmdCopyImage-srcImage-01548 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-vkCmdCopyImage-srcImage-01551 (7 tests): agc_driver_depth_clear_hle, agc_driver_depth_plane_buffer_memory, agc_driver_depth_plane_partial_store, agc_driver_depth_plane_sampling, agc_driver_depth_storage_dispatch, agc_driver_depth_surface_from_storage, agc_driver_dispatch_move_depth
VUID-VkPipelineColorBlendStateCreateInfo-pAttachments-00605 (1 test): agc_driver_prepared_exports
VUID-VkShaderModuleCreateInfo-pCode-08737 (1 test): agc_driver_image_sample_derivatives
Skipped (45)
agc_driver_bda_reads_shadowed_results: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_bda_reads_shadowed_results_per_build: skipped, the device reports watched host memory written when it imports or reads it
agc_driver_sample_order_grid_reference: skipped, the device does not keep depth drawn at custom sample locations at those locations, even after a transition that names them: the composite of uniform renders holds the guest's depth in 0 of 3005 sloped samples, so no path can be compared with it bit for bit
agc_host_import_sparse: skipped, host imports are unavailable: /dev/udmabuf is not accessible
agc_import_settle_initial_dcc: skipped, the device has no deferred settle of host imports: imports under 1 MiB wait for one only on Windows
agc_unit_shadow_completion_store: skipped, the device reports watched host memory written when it imports or reads it
guest_http_uri_locale: 
guest_net_ctl: skipped, the host has no IPv4 address
guest_video_out_color_space: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_hdr: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_high_frame_rate: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_output_mode: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_resolution_status: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
guest_video_out_wqhd_detection: skipped, no display or Vulkan device: SDL_InitSubSystem(VIDEO | GAMECONTROLLER) failed: wayland,x11 not available
video_out_flip_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_backtoback: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_controls: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_cpuflip_in_order: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_dcc: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_deferred: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_aftergpu: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_eop_main: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_out_flip_fps_limit: skipped, no display or Vulkan device: DisplayWindow: Vulkan support is either not configured in SDL or not available in current SDL video driver (offscreen) or platform
video_o
Reproduce
git fetch origin 2a9175b6a6a2cea63eb7dce6aa62a4d77ea9bee1 93dbd0f82aea684e95f15f06e7d42c0064af18cf
git checkout 93dbd0f82aea684e95f15f06e7d42c0064af18cf && git merge --no-ff 2a9175b6a6a2cea63eb7dce6aa62a4d77ea9bee1
git submodule update --init --recursive
cmake -S . -B build -G Ninja -DCMAKE_BUILD_TYPE=Release -DBUILD_TESTING=ON -DANYPS5_ENABLE_SPIRV_TOOLS=ON
cmake --build build && cmake --build build --target libs
VK_DRIVER_FILES=/nonexistent/icd.json ctest --test-dir build -E '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 32 --output-on-failure
ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation \
  ctest --test-dir build -R '^(agc|shader|recompiler|videoout|video_out|guest_video_out|vulkan)|gpu' --timeout 120 --parallel 4 --output-on-failure
# failures are then re-run alone: --timeout 360 --parallel 2; new validation IDs: the test alone, twice, --parallel 1

Automated build and test on a contributor's NVIDIA machine; it does not replace upstream CI. A new comment is posted for each new push. Full logs.

@savvaniss

Copy link
Copy Markdown
Contributor

@tibzejoker here is a run on AMD's Windows driver, which Tested lists as not covered. On this card the change is only reached with #11483 merged too, and there it passes.

Setup. RX 9070 XT (RDNA 4, 1002:7550), AMD driver 26.8.1, denormBehaviorIndependence = 32_BIT_ONLY, Windows 11 Pro 26300, WinLibs GCC 15.2.0 r7, Release, ANYPS5_ENABLE_SPIRV_TOOLS=ON. ANYPS5_GPU=AMD ANYPS5_REQUIRE_VULKAN=1 ANYPS5_NO_SHADER_CACHE=1 VK_INSTANCE_LAYERS=VK_LAYER_KHRONOS_validation (SDK 1.4.363). All merges were clean and local only. Each tree ran 3 times, alternating with its pair, and every run gave the same result.

main e49d43d625, with ^agc.*(f16|float16|pack|fma|minmax|med3|clamp) and -E '^agc_driver_bda_device$':

Tree Focused set agc_driver_float16_unused_nan_payloads VUID- lines
main + the new test file 78 pass, 1 skips skips 0
main + this PR (2a9175b6a6) 78 pass, 1 skips skips 0

The test's message is the device has no native f16 conversions with RTE rounding and kept f16 denormals. So on main this card doesn't take the changed path. That condition isn't met on a 32_BIT_ONLY device until #11483 lands.

The same main + #11483 (80cb2cd3ac), with that set plus f64|convert|interpolation|denorm, request_serializer, shader_memory and agc_float16_conversion:

Tree Focused set agc_driver_float16_unused_nan_payloads VUID- lines
+ the new test file 109 of 110 fails 3 of 3: packed f16, dx10 ieee ovfl still rebuilds the NaN of an f16 input whose payload the result never uses 0
+ this PR 110 of 110 passes 3 of 3: 20971520 results, 6152589 NaN halves 0

The failure is the check this PR is meant to satisfy, so it works as the red side.

AI-assisted: yes (Claude Code)

@tibzejoker

Copy link
Copy Markdown
Contributor Author

Thanks @savvaniss, that fills the gap in Tested: AMD's Windows driver on RDNA 4.

I'll add your table to the body. I'll also cite the RDNA 4 line on #11483, since it shows DenormPreserve 16 and 64 declared together with no device-dependent f64 skips.

AI-assisted: yes (Claude Code)

@oneandonlydean
oneandonlydean merged commit a38ce98 into boykopovar:main Nov 2, 2026
12 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* perf Performance change shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants