diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxDenormalModes.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxDenormalModes.cpp new file mode 100644 index 0000000000..a9d0238d0b --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxDenormalModes.cpp @@ -0,0 +1,181 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 64; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +constexpr std::uint32_t Instructions = 5; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x1e140b04u, 0x20160b04u, 0xd551000cu, + 0x041a0b04u, 0xd554000du, 0x041a0b04u, 0xd557000eu, 0x041a0b04u, 0xe0781000u, 0x80010a02u, 0xe0701010u, + 0x80010e02u, 0xbf810000u, +}; + +constexpr const char* Names[Instructions] = {"v_min_f32", "v_max_f32", "v_min3_f32", "v_max3_f32", "v_med3_f32"}; + +struct MinMaxVector { + std::uint32_t operands[3]; + std::uint32_t inputsKept[Instructions]; + std::uint32_t inputsFlushed[Instructions]; +}; + +constexpr std::array Vectors{{ + {0x00000000u, 0x00000000u, 0x00000001u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000001u, 0x00000000u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {0x00000000u, 0x80000001u, 0x80000000u, {0x80000001u, 0x00000000u, 0x80000001u, 0x00000000u, 0x80000000u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x00000000u, 0x007fffffu, 0x7f800000u, {0x00000000u, 0x007fffffu, 0x00000000u, 0x7f800000u, 0x007fffffu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u}}, + {0x00000000u, 0x80800000u, 0x807fffffu, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x807fffffu}, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x80000000u}}, + {0x80000000u, 0x80000000u, 0x00000001u, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000001u, 0x80000000u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x80000000u, 0x80000001u, 0x807fffffu, {0x80000001u, 0x80000000u, 0x807fffffu, 0x80000000u, 0x80000001u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}}, + {0x80000000u, 0x807fffffu, 0x00000001u, {0x807fffffu, 0x80000000u, 0x807fffffu, 0x00000001u, 0x80000000u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x80000000u, 0x3f800000u, 0x807fffffu, {0x80000000u, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x80000000u}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x00000001u, 0x00000000u, 0x3f800000u, {0x00000000u, 0x00000001u, 0x00000000u, 0x3f800000u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}}, + {0x00000001u, 0x00000001u, 0x807fffffu, {0x00000001u, 0x00000001u, 0x807fffffu, 0x00000001u, 0x00000001u}, {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}}, + {0x00000001u, 0x007fffffu, 0x00000001u, {0x00000001u, 0x007fffffu, 0x00000001u, 0x007fffffu, 0x00000001u}, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}}, + {0x00000001u, 0x807fffffu, 0xff800000u, {0x807fffffu, 0x00000001u, 0xff800000u, 0x00000001u, 0x807fffffu}, {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u}}, + {0x00000001u, 0x80800000u, 0x3f800000u, {0x80800000u, 0x00000001u, 0x80800000u, 0x3f800000u, 0x00000001u}, {0x80800000u, 0x00000000u, 0x80800000u, 0x3f800000u, 0x00000000u}}, + {0x00000001u, 0xbf800000u, 0x807fffffu, {0xbf800000u, 0x00000001u, 0xbf800000u, 0x00000001u, 0x807fffffu}, {0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u}}, + {0x00000001u, 0xff800000u, 0x00000001u, {0xff800000u, 0x00000001u, 0xff800000u, 0x00000001u, 0x00000001u}, {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}}, + {0x80000001u, 0x00000000u, 0xff800000u, {0x80000001u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000001u}, {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u}}, + {0x80000001u, 0x00000001u, 0x3f800000u, {0x80000001u, 0x00000001u, 0x80000001u, 0x3f800000u, 0x00000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u}}, + {0x80000001u, 0x007fffffu, 0x807fffffu, {0x80000001u, 0x007fffffu, 0x807fffffu, 0x007fffffu, 0x80000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x80000001u, 0x00800000u, 0x00000001u, {0x80000001u, 0x00800000u, 0x80000001u, 0x00800000u, 0x00000001u}, {0x80000000u, 0x00800000u, 0x80000000u, 0x00800000u, 0x00000000u}}, + {0x80000001u, 0x80800000u, 0xff800000u, {0x80800000u, 0x80000001u, 0xff800000u, 0x80000001u, 0x80800000u}, {0x80800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0x80800000u}}, + {0x80000001u, 0xbf800000u, 0x3f800000u, {0xbf800000u, 0x80000001u, 0xbf800000u, 0x3f800000u, 0x80000001u}, {0xbf800000u, 0x80000000u, 0xbf800000u, 0x3f800000u, 0x80000000u}}, + {0x80000001u, 0xff800000u, 0x807fffffu, {0xff800000u, 0x80000001u, 0xff800000u, 0x80000001u, 0x807fffffu}, {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0x80000000u}}, + {0x007fffffu, 0x80000000u, 0x00000001u, {0x80000000u, 0x007fffffu, 0x80000000u, 0x007fffffu, 0x00000001u}, {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u}}, + {0x007fffffu, 0x00000001u, 0xff800000u, {0x00000001u, 0x007fffffu, 0xff800000u, 0x007fffffu, 0x00000001u}, {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u}}, + {0x007fffffu, 0x007fffffu, 0x3f800000u, {0x007fffffu, 0x007fffffu, 0x007fffffu, 0x3f800000u, 0x007fffffu}, {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u}}, + {0x007fffffu, 0x00800000u, 0x807fffffu, {0x007fffffu, 0x00800000u, 0x807fffffu, 0x00800000u, 0x007fffffu}, {0x00000000u, 0x00800000u, 0x80000000u, 0x00800000u, 0x00000000u}}, + {0x007fffffu, 0x3f800000u, 0x00000001u, {0x007fffffu, 0x3f800000u, 0x00000001u, 0x3f800000u, 0x007fffffu}, {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u}}, + {0x007fffffu, 0xbf800000u, 0xff800000u, {0xbf800000u, 0x007fffffu, 0xff800000u, 0x007fffffu, 0xbf800000u}, {0xbf800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xbf800000u}}, + {0x007fffffu, 0xff800000u, 0x3f800000u, {0xff800000u, 0x007fffffu, 0xff800000u, 0x3f800000u, 0x007fffffu}, {0xff800000u, 0x00000000u, 0xff800000u, 0x3f800000u, 0x00000000u}}, + {0x807fffffu, 0x80000000u, 0x807fffffu, {0x807fffffu, 0x80000000u, 0x807fffffu, 0x80000000u, 0x807fffffu}, {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}}, + {0x807fffffu, 0x80000001u, 0x00000001u, {0x807fffffu, 0x80000001u, 0x807fffffu, 0x00000001u, 0x80000001u}, {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u}}, + {0x807fffffu, 0x007fffffu, 0xff800000u, {0x807fffffu, 0x007fffffu, 0xff800000u, 0x007fffffu, 0x807fffffu}, {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u}}, + {0x807fffffu, 0x00800000u, 0x3f800000u, {0x807fffffu, 0x00800000u, 0x807fffffu, 0x3f800000u, 0x00800000u}, {0x80000000u, 0x00800000u, 0x80000000u, 0x3f800000u, 0x00800000u}}, + {0x807fffffu, 0x3f800000u, 0x807fffffu, {0x807fffffu, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x807fffffu}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x807fffffu, 0x7f800000u, 0x00000001u, {0x807fffffu, 0x7f800000u, 0x807fffffu, 0x7f800000u, 0x00000001u}, {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00000000u}}, + {0x807fffffu, 0xff800000u, 0xff800000u, {0xff800000u, 0x807fffffu, 0xff800000u, 0x807fffffu, 0xff800000u}, {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xff800000u}}, + {0x00800000u, 0x80000001u, 0x00000000u, {0x80000001u, 0x00800000u, 0x80000001u, 0x00800000u, 0x00000000u}, {0x80000000u, 0x00800000u, 0x80000000u, 0x00800000u, 0x00000000u}}, + {0x00800000u, 0x007fffffu, 0xbf800000u, {0x007fffffu, 0x00800000u, 0xbf800000u, 0x00800000u, 0x007fffffu}, {0x00000000u, 0x00800000u, 0xbf800000u, 0x00800000u, 0x00000000u}}, + {0x00800000u, 0x80800000u, 0x007fffffu, {0x80800000u, 0x00800000u, 0x80800000u, 0x00800000u, 0x007fffffu}, {0x80800000u, 0x00800000u, 0x80800000u, 0x00800000u, 0x00000000u}}, + {0x80800000u, 0x00000000u, 0x807fffffu, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x807fffffu}, {0x80800000u, 0x00000000u, 0x80800000u, 0x00000000u, 0x80000000u}}, + {0x80800000u, 0x80000001u, 0x007fffffu, {0x80800000u, 0x80000001u, 0x80800000u, 0x007fffffu, 0x80000001u}, {0x80800000u, 0x80000000u, 0x80800000u, 0x00000000u, 0x80000000u}}, + {0x80800000u, 0x807fffffu, 0x80000000u, {0x80800000u, 0x807fffffu, 0x80800000u, 0x80000000u, 0x807fffffu}, {0x80800000u, 0x80000000u, 0x80800000u, 0x80000000u, 0x80000000u}}, + {0x80800000u, 0x3f800000u, 0x007fffffu, {0x80800000u, 0x3f800000u, 0x80800000u, 0x3f800000u, 0x007fffffu}, {0x80800000u, 0x3f800000u, 0x80800000u, 0x3f800000u, 0x00000000u}}, + {0x3f800000u, 0x80000000u, 0x807fffffu, {0x80000000u, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x80000000u}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x3f800000u, 0x80000001u, 0x3f800000u, {0x80000001u, 0x3f800000u, 0x80000001u, 0x3f800000u, 0x3f800000u}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f800000u}}, + {0x3f800000u, 0x807fffffu, 0x807fffffu, {0x807fffffu, 0x3f800000u, 0x807fffffu, 0x3f800000u, 0x807fffffu}, {0x80000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x80000000u}}, + {0x3f800000u, 0xbf800000u, 0x007fffffu, {0xbf800000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x007fffffu}, {0xbf800000u, 0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u}}, + {0xbf800000u, 0x00000001u, 0x80000001u, {0xbf800000u, 0x00000001u, 0xbf800000u, 0x00000001u, 0x80000001u}, {0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u}}, + {0xbf800000u, 0x007fffffu, 0x00000000u, {0xbf800000u, 0x007fffffu, 0xbf800000u, 0x007fffffu, 0x00000000u}, {0xbf800000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u}}, + {0xbf800000u, 0x807fffffu, 0xbf800000u, {0xbf800000u, 0x807fffffu, 0xbf800000u, 0x807fffffu, 0xbf800000u}, {0xbf800000u, 0x80000000u, 0xbf800000u, 0x80000000u, 0xbf800000u}}, + {0xbf800000u, 0x7f800000u, 0x007fffffu, {0xbf800000u, 0x7f800000u, 0xbf800000u, 0x7f800000u, 0x007fffffu}, {0xbf800000u, 0x7f800000u, 0xbf800000u, 0x7f800000u, 0x00000000u}}, + {0x7f800000u, 0x00000001u, 0x80800000u, {0x00000001u, 0x7f800000u, 0x80800000u, 0x7f800000u, 0x00000001u}, {0x00000000u, 0x7f800000u, 0x80800000u, 0x7f800000u, 0x00000000u}}, + {0x7f800000u, 0x007fffffu, 0x007fffffu, {0x007fffffu, 0x7f800000u, 0x007fffffu, 0x7f800000u, 0x007fffffu}, {0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u}}, + {0x7f800000u, 0x00800000u, 0x80000001u, {0x00800000u, 0x7f800000u, 0x80000001u, 0x7f800000u, 0x00800000u}, {0x00800000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00800000u}}, + {0x7f800000u, 0xff800000u, 0x007fffffu, {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x007fffffu}, {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x00000000u}}, + {0xff800000u, 0x00000001u, 0xff800000u, {0xff800000u, 0x00000001u, 0xff800000u, 0x00000001u, 0xff800000u}, {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u}}, +}}; + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +bool Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode floatMode) { + Input.fill(0u); + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + std::copy(std::begin(Vectors[lane].operands), std::end(Vectors[lane].operands), Input.begin() + lane * Inputs); + } + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + request.context.floatMode = floatMode; + std::optional result; + try { + result.emplace(ShaderRecompiler::Recompile(request)); + } catch (const std::exception& error) { + const std::string message = error.what(); + const bool keepsDenormals = request.target.float32DenormPreserve && !request.target.float32FlushToZero; + Require(!keepsDenormals && ((floatMode.floatMode >> 4u) & 3u) == 1u && message.find("needs DenormPreserve 32") != std::string::npos, + "f32 min/max FLOAT_MODE " + Hex(floatMode.floatMode) + ": " + message); + return false; + } + device.Dispatch(*result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); + return true; +} + +void Check(std::uint32_t denormals, bool ieee) { + for (std::uint32_t lane = 0; lane < Vectors.size(); ++lane) { + const auto& vector = Vectors[lane]; + for (std::uint32_t instruction = 0; instruction < Instructions; ++instruction) { + const auto actual = Output[lane * Results + instruction]; + const auto expected = denormals == 1u ? vector.inputsKept[instruction] : vector.inputsFlushed[instruction]; + Require(actual == expected, std::string(Names[instruction]) + " f32 denormal mode " + std::to_string(denormals) + (ieee ? ", IEEE=1" : ", IEEE=0") + " of " + + Hex(vector.operands[0]) + ", " + Hex(vector.operands[1]) + ", " + Hex(vector.operands[2]) + " is " + Hex(actual) + + ", expected " + Hex(expected)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + std::uint32_t checked = 0u; + for (const std::uint32_t denormals : {1u, 2u}) { + for (const bool ieee : {false, true}) { + if (!Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u | denormals << 4u, true, ieee, false})) continue; + ++checked; + Check(denormals, ieee); + } + } + std::printf("f32 min/max denormal mode 1 and 2 tests passed (%u of 4 modes run; mode 1 needs DenormPreserve 32)\n", checked); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 31395cd973..f94148689f 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -61,11 +61,18 @@ std::uint32_t TranslationContext::f32DenormalFlushFor(const RdnaInstruction& ins flush = 3u; break; case RdnaOpcode::VMullitF32: + flush = 3u; + break; case RdnaOpcode::VMinF32: case RdnaOpcode::VMaxF32: case RdnaOpcode::VMin3F32: case RdnaOpcode::VMax3F32: case RdnaOpcode::VMed3F32: + if (!floatMode.has_value() || ((floatMode->floatMode >> 4u) & 1u) == 0u) return 3u; + if (((floatMode->floatMode >> 4u) & 3u) == 1u && (!float32DenormPreserve || float32FlushToZero)) { + throw std::runtime_error("f32 denormal mode 1 at pc " + std::to_string(inst.programCounter) + " needs DenormPreserve 32, which the device does not offer for this shader"); + } + return 0u; case RdnaOpcode::VFractF32: case RdnaOpcode::VFloorF32: case RdnaOpcode::VCeilF32: diff --git a/docs/dev/debt/shader.md b/docs/dev/debt/shader.md index db3130a1f6..6919cefeed 100644 --- a/docs/dev/debt/shader.md +++ b/docs/dev/debt/shader.md @@ -133,6 +133,7 @@ - [32-bit denormal execution mode](../../../core/shader/recompiler/Recompiler.cpp) (shader recompiler) - the hardware f32 flush that the startup probe chooses (`DenormFlushToZero 32`) is declared only in shaders whose `FLOAT_MODE` flushes f32 denormals (field 0, or no known float mode). A shader that keeps them on either side (field 1, 2 or 3) declares `DenormPreserve 32` when the device has `shaderDenormPreserveFloat32` with a `denormBehaviorIndependence` other than NONE; on other devices it gets the driver's default f32 denormal handling, which flushes the denormals it keeps on MoltenVK. - [v_log_f32 and v_log_f16](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) keep the driver's log2 unless, for inputs within 1/8 of 1.0, it is more than 4 ULP from an eight-term series in x - 1, which then replaces it. `v_log_f16` goes through the same f32 lowering and is rounded to half. The series is evaluated for every `v_log` and chosen with `OpSelect` rather than a branch, so its seven fused multiply-adds and two multiplies are an unconditional cost. Measured on RDNA2 over all 3,145,728 f32 inputs within 1/8 of 1.0 (gfx1035, IEEE 0, f32 denormals flushed), the hardware is within 1 ULP of the correctly rounded log2 and the series within 2 ULP of it and within 3 ULP of the hardware (2 except at 0x3f752e47, 0x3f7baab0 and 0x3f7bc4fb), so a kept driver value is within 7 ULP of the hardware and a driver that returns the hardware's bits never triggers the gate; RADV (Mesa 26.2.4) returns the hardware's bits on the 9,280 such inputs measured on gfx1036; NVIDIA (615.71.09) was up to 23,947,836 ULP off at 1 - 2^-24 and is within 6 ULP inside the band after the change; outside it its own log2 stays up to 9 ULP off (measured up to 1.375). Over every f16 input, the change moves one `v_log_f16` result on NVIDIA (0x3bed, 1 ULP, onto the hardware's bits) and none on RADV. - [v_add_f32, v_sub_f32, v_subrev_f32, v_mul_f32 and v_mul_legacy_f32 in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) throw with a directed f32 round mode, as the host rounds these sums and products to nearest even (a difference that modes 0 and 3 also have then), and when the module cannot declare `DenormPreserve 32` (no `shaderDenormPreserveFloat32`, or the hardware f32 flush in use); the PS5's gfx1013 is not measured. Otherwise mode 1 keeps denormal inputs and flushes a result below 2^-126 to a zero of its sign (a product only when it stays below 2^-126 after rounding to 24 bits with an unbounded exponent), and mode 2 flushes denormal inputs and keeps a denormal result. Measured on RDNA2 (gfx1030, RX 6800) over 1,296 operand pairs, 896 products around 2^-126 and 1,632 with a constant factor, at IEEE 0 and 1 in every f32 denormal mode, with DX10_CLAMP=1, f16 denormals kept and FP16_OVFL=0. +- [f32 min and max in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) (`v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32`, `v_med3_f32`) throw in mode 1 when the module cannot declare `DenormPreserve 32` (no `shaderDenormPreserveFloat32`, or the hardware f32 flush in use), and mode 3 keeps depending on the driver there; the PS5's gfx1013 is not measured. They follow only bit 0 of the `FLOAT_MODE` f32 denormal field: mode 2 flushes denormal inputs to zeros of their sign as mode 0 does, and mode 1 keeps them and returns a selected denormal unflushed as mode 3 does. Measured on RDNA2 (gfx1030, RX 6800) over the 2,744 operand triples of ±0, ±2^-149, ±0x007fffff, ±2^-126, ±1, ±infinity, a quiet and a signaling NaN at IEEE 0 and 1 in each f32 denormal mode, round to nearest even, DX10_CLAMP=1, f16 denormals kept, FP16_OVFL=0: modes 1 and 3 give identical results, and so do modes 2 and 0. - [Ordered not-equal, less-than and greater-than compares](../../../core/shader/recompiler/SpirvBackend/src/SpirvAlu/SpirvAluEmitterMath.cpp) (shader recompiler) - `FPOrdNotEqual32` is emitted as `OpFUnordNotEqual`, and `FPOrdLessThan32` and `FPOrdGreaterThan32` as their ordered forms, each with both operands checked for NaN on their bits; the results are the same for every input. The AMD Windows driver 32.0.13058.8001 (Radeon 860M, RDNA 3.5) returns 1 for a NaN operand when any float form of not-equal feeds a ballot on a 32-wide subgroup (#825), and so does a less-than or greater-than against an infinity the driver sees as a constant (#3536), presumably rewritten into a not-equal by its compiler - [16-bit integer multiplies](../../../core/shader/recompiler/Translation/src/IntegerInstructions.cpp) (`v_mul_lo_u16`, `v_pk_mul_lo_u16`) (shader recompiler) - a factor taken from the low half of its source is read as the low 24 bits of the register, not the low 16; the low 16 bits of the product are the same for every input. The AMD Windows driver 32.0.21045.5002 (RX 6700 XT, RDNA 2) stores the full 32-bit product when two values it knows fit in 16 bits are multiplied, the product is reduced to 16 bits and nothing else is merged into the stored value: `v_mul_lo_u16` into a register whose high half is zero, also with factors masked to 16 bits by the program, and `v_pk_mul_lo_u16` whose high lanes are zero wrote 0xfffe0001 for 0xffff * 0xffff where the GPU writes 0x00000001. Reducing the product with `v_and_b32`, `v_bfe_u32` or a shift pair loses the same bits; factors read as 24-bit fields do not. The [constant folder](../../../core/shader/recompiler/Optimization/src/ConstantFolder.cpp) keeps a field read that a multiply uses, even when the program already masked the factor to 16 bits, since folding it away brings the full product back (#8271). Measured against the same instructions run through the [hardware oracle](../HW_ORACLE.md) on that GPU; other drivers are not checked - [f32 compares in denormal modes 1 and 2](../../../core/shader/recompiler/Translation/src/VectorInstructions.cpp) (`v_cmp_X_f32` and `v_cmpx_X_f32` with X = eq, ge, gt, le, lg, lt, neq, nge, ngt, nle, nlg, nlt) flush their f32 denormal inputs by bit 0 of the `FLOAT_MODE` f32 denormal field: mode 1 keeps them as mode 3 does, and mode 2 flushes them as mode 0 does. Measured on RDNA2 (gfx1030, Radeon RX 6800, AMD Windows driver 32.0.21045.5002 through HIP) over 484 operand pairs of zeros, denormals, minimum normals, ones, the largest finite value, infinities and quiet and signaling NaNs of both signs, in VOPC form, at IEEE=0 and IEEE=1 with each f32 denormal mode 0 to 3, f32 round to nearest even, DX10_CLAMP=1, f16 denormals kept, f16 round to nearest even and FP16_OVFL=0. The PS5's gfx1013 is not measured.