diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp new file mode 100644 index 0000000000..c78f91002f --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32ArithmeticNan.cpp @@ -0,0 +1,238 @@ +#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 +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e140280u, 0x7e160280u, 0x7e180280u, + 0x7e1a0280u, 0x7e1c0280u, 0x7e1e0280u, 0x7e200280u, 0x7e220280u, 0x7e240280u, 0x7e260280u, 0x7e280280u, + 0x7e2a0280u, 0x7e2c0280u, 0x7e2e0280u, 0x7e300280u, 0x7e320280u, 0x06140b04u, 0x08160b04u, 0x0a180b04u, + 0x101a0b04u, 0xd54b000eu, 0x041a0b04u, 0x7e1e0306u, 0x561e0b04u, 0x0e200b04u, 0xd5400011u, 0x041a0b04u, + 0x58240b04u, 0x7f812345u, 0x5a260b04u, 0xffc12345u, 0xcc200014u, 0x041a0b04u, 0xd5030215u, 0x22020b04u, + 0xd5080016u, 0x0a020b04u, 0xd54b0017u, 0xa41a0b04u, 0xd5040118u, 0x42020b04u, 0xd5038019u, 0x02020b04u, + 0xe0781000u, 0x80010a03u, 0xe0781010u, 0x80010e03u, 0xe0781020u, 0x80011203u, 0xe0781030u, 0x80011603u, + 0xbf810000u, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x7f812345u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0xffa00001u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x7f812345u, 0x00000000u}, + {0x7fc00003u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0xffc12345u, 0x3f800000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0xffc12345u, 0x00000000u}, + {0x7f812345u, 0xffa00001u, 0x7fc00003u, 0x00000000u}, + {0x7fc00003u, 0xffa00001u, 0x7f812345u, 0x00000000u}, + {0xffc12345u, 0x7fc00003u, 0xffa00001u, 0x00000000u}, + {0xffa00001u, 0x7f812345u, 0xffc12345u, 0x00000000u}, + {0x7f800000u, 0x00000000u, 0x7fc00003u, 0x00000000u}, + {0x00000000u, 0xff800000u, 0x7f812345u, 0x00000000u}, + {0x7f800000u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x7f800000u, 0xff800000u, 0x3f800000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0x7f812345u, 0x3f800000u, 0x00000000u}, + {0xffa00001u, 0x00000000u, 0x3f800000u, 0x00000000u}, + {0x00000000u, 0xffc12345u, 0x7f812345u, 0x00000000u}, + {0x7fc00003u, 0x80000000u, 0xffc12345u, 0x00000000u}, + {0x7f800000u, 0x3f800000u, 0xff800000u, 0x00000000u}, + {0x3f800000u, 0x7f800000u, 0xff800000u, 0x00000000u}, + {0xbf800000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x7fc00003u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0xffa00001u, 0x00000000u}, + {0x3f800000u, 0xbf800000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x00000000u}, + {0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x00000000u}, + {0x7f812345u, 0x7f812345u, 0x7f812345u, 0x00000000u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t ExpectedIeee[32][16] = { + {0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x00000000u}, + {0xffe00001u, 0x7fe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0x7fc12345u, 0xffe00001u, 0xffe00001u, 0x7fe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x00000000u, 0x3f800000u, 0xffc12345u, 0x40000000u, 0x3f800000u}, + {0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x00000000u}, + {0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0xffc12345u, 0xffc12345u, 0x3f800000u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x3f800000u, 0x7fc12345u, 0x40000000u, 0x3f800000u}, + {0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x00000000u}, + {0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x00000000u}, + {0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0xffe00001u, 0xffe00001u, 0x7fe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0x7fe00001u, 0xffe00001u, 0x7fe00001u, 0x7fe00001u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x7fc00003u, 0x7fc12345u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x7fc12345u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0xffc00000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7fc12345u, 0xffc12345u, 0xff800000u, 0xffc00000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0x00000000u}, + {0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7fc12345u, 0xffc12345u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0xff800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7fc12345u, 0xffc12345u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0x00000000u}, + {0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u, 0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0xffe00001u, 0xffe00001u, 0x7fe00001u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0x00000000u, 0x3f800000u, 0xffe00001u, 0xffe00001u, 0xffe00001u, 0x7fe00001u, 0xffe00001u, 0x7fe00001u, 0x7fe00001u, 0x00000000u}, + {0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u}, + {0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x00000000u, 0xffc12345u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7fc12345u, 0xffc12345u, 0xffc00000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7fc12345u, 0xffc12345u, 0xffc00000u, 0x7f800000u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xffc00000u, 0x7fc12345u, 0xffc12345u, 0xffc00000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x7fc00003u, 0x7fc00003u, 0x00000000u, 0x7fc00003u, 0x7fc12345u, 0xffc12345u, 0x7fc00003u, 0x3f800000u, 0x00000000u, 0xffc00003u, 0x3f800000u, 0x3f800000u}, + {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffe00001u, 0xffe00001u, 0x00000000u, 0xffe00001u, 0x7fc12345u, 0xffc12345u, 0xffe00001u, 0x00000000u, 0x00000000u, 0x7fe00001u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x40000000u, 0xc0000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x7fc12345u, 0xffc12345u, 0xbf800000u, 0x00000000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x40000000u, 0x3f800000u, 0x40000000u, 0x7fc12345u, 0xffc12345u, 0x40000000u, 0x00000000u, 0x3f800000u, 0xc0000000u, 0x40000000u, 0x3f800000u}, + {0x00000000u, 0xc0000000u, 0x40000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x7fc12345u, 0xffc12345u, 0xbf800000u, 0x40000000u, 0xbf800000u, 0x3f800000u, 0x40000000u, 0x00000000u}, + {0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7fc12345u, 0xffc12345u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u}, + {0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x00000000u}, + {0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u, 0xffc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u} +}; +constexpr std::uint32_t ExpectedNoIeee[32][16] = { + {0x7f812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0x00000000u}, + {0xffa00001u, 0x7fa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0x7f812345u, 0xffa00001u, 0xffa00001u, 0x7fa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f812345u, 0x7f812345u, 0x3f800000u, 0x7f812345u, 0x7f812345u, 0xffc12345u, 0x7f812345u, 0x00000000u, 0x40000000u, 0xff812345u, 0x40000000u, 0x3f800000u}, + {0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x00000000u}, + {0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7f812345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0xffc12345u, 0xffc12345u, 0x3f800000u, 0xffc12345u, 0x7f812345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x40000000u, 0x7fc12345u, 0x40000000u, 0x3f800000u}, + {0x7f812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0x00000000u}, + {0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x00000000u}, + {0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0xffa00001u, 0xffa00001u, 0x7fa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0x7fa00001u, 0xffa00001u, 0x7fa00001u, 0x7fa00001u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x7fc00003u, 0x7f812345u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x7f812345u, 0x7f812345u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x7f812345u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0xffc00000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7f812345u, 0xffc12345u, 0xff800000u, 0xffc00000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0x00000000u}, + {0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7f812345u, 0xffc12345u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0xff800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f812345u, 0xffc12345u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0x00000000u}, + {0x7f812345u, 0xff812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x00000000u, 0x3f800000u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x00000000u}, + {0xffa00001u, 0xffa00001u, 0x7fa00001u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0x00000000u, 0x3f800000u, 0xffa00001u, 0xffa00001u, 0xffa00001u, 0x7fa00001u, 0xffa00001u, 0x7fa00001u, 0x7fa00001u, 0x00000000u}, + {0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x7f812345u, 0x7f812345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u}, + {0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0x00000000u, 0xffc12345u, 0x7fc00003u, 0x7fc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0xffc00003u, 0x7fc00003u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7f812345u, 0xffc12345u, 0xffc00000u, 0xff800000u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0xffc00000u, 0x7f812345u, 0xffc12345u, 0xffc00000u, 0x7f800000u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0xffc00000u, 0xff800000u, 0xffc00000u, 0x7f812345u, 0xffc12345u, 0xffc00000u, 0x7f800000u, 0xff800000u, 0xffc00000u, 0x7f800000u, 0x3f800000u}, + {0x3f800000u, 0xbf800000u, 0x3f800000u, 0x00000000u, 0x7fc00003u, 0x7fc00003u, 0x00000000u, 0x7fc00003u, 0x7f812345u, 0xffc12345u, 0x7fc00003u, 0x3f800000u, 0x00000000u, 0xffc00003u, 0x3f800000u, 0x3f800000u}, + {0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffa00001u, 0xffa00001u, 0x00000000u, 0xffa00001u, 0x7f812345u, 0xffc12345u, 0xffa00001u, 0x00000000u, 0x00000000u, 0x7fa00001u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x40000000u, 0xc0000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x7f812345u, 0xffc12345u, 0xbf800000u, 0x00000000u, 0xc0000000u, 0x3f800000u, 0x00000000u, 0x00000000u}, + {0x40000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x40000000u, 0x40000000u, 0x3f800000u, 0x40000000u, 0x7f812345u, 0xffc12345u, 0x40000000u, 0x00000000u, 0x40000000u, 0xc0000000u, 0x40000000u, 0x3f800000u}, + {0x00000000u, 0xc0000000u, 0x40000000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u, 0x7f812345u, 0xffc12345u, 0xbf800000u, 0x40000000u, 0xc0000000u, 0x3f800000u, 0x40000000u, 0x00000000u}, + {0x7f800000u, 0xffc00000u, 0xffc00000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f812345u, 0xffc12345u, 0x7f800000u, 0xffc00000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u}, + {0x7f812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0xff812345u, 0x7f812345u, 0x00000000u}, + {0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f812345u, 0xffc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u} +}; +constexpr const char* Names[16] = { + "v_add_f32", + "v_sub_f32", + "v_subrev_f32", + "v_mul_f32", + "v_fma_f32", + "v_fmac_f32", + "v_mul_legacy_f32", + "v_fma_legacy_f32", + "v_fmamk_f32", + "v_fmaak_f32", + "v_fma_mix_f32", + "v_add_f32 -v4 |v5|", + "v_mul_f32 mul:2", + "v_fma_f32 -v4 v5 -v6", + "v_sub_f32 |v4| -v5", + "v_add_f32 clamp" +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +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; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 arithmetic nan: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +bool IsNan32(std::uint32_t value) { + return (value & 0x7fffffffu) > 0x7f800000u; +} + +bool IsNan16(std::uint32_t value) { + return (value & 0x7fffu) > 0x7c00u; +} + +void Run(AgcDriver::VulkanDevice& device, const std::optional& floatMode) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * 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}, 0u, {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; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const std::uint32_t (&expected)[32][16], const char* mode) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + const std::string name = std::string(mode) + " " + Names[i]; + Expect(tid, out[i], expected[tid][i], name.c_str()); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device, std::nullopt); + Check(ExpectedNoIeee, "no float mode"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); + Check(ExpectedNoIeee, "IEEE=0"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, true, false}); + Check(ExpectedIeee, "IEEE=1"); + std::puts("f32 arithmetic nan tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index ac5698c0ea..bf5a58d69c 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -4,6 +4,7 @@ #include "Translation/InstructionTranslator.hpp" #include #include +#include #include namespace ShaderRecompiler { @@ -61,6 +62,8 @@ private: IrF32 flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend = nullptr); IrU32 quietNan32(IrU32 bits); IrU32 quietNan16(IrU32 bits); + IrValue* nanResultF32(std::initializer_list sources, IrValue* result, IrValue* invalidProduct = nullptr); + IrValue& invalidProductF32(IrValue* lhs, IrValue* rhs); std::array readU32Pair(const RdnaOperand& operand); IrU64 readU64(const RdnaOperand& operand); std::array readF64Bits(const RdnaOperand& operand); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 86a3549349..502e601a36 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -338,7 +338,7 @@ bool TranslationContext::vMulLegacyF32(const RdnaInstruction& inst, bool accumul IrValue* rhs = readOperand(sourceAt(inst, 1u), IrType::F32); const auto isZero = [&](IrValue* value) { return IrU1(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*value), ir.Constant(0x7fffffffu)), ir.Constant(0u))); }; const IrU1 zero(ir.LogicalOr(isZero(lhs).Value(), isZero(rhs).Value())); - IrValue* result = &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zero.Value(), &ir.ConstantF32(0.0f), &flushTinyProduct(lhs, rhs, &ir.Emit(IrOpcode::FPMul32, IrType::F32, {lhs, rhs})).Value()}); + IrValue* result = &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zero.Value(), &ir.ConstantF32(0.0f), nanResultF32({lhs, rhs}, &flushTinyProduct(lhs, rhs, &ir.Emit(IrOpcode::FPMul32, IrType::F32, {lhs, rhs})).Value())}); if (accumulate) { IrValue* addend = readOperand(accumulatorOperand(inst), IrType::F32); result = &ir.Emit(IrOpcode::FPAdd32, IrType::F32, {result, addend}); @@ -510,6 +510,11 @@ bool TranslationContext::floatBinary(const RdnaInstruction& inst, IrOpcode opcod } IrValue* result = &ir.Emit(opcode, IrOpcodeType(opcode), {args[0], args[1]}); if (opcode == IrOpcode::FPMul32) result = &flushTinyProduct(args[0], args[1], result).Value(); + if (opcode == IrOpcode::FPAdd32 || opcode == IrOpcode::FPMul32) result = nanResultF32({args[0], args[1]}, result); + if (opcode == IrOpcode::FPSub32) { + IrValue* negated = &ir.BitCastF32(ir.BitwiseXor(ir.BitCastU32(*args[1]), ir.Constant(0x80000000u))); + result = reverse ? nanResultF32({negated, args[0]}, result) : nanResultF32({args[0], negated}, result); + } writeOperand(inst.destination, result); return true; } @@ -523,6 +528,8 @@ bool TranslationContext::floatTernary(const RdnaInstruction& inst, IrOpcode opco } IrValue* result = &ir.Emit(opcode, IrOpcodeType(opcode), {args[0], args[1], args[2]}); if (opcode == IrOpcode::FPFma32) result = &flushTinyProduct(args[0], args[1], result, args[2]).Value(); + const bool f32Sources = !mix || !(sourceAt(inst, 0u).opSelHi || sourceAt(inst, 1u).opSelHi || (!accumulator && sourceAt(inst, 2u).opSelHi)); + if (opcode == IrOpcode::FPFma32 && f32Sources) result = nanResultF32({args[0], args[1], args[2]}, result, &invalidProductF32(args[0], args[1])); writeOperand(inst.destination, result); return true; } @@ -565,7 +572,9 @@ bool TranslationContext::vFmaLegacyF32(const RdnaInstruction& inst) { const auto isZero = [&](IrValue* value) { return IrU1(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*value), ir.Constant(0x7fffffffu)), ir.Constant(0u))); }; const IrU1 zero(ir.LogicalOr(isZero(lhs).Value(), isZero(rhs).Value())); const auto factor = [&](IrValue* value) { return &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zero.Value(), &ir.ConstantF32(0.0f), value}); }; - writeOperand(inst.destination, &ir.Emit(IrOpcode::FPFma32, IrType::F32, {factor(lhs), factor(rhs), addend})); + IrValue* lhsFactor = factor(lhs); + IrValue* rhsFactor = factor(rhs); + writeOperand(inst.destination, nanResultF32({lhsFactor, rhsFactor, addend}, &ir.Emit(IrOpcode::FPFma32, IrType::F32, {lhsFactor, rhsFactor, addend}), &invalidProductF32(lhsFactor, rhsFactor))); return true; } diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 0e1cda62c6..3136b2f114 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -286,6 +286,27 @@ IrU32 TranslationContext::quietNan32(IrU32 bits) { return ieeeMode ? IrU32(ir.BitwiseOr(bits.Value(), ir.Constant(0x00400000u))) : bits; } +IrValue* TranslationContext::nanResultF32(std::initializer_list sources, IrValue* result, IrValue* invalidProduct) { + const auto isNan = [&](IrValue& bits) -> IrValue& { return ir.UGreaterThan(ir.BitwiseAnd(bits, ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u)); }; + IrValue* bits = &ir.BitCastU32(*result); + bits = &ir.Select(isNan(*bits), ir.Constant(0xffc00000u), *bits); + for (auto source = sources.end(); source != sources.begin();) { + --source; + IrValue& sourceBits = ir.BitCastU32(**source); + IrValue* selected = &isNan(sourceBits); + if (invalidProduct != nullptr && source + 1 == sources.end() && sources.size() == 3u) selected = &ir.LogicalAnd(*selected, ir.LogicalNot(*invalidProduct)); + bits = &ir.Select(*selected, quietNan32(IrU32(sourceBits)).Value(), *bits); + } + return &ir.BitCastF32(*bits); +} + +IrValue& TranslationContext::invalidProductF32(IrValue* lhs, IrValue* rhs) { + IrValue& lhsMagnitude = ir.BitwiseAnd(ir.BitCastU32(*lhs), ir.Constant(0x7fffffffu)); + IrValue& rhsMagnitude = ir.BitwiseAnd(ir.BitCastU32(*rhs), ir.Constant(0x7fffffffu)); + const auto infZero = [&](IrValue& inf, IrValue& zero) -> IrValue& { return ir.LogicalAnd(ir.IEqual(inf, ir.Constant(0x7f800000u)), ir.IEqual(zero, ir.Constant(0u))); }; + return ir.LogicalOr(infZero(lhsMagnitude, rhsMagnitude), infZero(rhsMagnitude, lhsMagnitude)); +} + IrU32 TranslationContext::quietNan16(IrU32 bits) { return ieeeMode ? IrU32(ir.BitwiseOr(bits.Value(), ir.Constant(0x0200u))) : bits; } diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 81b6743662..5ba9128a74 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -286,11 +286,11 @@ Throughout the project, every function at every stage either **does exactly what - A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. - A [COND_INDIRECT_BUFFER](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (`sceAgcCbBranch`) runs only with the always-pass compare function; a comparison or a predicated packet throws. Modes 1 and 2 run the first buffer, assumed to be the if-then and if-then-else modes of AMD's PM4 packet. [sceAgcCbBranch](../../core/libs/prx/libSceAgc/Cb/src/Branch.cpp) does not check the compare address of an always-pass branch or the address of an empty buffer, as PPSA14632 passes an uninitialised compare address and a null else buffer of 0 dwords. - [SET_*_REG_INDIRECT](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write. -- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. The f16 and f64 min/max/med3, NaN quieting in binary and ternary arithmetic (the unary instructions follow it, see below), `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. +- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. The f16 and f64 min/max/med3, NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below), `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted only with IEEE=1, as measured at IEEE=0 too; a positive denormal src2 counts as positive): other denormal settings are not modelled. - [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same. - [v_fma_f16, v_pk_fma_f16 and v_pk_fmac_f16](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) are an f32 FMA rounded to f16, which rounds twice: 0x62e9 * 0xd159 + 0x23ec gives 0xf89e, the hardware 0xf89f. -- [Signaling NaN inputs](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) of the f32 and f16 unary instructions (`v_sqrt/rcp/rsq/log/exp/sin/cos/fract/floor/ceil/trunc/rndne/rcp_iflag/frexp_mant`), `v_ldexp_f32/f16`, `v_frexp_mant_f16`, `v_div_fixup_f32/f16` and `v_mullit_f32` are returned quieted only with IEEE=1 and unchanged with IEEE=0, measured on RDNA2 (gfx1035) in both modes; shaders without a known float mode follow IEEE=0. The binary and ternary arithmetic (`v_add/mul/fma_f32/f16`, `v_mul_legacy`, `v_fma_mix`, `v_cubema`) follows the same rule on hardware but its NaN bits are left to the driver. `v_cvt_f16_f32` and `v_dot2_f32_f16` always quiet and `v_div_scale_f32` never does. +- [Signaling NaN inputs](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) of the f32 and f16 unary instructions (`v_sqrt/rcp/rsq/log/exp/sin/cos/fract/floor/ceil/trunc/rndne/rcp_iflag/frexp_mant`), `v_ldexp_f32/f16`, `v_frexp_mant_f16`, `v_div_fixup_f32/f16` and `v_mullit_f32` are returned quieted only with IEEE=1 and unchanged with IEEE=0, measured on RDNA2 (gfx1035) in both modes; shaders without a known float mode follow IEEE=0. `v_add/sub/subrev/mul/fma/fmac/fmamk/fmaak/mul_legacy/fma_legacy_f32` and `v_fma_mix_f32` with f32 sources choose their NaN result on the bits, measured over 1000 operand triples in both modes: the first NaN source in operand order after its modifiers (the subtrahend negated, as the hardware adds its negation), quieted only with IEEE=1, or the default NaN 0xffc00000 for an invalid operation; in the fma forms an invalid product (inf * 0) gives the default NaN even when src2 is a NaN, and in the legacy forms a zero factor makes the product 0 even when the other factor is a NaN (a NaN src2 of `v_fma_legacy_f32` still wins). The f16 arithmetic, `v_fma_mix_f32` with f16 sources, `v_cubema`, `v_mac_legacy_f32` and the separately rounded mad/mac forms (not measurable on RDNA2) follow the same rule on hardware but their NaN bits are left to the driver. `v_cvt_f16_f32` and `v_dot2_f32_f16` always quiet and `v_div_scale_f32` never does. - [Packed f16 NaN results](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (`v_pk_add/mul/fma/min/max_f16`) follow the hardware measured on RDNA2 (gfx1035) at IEEE=0 and IEEE=1 with f16 round-to-nearest-even and f16 denormals kept: with IEEE=1 a signaling NaN input is quieted and min/max return the other operand only for a quiet NaN; with IEEE=0 NaN inputs pass through unquieted and min/max ignore signaling. In `v_pk_fma_f16` an invalid product (inf * 0) gives the default NaN even when src2 is a NaN. A shader without a known float mode is translated as IEEE=0, the mode titles use; a mode with another f16 rounding or that flushes f16 denormals throws. - [f32 denormals](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follow the shader's `FLOAT_MODE` f32 denormal field (bits 5:4) when it flushes them (0, and shaders without a known float mode, as in the 0xc0 most titles use), measured on RDNA2 (gfx1035) over 1444 rows per instruction at IEEE=0 and IEEE=1. `v_add/sub/subrev/mul/mul_legacy/fma/fmac/fmamk/fmaak/min/max/min3/max3/med3/fract/floor/ceil/frexp_mant/sin/cos_f32` flush their f32 inputs and their result to a zero of the same sign; the f32 compares, `v_cvt_i32/u32/rpi_i32/flr_i32_f32`, `v_frexp_exp_i32_f32`, `v_cvt_f64_f32`, `v_cvt_f16_f32`, `v_cvt_pkrtz_f16_f32`, `v_cvt_pknorm_i16/u16_f32` and `v_cvt_pk_u8_f32` flush their f32 inputs. Tininess is detected before rounding: a product whose exact value is below 2^-126 gives zero even when it rounds to 2^-126, computed exactly from the significands for `v_mul_f32`, `v_mul_legacy_f32`, and for `v_fma_f32`, `v_fmac_f32`, `v_fmamk_f32`, `v_fmaak_f32` and `v_fma_mix_f32` when the addend is zero; with a nonzero addend the fused result is kept, so an exact result just below 2^-126 that rounds to it is not flushed. `v_fma_mix_f32` was measured with f32 sources only; its f16 sources are not flushed. Denormal modes 1 and 2 throw. Not covered, and not measurable on RDNA2 (no such opcodes) or not measured: `v_mac_f32`, `v_mad_f32`, `v_madak_f32` and `v_madmk_f32` (rounded separately), `v_mac_legacy_f32`, `v_dot2_f32_f16`, the division steps (on hardware `v_div_fixup_f32` flushes only src1 and src2, `v_div_fmas_f32` only its result), f32 interpolation and LDS float atomics keep the previous lowering. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested, and `v_ldexp_f32` flushes in every mode. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.