fix(shader): flush f16 denormals in unary, frexp and conversion ops per the float mode
The f16 unary ops, v_frexp_mant_f16, v_frexp_exp_i16_f16, the norm conversions and v_cvt_f32_f16 read denormal halves as they are and rounded denormal results as they are, whatever the denormal field of FLOAT_MODE says. On RDNA2 a clear bit 0 flushes a denormal half input to its signed zero and a clear bit 1 flushes a denormal half result. One shared rule now drives all of them, and v_ldexp_f16 uses it for its input too. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
parent
a11c8f7f70
commit
95988c9c34
|
|
@ -0,0 +1,353 @@
|
|||
#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp"
|
||||
#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp"
|
||||
#include "Recompiler.hpp"
|
||||
#include "VulkanTestDevice.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <iostream>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
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 = 1;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
struct alignas(256) Shader {
|
||||
std::array<std::uint32_t, 11> words;
|
||||
};
|
||||
|
||||
struct Vector {
|
||||
std::uint32_t half;
|
||||
std::uint32_t expected[4];
|
||||
};
|
||||
|
||||
struct Case {
|
||||
const char* name;
|
||||
std::uint32_t word;
|
||||
std::span<const Vector> rows;
|
||||
};
|
||||
|
||||
constexpr Vector CeilRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a3c00u, 0x5a5a0000u, 0x5a5a3c00u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a3c00u, 0x5a5a0000u, 0x5a5a3c00u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a3c00u, 0x5a5a0000u, 0x5a5a3c00u}},
|
||||
{0x8001u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u}},
|
||||
{0x8200u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u}},
|
||||
{0x83ffu, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u}},
|
||||
{0x0400u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x8400u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0xbc00u, {0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u}},
|
||||
{0x3800u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
};
|
||||
|
||||
constexpr Vector CvtF32Rows[] = {
|
||||
{0x0001u, {0x00000000u, 0x33800000u, 0x00000000u, 0x33800000u}},
|
||||
{0x0200u, {0x00000000u, 0x38000000u, 0x00000000u, 0x38000000u}},
|
||||
{0x03ffu, {0x00000000u, 0x387fc000u, 0x00000000u, 0x387fc000u}},
|
||||
{0x8001u, {0x80000000u, 0xb3800000u, 0x80000000u, 0xb3800000u}},
|
||||
{0x8200u, {0x80000000u, 0xb8000000u, 0x80000000u, 0xb8000000u}},
|
||||
{0x83ffu, {0x80000000u, 0xb87fc000u, 0x80000000u, 0xb87fc000u}},
|
||||
{0x0400u, {0x38800000u, 0x38800000u, 0x38800000u, 0x38800000u}},
|
||||
{0x8400u, {0xb8800000u, 0xb8800000u, 0xb8800000u, 0xb8800000u}},
|
||||
{0x0000u, {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}},
|
||||
{0x3c00u, {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}},
|
||||
{0xbc00u, {0xbf800000u, 0xbf800000u, 0xbf800000u, 0xbf800000u}},
|
||||
{0x3800u, {0x3f000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u}},
|
||||
};
|
||||
|
||||
constexpr Vector CvtNormI16Rows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a0001u, 0x5a5a0000u, 0x5a5a0001u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a0002u, 0x5a5a0000u, 0x5a5a0002u}},
|
||||
{0x8001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x8200u, {0x5a5a0000u, 0x5a5affffu, 0x5a5a0000u, 0x5a5affffu}},
|
||||
{0x83ffu, {0x5a5a0000u, 0x5a5afffeu, 0x5a5a0000u, 0x5a5afffeu}},
|
||||
{0x0400u, {0x5a5a0002u, 0x5a5a0002u, 0x5a5a0002u, 0x5a5a0002u}},
|
||||
{0x8400u, {0x5a5afffeu, 0x5a5afffeu, 0x5a5afffeu, 0x5a5afffeu}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a7fffu, 0x5a5a7fffu, 0x5a5a7fffu, 0x5a5a7fffu}},
|
||||
{0xbc00u, {0x5a5a8001u, 0x5a5a8001u, 0x5a5a8001u, 0x5a5a8001u}},
|
||||
{0x3800u, {0x5a5a4000u, 0x5a5a4000u, 0x5a5a4000u, 0x5a5a4000u}},
|
||||
{0xb800u, {0x5a5ac000u, 0x5a5ac000u, 0x5a5ac000u, 0x5a5ac000u}},
|
||||
};
|
||||
|
||||
constexpr Vector CvtNormU16Rows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a0002u, 0x5a5a0000u, 0x5a5a0002u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a0004u, 0x5a5a0000u, 0x5a5a0004u}},
|
||||
{0x8001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x8200u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x83ffu, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x0400u, {0x5a5a0004u, 0x5a5a0004u, 0x5a5a0004u, 0x5a5a0004u}},
|
||||
{0x8400u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5affffu, 0x5a5affffu, 0x5a5affffu, 0x5a5affffu}},
|
||||
{0xbc00u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3800u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u}},
|
||||
{0xb800u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
};
|
||||
|
||||
constexpr Vector ExpRows[] = {
|
||||
{0x0001u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x0200u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x03ffu, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x8001u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x8200u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x83ffu, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x0400u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x8400u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x0000u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0x3c00u, {0x5a5a4000u, 0x5a5a4000u, 0x5a5a4000u, 0x5a5a4000u}},
|
||||
{0xbc00u, {0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u}},
|
||||
{0xcb01u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a03fau, 0x5a5a03fau}},
|
||||
{0xcca0u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a002du, 0x5a5a002du}},
|
||||
{0xce3cu, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0001u, 0x5a5a0001u}},
|
||||
{0xc800u, {0x5a5a1c00u, 0x5a5a1c00u, 0x5a5a1c00u, 0x5a5a1c00u}},
|
||||
{0xc400u, {0x5a5a2c00u, 0x5a5a2c00u, 0x5a5a2c00u, 0x5a5a2c00u}},
|
||||
};
|
||||
|
||||
constexpr Vector FloorRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x8001u, {0x5a5a8000u, 0x5a5abc00u, 0x5a5a8000u, 0x5a5abc00u}},
|
||||
{0x8200u, {0x5a5a8000u, 0x5a5abc00u, 0x5a5a8000u, 0x5a5abc00u}},
|
||||
{0x83ffu, {0x5a5a8000u, 0x5a5abc00u, 0x5a5a8000u, 0x5a5abc00u}},
|
||||
{0x0400u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x8400u, {0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0xbc00u, {0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u}},
|
||||
{0xb800u, {0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u}},
|
||||
};
|
||||
|
||||
constexpr Vector FractRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0001u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0200u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a03ffu}},
|
||||
{0x8001u, {0x5a5a0000u, 0x5a5a3bffu, 0x5a5a0000u, 0x5a5a3bffu}},
|
||||
{0x8200u, {0x5a5a0000u, 0x5a5a3bffu, 0x5a5a0000u, 0x5a5a3bffu}},
|
||||
{0x83ffu, {0x5a5a0000u, 0x5a5a3bffu, 0x5a5a0000u, 0x5a5a3bffu}},
|
||||
{0x0400u, {0x5a5a0400u, 0x5a5a0400u, 0x5a5a0400u, 0x5a5a0400u}},
|
||||
{0x8400u, {0x5a5a3bffu, 0x5a5a3bffu, 0x5a5a3bffu, 0x5a5a3bffu}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0xbc00u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0xb800u, {0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u}},
|
||||
{0x4900u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
};
|
||||
|
||||
constexpr Vector FrexpExpRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5affe9u, 0x5a5a0000u, 0x5a5affe9u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5afff2u, 0x5a5a0000u, 0x5a5afff2u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5afff2u, 0x5a5a0000u, 0x5a5afff2u}},
|
||||
{0x8001u, {0x5a5a0000u, 0x5a5affe9u, 0x5a5a0000u, 0x5a5affe9u}},
|
||||
{0x8200u, {0x5a5a0000u, 0x5a5afff2u, 0x5a5a0000u, 0x5a5afff2u}},
|
||||
{0x83ffu, {0x5a5a0000u, 0x5a5afff2u, 0x5a5a0000u, 0x5a5afff2u}},
|
||||
{0x0400u, {0x5a5afff3u, 0x5a5afff3u, 0x5a5afff3u, 0x5a5afff3u}},
|
||||
{0x8400u, {0x5a5afff3u, 0x5a5afff3u, 0x5a5afff3u, 0x5a5afff3u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a0001u, 0x5a5a0001u, 0x5a5a0001u, 0x5a5a0001u}},
|
||||
{0xbc00u, {0x5a5a0001u, 0x5a5a0001u, 0x5a5a0001u, 0x5a5a0001u}},
|
||||
{0x4900u, {0x5a5a0004u, 0x5a5a0004u, 0x5a5a0004u, 0x5a5a0004u}},
|
||||
};
|
||||
|
||||
constexpr Vector FrexpMantRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a3800u, 0x5a5a0000u, 0x5a5a3800u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a3800u, 0x5a5a0000u, 0x5a5a3800u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a3bfeu, 0x5a5a0000u, 0x5a5a3bfeu}},
|
||||
{0x8001u, {0x5a5a8000u, 0x5a5ab800u, 0x5a5a8000u, 0x5a5ab800u}},
|
||||
{0x8200u, {0x5a5a8000u, 0x5a5ab800u, 0x5a5a8000u, 0x5a5ab800u}},
|
||||
{0x83ffu, {0x5a5a8000u, 0x5a5abbfeu, 0x5a5a8000u, 0x5a5abbfeu}},
|
||||
{0x0400u, {0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u}},
|
||||
{0x8400u, {0x5a5ab800u, 0x5a5ab800u, 0x5a5ab800u, 0x5a5ab800u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u}},
|
||||
{0xbc00u, {0x5a5ab800u, 0x5a5ab800u, 0x5a5ab800u, 0x5a5ab800u}},
|
||||
{0x4900u, {0x5a5a3900u, 0x5a5a3900u, 0x5a5a3900u, 0x5a5a3900u}},
|
||||
};
|
||||
|
||||
constexpr Vector LogRows[] = {
|
||||
{0x0001u, {0x5a5afc00u, 0x5a5ace00u, 0x5a5afc00u, 0x5a5ace00u}},
|
||||
{0x0200u, {0x5a5afc00u, 0x5a5acb80u, 0x5a5afc00u, 0x5a5acb80u}},
|
||||
{0x03ffu, {0x5a5afc00u, 0x5a5acb00u, 0x5a5afc00u, 0x5a5acb00u}},
|
||||
{0x8001u, {0x5a5afc00u, 0x5a5afe00u, 0x5a5afc00u, 0x5a5afe00u}},
|
||||
{0x8200u, {0x5a5afc00u, 0x5a5afe00u, 0x5a5afc00u, 0x5a5afe00u}},
|
||||
{0x83ffu, {0x5a5afc00u, 0x5a5afe00u, 0x5a5afc00u, 0x5a5afe00u}},
|
||||
{0x0400u, {0x5a5acb00u, 0x5a5acb00u, 0x5a5acb00u, 0x5a5acb00u}},
|
||||
{0x8400u, {0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u}},
|
||||
{0x0000u, {0x5a5afc00u, 0x5a5afc00u, 0x5a5afc00u, 0x5a5afc00u}},
|
||||
{0x3c00u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0xbc00u, {0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u}},
|
||||
{0x4900u, {0x5a5a42a5u, 0x5a5a42a5u, 0x5a5a42a5u, 0x5a5a42a5u}},
|
||||
{0x0801u, {0x5a5aca80u, 0x5a5aca80u, 0x5a5aca80u, 0x5a5aca80u}},
|
||||
};
|
||||
|
||||
constexpr Vector RcpRows[] = {
|
||||
{0x0001u, {0x5a5a7c00u, 0x5a5a7c00u, 0x5a5a7c00u, 0x5a5a7c00u}},
|
||||
{0x0200u, {0x5a5a7c00u, 0x5a5a7800u, 0x5a5a7c00u, 0x5a5a7800u}},
|
||||
{0x03ffu, {0x5a5a7c00u, 0x5a5a7401u, 0x5a5a7c00u, 0x5a5a7401u}},
|
||||
{0x8001u, {0x5a5afc00u, 0x5a5afc00u, 0x5a5afc00u, 0x5a5afc00u}},
|
||||
{0x8200u, {0x5a5afc00u, 0x5a5af800u, 0x5a5afc00u, 0x5a5af800u}},
|
||||
{0x83ffu, {0x5a5afc00u, 0x5a5af401u, 0x5a5afc00u, 0x5a5af401u}},
|
||||
{0x0400u, {0x5a5a7400u, 0x5a5a7400u, 0x5a5a7400u, 0x5a5a7400u}},
|
||||
{0x8400u, {0x5a5af400u, 0x5a5af400u, 0x5a5af400u, 0x5a5af400u}},
|
||||
{0x0000u, {0x5a5a7c00u, 0x5a5a7c00u, 0x5a5a7c00u, 0x5a5a7c00u}},
|
||||
{0x3c00u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0xbc00u, {0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u, 0x5a5abc00u}},
|
||||
{0x7bffu, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0100u, 0x5a5a0100u}},
|
||||
{0x7401u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a03ffu, 0x5a5a03ffu}},
|
||||
{0xfbffu, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8100u, 0x5a5a8100u}},
|
||||
{0xf404u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a83fcu, 0x5a5a83fcu}},
|
||||
{0x7800u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0200u, 0x5a5a0200u}},
|
||||
};
|
||||
|
||||
constexpr Vector RsqRows[] = {
|
||||
{0x0001u, {0x5a5a7c00u, 0x5a5a6c00u, 0x5a5a7c00u, 0x5a5a6c00u}},
|
||||
{0x0200u, {0x5a5a7c00u, 0x5a5a59a8u, 0x5a5a7c00u, 0x5a5a59a8u}},
|
||||
{0x03ffu, {0x5a5a7c00u, 0x5a5a5801u, 0x5a5a7c00u, 0x5a5a5801u}},
|
||||
{0x8001u, {0x5a5afc00u, 0x5a5afe00u, 0x5a5afc00u, 0x5a5afe00u}},
|
||||
{0x8200u, {0x5a5afc00u, 0x5a5afe00u, 0x5a5afc00u, 0x5a5afe00u}},
|
||||
{0x83ffu, {0x5a5afc00u, 0x5a5afe00u, 0x5a5afc00u, 0x5a5afe00u}},
|
||||
{0x0400u, {0x5a5a5800u, 0x5a5a5800u, 0x5a5a5800u, 0x5a5a5800u}},
|
||||
{0x8400u, {0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u}},
|
||||
{0x0000u, {0x5a5a7c00u, 0x5a5a7c00u, 0x5a5a7c00u, 0x5a5a7c00u}},
|
||||
{0x3c00u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0xbc00u, {0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u}},
|
||||
{0x4400u, {0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u, 0x5a5a3800u}},
|
||||
};
|
||||
|
||||
constexpr Vector SinRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0006u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a0a48u, 0x5a5a0000u, 0x5a5a0a48u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a0e47u, 0x5a5a0000u, 0x5a5a0e47u}},
|
||||
{0x8001u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a8006u}},
|
||||
{0x8200u, {0x5a5a8000u, 0x5a5a8a48u, 0x5a5a8000u, 0x5a5a8a48u}},
|
||||
{0x83ffu, {0x5a5a8000u, 0x5a5a8e47u, 0x5a5a8000u, 0x5a5a8e47u}},
|
||||
{0x0400u, {0x5a5a0e48u, 0x5a5a0e48u, 0x5a5a0e48u, 0x5a5a0e48u}},
|
||||
{0x8400u, {0x5a5a8e48u, 0x5a5a8e48u, 0x5a5a8e48u, 0x5a5a8e48u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0xbc00u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x00a0u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a03edu}},
|
||||
{0x80a0u, {0x5a5a8000u, 0x5a5a8000u, 0x5a5a8000u, 0x5a5a83edu}},
|
||||
{0x3800u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
};
|
||||
|
||||
constexpr Vector SqrtRows[] = {
|
||||
{0x0001u, {0x5a5a0000u, 0x5a5a0c00u, 0x5a5a0000u, 0x5a5a0c00u}},
|
||||
{0x0200u, {0x5a5a0000u, 0x5a5a1da8u, 0x5a5a0000u, 0x5a5a1da8u}},
|
||||
{0x03ffu, {0x5a5a0000u, 0x5a5a1fffu, 0x5a5a0000u, 0x5a5a1fffu}},
|
||||
{0x8001u, {0x5a5a8000u, 0x5a5afe00u, 0x5a5a8000u, 0x5a5afe00u}},
|
||||
{0x8200u, {0x5a5a8000u, 0x5a5afe00u, 0x5a5a8000u, 0x5a5afe00u}},
|
||||
{0x83ffu, {0x5a5a8000u, 0x5a5afe00u, 0x5a5a8000u, 0x5a5afe00u}},
|
||||
{0x0400u, {0x5a5a2000u, 0x5a5a2000u, 0x5a5a2000u, 0x5a5a2000u}},
|
||||
{0x8400u, {0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u}},
|
||||
{0x0000u, {0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u, 0x5a5a0000u}},
|
||||
{0x3c00u, {0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u, 0x5a5a3c00u}},
|
||||
{0xbc00u, {0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u, 0x5a5afe00u}},
|
||||
{0x4400u, {0x5a5a4000u, 0x5a5a4000u, 0x5a5a4000u, 0x5a5a4000u}},
|
||||
};
|
||||
|
||||
constexpr Case Cases[] = {
|
||||
{"v_ceil_f16", 0x7e14b904u, CeilRows},
|
||||
{"v_cvt_f32_f16", 0x7e141704u, CvtF32Rows},
|
||||
{"v_cvt_norm_i16_f16", 0x7e14c704u, CvtNormI16Rows},
|
||||
{"v_cvt_norm_u16_f16", 0x7e14c904u, CvtNormU16Rows},
|
||||
{"v_exp_f16", 0x7e14b104u, ExpRows},
|
||||
{"v_floor_f16", 0x7e14b704u, FloorRows},
|
||||
{"v_fract_f16", 0x7e14bf04u, FractRows},
|
||||
{"v_frexp_exp_i16_f16", 0x7e14b504u, FrexpExpRows},
|
||||
{"v_frexp_mant_f16", 0x7e14b304u, FrexpMantRows},
|
||||
{"v_log_f16", 0x7e14af04u, LogRows},
|
||||
{"v_rcp_f16", 0x7e14a904u, RcpRows},
|
||||
{"v_rsq_f16", 0x7e14ad04u, RsqRows},
|
||||
{"v_sin_f16", 0x7e14c104u, SinRows},
|
||||
{"v_sqrt_f16", 0x7e14ab04u, SqrtRows},
|
||||
};
|
||||
|
||||
constexpr std::uint32_t CaseCount = std::size(Cases);
|
||||
std::array<Shader, CaseCount> Shaders{};
|
||||
|
||||
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
|
||||
const auto address = reinterpret_cast<std::uintptr_t>(data);
|
||||
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((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 Run(AgcDriver::VulkanDevice& device, const Case& item, const Shader& shader, std::uint32_t denorm16, bool ieee) {
|
||||
Input.fill(0u);
|
||||
for (std::uint32_t row = 0; row < item.rows.size(); ++row) {
|
||||
Input[row * Inputs] = item.rows[row].half;
|
||||
}
|
||||
Output.fill(0xdeadbeefu);
|
||||
std::vector<std::uint32_t> userData(8, 0u);
|
||||
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
|
||||
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
|
||||
std::copy(input.begin(), input.end(), userData.begin());
|
||||
std::copy(output.begin(), output.end(), userData.begin() + 4);
|
||||
const std::span<const std::uint32_t> code(shader.words);
|
||||
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(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 = ShaderRecompiler::ShaderFloatMode{(3u << 4u) | (denorm16 << 6u), true, ieee, false};
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(code.data()));
|
||||
device.WaitIdle();
|
||||
}
|
||||
|
||||
void Check(const Case& item, std::uint32_t denorm16, bool ieee) {
|
||||
for (std::uint32_t row = 0; row < item.rows.size(); ++row) {
|
||||
const auto actual = Output[row];
|
||||
const auto expected = item.rows[row].expected[denorm16];
|
||||
Require(actual == expected, std::string(item.name) + " denorm16 " + std::to_string(denorm16) + (ieee ? " ieee" : " no ieee") + " input " +
|
||||
Hex(item.rows[row].half) + " is " + Hex(actual) + ", expected " + Hex(expected));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
for (std::uint32_t index = 0; index < CaseCount; ++index) {
|
||||
Shaders[index].words = {0x34020084u, 0x34040082u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e1402ffu, 0x5a5a5a5au, Cases[index].word,
|
||||
0xe0701000u, 0x80010a02u, 0xbf810000u};
|
||||
for (std::uint32_t denorm16 = 0u; denorm16 < 4u; ++denorm16) {
|
||||
for (const bool ieee : {false, true}) {
|
||||
Run(*device, Cases[index], Shaders[index], denorm16, ieee);
|
||||
Check(Cases[index], denorm16, ieee);
|
||||
}
|
||||
}
|
||||
}
|
||||
std::puts("f16 denormal flush tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -60,6 +60,10 @@ private:
|
|||
void write16Bits(const RdnaOperand& operand, IrU32 value);
|
||||
void writeF16(const RdnaOperand& operand, IrF32 value, std::initializer_list<IrValue*> sources);
|
||||
bool directedF16Rounding() const;
|
||||
IrU32 flushF16Bits(IrU32 bits, std::uint32_t keepBit);
|
||||
IrU32 flushF16Input(IrU32 bits);
|
||||
IrU32 flushF16Result(IrU32 bits);
|
||||
IrF32 flushF16InputValue(IrF32 value);
|
||||
IrU32 convertF32ToF16Bits(IrF32 value, std::initializer_list<IrValue*> sources);
|
||||
void writeF16Conversion(const RdnaOperand& operand, IrF32 value, std::initializer_list<IrValue*> sources);
|
||||
IrU32 readU32(const RdnaOperand& operand);
|
||||
|
|
|
|||
|
|
@ -123,7 +123,7 @@ void TranslationContext::vCvtF16F32(const RdnaInstruction& inst) {
|
|||
}
|
||||
|
||||
void TranslationContext::vCvtF32F16(const RdnaInstruction& inst) {
|
||||
const IrF32 value = readF16AsF32(sourceAt(inst, 0u));
|
||||
const IrF32 value = flushF16InputValue(readF16AsF32(sourceAt(inst, 0u)));
|
||||
writeOperand(inst.destination, &value.Value());
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -88,12 +88,13 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc
|
|||
|
||||
bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opcode) {
|
||||
const RdnaOperand& operand = sourceAt(inst, 0u);
|
||||
const IrF32 argument = readF16AsF32(operand);
|
||||
const IrF32 argument = flushF16InputValue(readF16AsF32(operand));
|
||||
const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()})));
|
||||
IrU32 bits = packHalf2x16(applyF16ResultModifiers(inst.destination, clampF16Overflow(result, {&argument.Value()})), IrF32(ir.ConstantF32(0.0f)));
|
||||
if (opcode == IrOpcode::FPFract32) {
|
||||
bits = IrU32(ir.Select(ir.IEqual(bits.Value(), ir.Constant(0x3c00u)), ir.Constant(0x3bffu), bits.Value()));
|
||||
}
|
||||
bits = flushF16Result(bits);
|
||||
const IrU32 half = readF16Bits(operand);
|
||||
const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(half.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u)));
|
||||
const IrU32 special(ir.Select(nan.Value(), quietNan16(half).Value(), ir.Constant(0xfe00u)));
|
||||
|
|
@ -327,11 +328,7 @@ IrU32 TranslationContext::normF32(IrU32 bits, bool signedValue) {
|
|||
|
||||
bool TranslationContext::vLdexpF16(const RdnaInstruction& inst) {
|
||||
const IrU32 source = readF16Bits(sourceAt(inst, 0u));
|
||||
IrU32 bits = source;
|
||||
if (floatMode.has_value() && (floatMode->floatMode & 0x40u) == 0u) {
|
||||
const IrU1 tiny(ir.ULessThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x0400u)));
|
||||
bits = IrU32(ir.Select(tiny.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x8000u)), bits.Value()));
|
||||
}
|
||||
const IrU32 bits = flushF16Input(source);
|
||||
const IrF16 half(ir.Emit(IrOpcode::BitCastF16U16, IrType::F16, {&ir.Emit(IrOpcode::ConvertU16U32, IrType::U16, {&bits.Value()})}));
|
||||
const IrF32 value(ir.Emit(IrOpcode::ConvertF32F16, IrType::F32, {&half.Value()}));
|
||||
const RdnaOperand& exponentSource = sourceAt(inst, 1u);
|
||||
|
|
@ -351,8 +348,8 @@ bool TranslationContext::vLdexpF16(const RdnaInstruction& inst) {
|
|||
}
|
||||
|
||||
bool TranslationContext::vFrexpF16(const RdnaInstruction& inst, bool exponent) {
|
||||
const IrU32 bits = readF16Bits(sourceAt(inst, 0u));
|
||||
const IrF32 value = readF16AsF32(sourceAt(inst, 0u));
|
||||
const IrU32 bits = flushF16Input(readF16Bits(sourceAt(inst, 0u)));
|
||||
const IrF32 value = flushF16InputValue(readF16AsF32(sourceAt(inst, 0u)));
|
||||
const IrU32 word(ir.BitCastU32(value.Value()));
|
||||
const IrU1 special(ir.LogicalOr(ir.IEqual(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0u)),
|
||||
ir.Emit(IrOpcode::UGreaterThanEqual32, IrType::U1, {&ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), &ir.Constant(0x7c00u)})));
|
||||
|
|
@ -371,7 +368,7 @@ bool TranslationContext::vFrexpF16(const RdnaInstruction& inst, bool exponent) {
|
|||
}
|
||||
|
||||
bool TranslationContext::vCvtNormF16(const RdnaInstruction& inst, bool signedValue) {
|
||||
write16Bits(inst.destination, normF16(readF16Bits(sourceAt(inst, 0u)), signedValue));
|
||||
write16Bits(inst.destination, normF16(flushF16Input(readF16Bits(sourceAt(inst, 0u))), signedValue));
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -160,6 +160,27 @@ bool TranslationContext::directedF16Rounding() const {
|
|||
return floatMode.has_value() && ((floatMode->floatMode >> 2u) & 3u) != 0u && ((floatMode->floatMode >> 6u) & 3u) == 3u;
|
||||
}
|
||||
|
||||
IrU32 TranslationContext::flushF16Bits(IrU32 bits, std::uint32_t keepBit) {
|
||||
if (!floatMode.has_value() || (floatMode->floatMode & keepBit) != 0u) return bits;
|
||||
const IrU1 tiny(ir.ULessThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x0400u)));
|
||||
return IrU32(ir.Select(tiny.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x8000u)), bits.Value()));
|
||||
}
|
||||
|
||||
IrU32 TranslationContext::flushF16Input(IrU32 bits) {
|
||||
return flushF16Bits(bits, 0x40u);
|
||||
}
|
||||
|
||||
IrU32 TranslationContext::flushF16Result(IrU32 bits) {
|
||||
return flushF16Bits(bits, 0x80u);
|
||||
}
|
||||
|
||||
IrF32 TranslationContext::flushF16InputValue(IrF32 value) {
|
||||
if (!floatMode.has_value() || (floatMode->floatMode & 0x40u) != 0u) return value;
|
||||
const IrU32 bits(ir.BitCastU32(value.Value()));
|
||||
const IrU1 tiny(ir.ULessThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)), ir.Constant(0x38800000u)));
|
||||
return IrF32(ir.BitCastF32(ir.Select(tiny.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), bits.Value())));
|
||||
}
|
||||
|
||||
IrU32 TranslationContext::convertF32ToF16Bits(IrF32 value, std::initializer_list<IrValue*> sources) {
|
||||
const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&clampF16Overflow(value, sources).Value()}));
|
||||
const IrU32 native(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&half.Value()})}));
|
||||
|
|
|
|||
|
|
@ -471,6 +471,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [v_fma_mixlo_f16 and v_fma_mixhi_f16](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) round the exact product-sum once to f16 with the f64 arithmetic of v_interp_p2_f16, so they need the Float64 capability. On RDNA2 (gfx1036) they follow the `FLOAT_MODE` f16 round and denormal fields, not the f32 round field: -1.5 * 2^-24 + 2^-126 from f32 sources (0xbfc00000, 0x33800000, 0x00800000) gives 0x8001 with f16 denormals kept and round to nearest even, 0x8000 with f16 denormals flushed and 0x8002 with f16 round toward -infinity. Only f16 denormals kept with round to nearest even is translated; another f16 mode throws, as for packed f16. `FP16_OVFL` set is not measured.
|
||||
- [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, and so are those of the f64 forms (`v_sqrt/rcp/rsq/fract/floor/ceil/trunc/rndne/frexp_mant/ldexp/div_fixup_f64`) and `v_cvt_f64_f32`, measured on RDNA2 (gfx1036) in both modes; shaders without a known float mode follow IEEE=0. `v_pack_b32_f16` quiets each signaling NaN half by the same rule, measured on RDNA2 (gfx1036) in both modes. A signaling NaN result of `v_cubesc/cubetc/cubema_f32` is quieted by the same rule and keeps the sign the instruction gives it, measured on RDNA2 (gfx1036) in both modes. `v_add/sub/subrev/mul/fma/fmac/fmamk/fmaak/mul_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 still wins). `v_mad_legacy_f32` and `v_mac_legacy_f32` follow the same rule with their separately rounded product, as measured on gfx1010 (#1281, #1290): a NaN source quieted only with IEEE=1, inf - inf giving 0xffc00000, the zero rule hiding a NaN factor and a NaN src2 winning. The f16 and f64 arithmetic, `v_fma_mix_f32` with f16 sources 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`, `v_cvt_f32_f64`, `v_dot2_f32_f16`, `v_fma_mixlo_f16` and `v_fma_mixhi_f16` always quiet and `v_div_scale_f32` never does: the f32 signaling NaN 0x7f812345 as src0 or src2 gives 0x7e09 from mixlo and mixhi at IEEE=0 and IEEE=1 on RDNA2 (gfx1036), where `v_fma_mix_f32` gives 0x7f812345 at IEEE=0 and 0x7fc12345 at IEEE=1. The payload of an f16 NaN source of mixlo and mixhi is left to the driver's f16 to f32 conversion, which NVIDIA replaces.
|
||||
- [Scalar f16 add, sub, subrev and mul denormals](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follow the `FLOAT_MODE` f16 denormal field at nearest-even: bit 0 preserves inputs and bit 1 preserves the result; otherwise a denormal is flushed to its signed zero. Measured on RDNA2 (gfx1036) with IEEE=0/1 and FP16_OVFL=0/1, over 32 operand pairs in all four denormal modes, including signed zeros, denormals, minimum normals, infinities and NaNs. Multiply detects tininess after rounding to 11 significant bits with an unbounded exponent, before gradual half packing: `0x3bff * 0x0400` flushes, while `0x3c01 * 0x03ff` rounds to `0x0400` and is kept. Directed rounding and ternary arithmetic keep their previous denormal lowering. Shaders without a known float mode keep the previous lowering. Scalar `v_sub_f16`/`v_subrev_f16` share `float16Binary` with add/mul and were measured on gfx1036 over the same operand set as add (1156 non-NaN-input rows per mode); NaN result bits still depend on the driver as recorded above.
|
||||
- [f16 unary, frexp and conversion denormals](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follow the `FLOAT_MODE` f16 denormal field: `v_ceil_f16`, `v_floor_f16`, `v_fract_f16`, `v_rcp_f16`, `v_rsq_f16`, `v_sqrt_f16`, `v_log_f16`, `v_exp_f16`, `v_sin_f16`, `v_frexp_mant_f16`, `v_frexp_exp_i16_f16`, `v_cvt_norm_i16_f16`, `v_cvt_norm_u16_f16` and `v_cvt_f32_f16` flush a denormal half input to its signed zero when bit 0 is clear (0 and 2), and the f16-result ones flush a denormal half result to its signed zero when bit 1 is clear (0 and 1), after the host rounds it to half. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with denorm16 0..3 over every non-NaN f16 input (63,490 rows per mode and instruction): the only mismatches are the 1-ULP driver differences of `v_exp_f16` (2 inputs) and `v_sin_f16` (14), which are the same with denormals kept. NaN inputs are not compared. `v_ldexp_f16` shares the input rule. `v_cvt_pknorm_i16_f16` and `v_cvt_pknorm_u16_f16` are not measured and keep their previous lowering. Shaders without a known float mode keep the previous lowering.
|
||||
- [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/mullit/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 (`v_mullit_f32` measured on RDNA2 (gfx1036) over 6907 rows per mode at IEEE=0 and IEEE=1, so a positive denormal src2 counts as zero); 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. `v_div_scale_f32` flushes all three inputs to signed zeros, measured on RDNA2 (gfx1036) over 2744 operand triples per mode at IEEE=0 and IEEE=1 with f32 denormals flushed and kept; its scale-down behaviour and host denormal preservation remain incomplete. `v_div_fixup_f32` flushes its src1 and src2 and keeps a denormal src0 and result, measured on RDNA2 (gfx1036) over 6859 rows per mode at IEEE=0 and IEEE=1. Tininess is detected after rounding, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: a product is flushed when its value rounded to 24 bits with an unbounded exponent is below 2^-126, so `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` and `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero. The check rounds in the f32 round mode of `FLOAT_MODE` (bits 1:0), measured in all four modes, while the products themselves are still rounded to nearest even. This applies to `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_f32`, and to `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. In the three directed modes the check is computed exactly from the significands. To nearest even (0, the mode of shaders without a known float mode) one factor is multiplied by 2^64, which is exact below 2^64, and the product with the other factor is compared with 2^-62. That multiply is a plain `OpFMul` like the product, and no f32 rounding mode is requested in the SPIR-V, so the check relies on the driver rounding f32 to nearest even by default, as the products do, and a product that rounds up to 2^-126 is the emitted product. `v_fma_mix_f32` follows this rule with mixed f16/f32 sources too, measured on RDNA2 (gfx1036) with f16 in either multiplicand slot, low and high half selection, and an f32 or f16 zero addend: f32 `0x00801002` times f16 `0x3bff` gives `0x00800000` to nearest at IEEE=0 and IEEE=1, while the neighbours `0x00801001` and `0x00801003` give zero and `0x00800001`. The tiny-product boundary was also measured for both signs in all four f32 round modes at IEEE=0; f16 inputs retain their previous conversion and denormal handling. `v_fma_mixlo_f16` and `v_fma_mixhi_f16` flush their f32 sources too and keep a product below 2^-126, measured on RDNA2 (gfx1036). `v_mad_legacy_f32` and `v_mac_legacy_f32` flush their inputs, their product and their result in every mode, see its entry; their product is still checked for tininess on its exact value, so one below 2^-126 gives zero even when it rounds to 2^-126, as the console's separately rounded encoding is not measured at this boundary. Denormal modes 1 and 2 throw for the other instructions. 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_dot2_f32_f16` and `v_div_fmas_f32` (on hardware `v_div_fmas_f32` flushes only its result), f32 interpolation and `ds_add_f32` keep the previous lowering; `ds_min/max/cmpst_f32` flush both operands by the field, see the LDS float atomics entry. With f32 denormals kept (3) the results depend on the driver's default denormal behaviour, as no `DenormPreserve` execution mode is requested; `v_ldexp_f32` is the exception: it preserves subnormal inputs and rounds a subnormal result in the f32 round mode of `FLOAT_MODE`, measured on RDNA2 (gfx1036) over 2163 rows in each of the four round modes at IEEE=0 and IEEE=1. An overflow rounds like any other result (infinity, or the largest finite value when the mode rounds that sign toward zero). With f32 denormals flushed it flushes a subnormal input and result, measured over the same rows in each round mode at IEEE=0 only.
|
||||
- The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.
|
||||
|
|
|
|||
Loading…
Reference in New Issue