Skip to content

perf(shader): flush f32 denormals through the DenormFlushToZero execution mode - #6953

Closed
alfalfa-star wants to merge 1 commit into
boykopovar:mainfrom
alfalfa-star:perf/shader-native-f32-ftz
Closed

alfalfa-star wants to merge 1 commit into
boykopovar:mainfrom
alfalfa-star:perf/shader-native-f32-ftz

Conversation

@alfalfa-star

@alfalfa-star alfalfa-star commented Oct 16, 2026 •

Copy link
Copy Markdown
Contributor

What

PS5 shaders usually run with f32 denormals flushed to zero (the float mode in the shader registers). The recompiler emulates this by flushing the result of every f32 operation in SPIR-V, which adds compares and selects around most ALU instructions. In GPU-heavy titles such as Kena: Bridge of Spirits (PPSA01802) this makes the shaders noticeably more expensive.

With APS5_NATIVE_F32_FTZ=1, on a device that supports DenormFlushToZero for 32-bit floats (VK_KHR_shader_float_controls), a shader whose float mode flushes f32 denormals declares the DenormFlushToZero execution mode instead of emulating the flush. Shaders whose float mode keeps denormals are unchanged, and so is everything when the device lacks the capability. Shader validation now accepts the mode as a float-controls execution mode. APS5_NATIVE_F32_FTZ_PIXEL_ONLY=1 limits it to pixel shaders.

Off by default.

Tested

AMD Radeon RX 9070 XT (RADV GFX1201), Mesa 26.2.4, Arch Linux (kernel 7.2.9), Kena: Bridge of Spirits (PPSA01802).

  • ctest: no new failures (agc_driver_tessellation segfaults on main without this change; guest_kernel_fcntl is flaky under load and passes on rerun).
  • Kena: used in our local test build (RADV, RX 9070 XT) that runs the forest scene at about 24 fps, with no rendering differences seen. A measurement of this change alone on top of main will follow in a comment.

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 under Silent stubs in their library's technical debt file
  • 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
  • Unblocks: none (performance of PPSA01802)
  • GPUs tested: AMD Radeon RX 9070 XT (RADV GFX1201), Mesa 26.2.4, Arch Linux (kernel 7.2.9)
  • AI-assisted: yes

🤖 Generated with Claude Code

…tion mode

With APS5_NATIVE_F32_FTZ=1 on a device that supports the
DenormFlushToZero float-controls mode for 32-bit floats, shaders whose
float mode flushes f32 denormals declare DenormFlushToZero instead of
emulating the flush after every f32 operation. Shader validation accepts
the execution mode as a float-controls mode.
APS5_NATIVE_F32_FTZ_PIXEL_ONLY=1 limits it to pixel shaders.

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

Copy link
Copy Markdown

Other open pull requests touch the same code. Merging one will break the other, or both implement the same thing. If this one builds on them, list them in Depends on.

Pull request Conflicting files (lines on main) Same exports Same new files
PR 6767 core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp L329-331
PR 6941 core/shader/recompiler/Recompiler.cpp L533
PR 6947 core/shader/recompiler/Recompiler.cpp L533

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 1395 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4040 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4048 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 4072 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp
core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp
core/shader/recompiler/Translation/src/InstructionTranslator.cpp
PR 4339 core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 4472 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 4650 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 4891 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5021 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 5390 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5431 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5441 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5480 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5499 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 5500 core/shader/recompiler/Recompiler.cpp
PR 5609 core/shader/recompiler/Recompiler.cpp
PR 5925 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6177 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6188 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6222 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6235 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
PR 6259 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6412 core/shader/recompiler/Recompiler.cpp
PR 6416 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6503 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitter.hpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvModuleSetup.cpp
core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp
core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp
core/shader/recompiler/Translation/src/InstructionTranslator.cpp
core/shader/recompiler/Translation/src/VectorInstructions.cpp
PR 6539 core/shader/recompiler/Recompiler.cpp
PR 6612 core/libs/prx/libSceAgcDriver/Graphics/src/ShaderValidation.cpp
core/shader/recompiler/Recompiler.cpp
core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/Translation/src/InstructionTranslator.cpp
PR 6718 core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp
PR 6751 core/shader/recompiler/SpirvBackend/include/SpirvBackend/SpirvEmitterState.hpp
core/shader/recompiler/SpirvBackend/src/SpirvEmitter.cpp
PR 6762 core/shader/recompiler/Recompiler.cpp
PR 6879 core/shader/recompiler/Recompiler.cpp

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

@CnRJay CnRJay 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.

Off by default, so the risk is limited to the switch, but it isn't ready to be made default and has gaps now.

  • No test. The change alters the SPIR-V of every f32 shader when the switch is set, and nothing checks either the emitted OpExecutionMode ... DenormFlushToZero 32 or that results still match the emulated flush. A shader test that runs v_mul_f32/v_add_f32/v_mac_f32 on denormal inputs and outputs under both settings (the way agc_driver_mad_f32 does it) would show whether the driver flushes inputs as well as results. The execution mode lets the implementation flush operands too, which the emulated flush does not do for every instruction, so the results can differ on ops the old path left alone (conversions, min/max, compares).
  • APS5_NATIVE_F32_FTZ_PIXEL_ONLY changes the emitted module but is read in nativeFloat32FlushToZero(request), outside the request. The first switch reaches the cache key through supportedCapabilities; this one doesn't, so a disk cache written with it off serves a module built without the mode when it's on, and the other way around. Take both from the request.
  • state->capabilities.push_back(spv::CapabilityDenormFlushToZero) has no duplicate check, unlike the DenormPreserve line above it.
  • The measurement "will follow in a comment". The point of the PR is shader cost, so it needs GPU time per dispatch/draw (APS5_PROFILE_GPU=1 is fine for that, as #5353 shows) and the summary lines of main and this PR on the same scene in PPSA01802, warm cache. "About 24 fps in our local test build" is not that.

@alfalfa-star

Copy link
Copy Markdown
Contributor Author

Closing in favour of #6503, which does the same thing more safely. I compared the two on main 82ddae7 (Arch Linux, RX 9070 XT, RADV Mesa 26.2.4, Release):

AI-assisted: yes (Claude Code)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agc libSceAgc* shader core/shader

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants