Merge pull request #2839 from CouchRiv/fix-shader-tininess-after-rounding

fix(shader): detect f32 tininess after rounding when flushing products
This commit is contained in:
Adrià 2026-10-11 00:57:20 +02:00 committed by GitHub
commit e4045bbbbe
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 331 additions and 13 deletions

View File

@ -22,6 +22,7 @@ using ShaderRecompiler::ShaderStage;
constexpr std::uint32_t Threads = 32;
constexpr std::uint32_t Inputs = 4;
constexpr std::uint32_t Results = 19;
constexpr std::uint32_t TinyResults = 16;
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
@ -124,9 +125,248 @@ constexpr const char* Names[19] = {
"v_mullit_f32 v28, v4, v5, 1.0"
};
void Fill(std::uint32_t tid, std::uint32_t* words) {
std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words);
}
alignas(256) constexpr std::array<std::uint32_t, 30> TinyCode{
0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x10140b04u, 0x0e160b04u, 0xd54b000cu,
0x041a0b04u, 0x7e1a0306u, 0x561a0b04u, 0x581c0d04u, 0x3f7ffffeu, 0x5a1e0b04u, 0x80000000u, 0xcc200010u,
0x041a0b04u, 0xd5080011u, 0x22020b04u, 0xd5400012u, 0x041a0b04u, 0x7e260306u, 0x0c260b04u, 0xe0781000u,
0x80010a02u, 0xe0781010u, 0x80010e02u, 0xe0741020u, 0x80011202u, 0xbf810000u,
};
constexpr std::uint32_t TinyRows[32][4] = {
{0x00800001u, 0x3f7ffffeu, 0x00000000u, 0x00000000u},
{0x80800001u, 0x3f7ffffeu, 0x80000000u, 0x00000000u},
{0x3f7ffffeu, 0x00800001u, 0x00000000u, 0x00000000u},
{0x008003e6u, 0x3f7ff834u, 0x00000000u, 0x00000000u},
{0x00f80000u, 0x3f042108u, 0x80000000u, 0x00000000u},
{0x80f80000u, 0x3f042108u, 0x00000000u, 0x00000000u},
{0x00800f95u, 0x3f7fe0d9u, 0x00000000u, 0x00000000u},
{0x00800001u, 0x3f7ffffdu, 0x00000000u, 0x00000000u},
{0x00ffffffu, 0x3f000000u, 0x00000000u, 0x00000000u},
{0x008005a8u, 0x3f7ff4b0u, 0x00000000u, 0x00000000u},
{0x00f7ffffu, 0x3f042108u, 0x00000000u, 0x00000000u},
{0x00ffffffu, 0x3effffffu, 0x00000000u, 0x00000000u},
{0x008005a9u, 0x3f7ff4aeu, 0x00000000u, 0x00000000u},
{0x00800001u, 0x3f7ffffeu, 0x00000001u, 0x00000000u},
{0x007fffffu, 0x3f800001u, 0x00000000u, 0x00000000u},
{0x00800000u, 0x3f7fffffu, 0x00000000u, 0x00000000u},
{0x80800001u, 0x3f7ffffeu, 0x80000000u, 0x00000000u},
{0x00800001u, 0x3f7ffffeu, 0x00000000u, 0x00000000u},
{0xbf7ffffeu, 0x00800001u, 0x80000000u, 0x00000000u},
{0x808003e6u, 0x3f7ff834u, 0x80000000u, 0x00000000u},
{0x80f80000u, 0x3f042108u, 0x00000000u, 0x00000000u},
{0x00f80000u, 0x3f042108u, 0x80000000u, 0x00000000u},
{0x80800f95u, 0x3f7fe0d9u, 0x80000000u, 0x00000000u},
{0x80800001u, 0x3f7ffffdu, 0x80000000u, 0x00000000u},
{0x80ffffffu, 0x3f000000u, 0x80000000u, 0x00000000u},
{0x808005a8u, 0x3f7ff4b0u, 0x80000000u, 0x00000000u},
{0x80f7ffffu, 0x3f042108u, 0x80000000u, 0x00000000u},
{0x80ffffffu, 0x3effffffu, 0x80000000u, 0x00000000u},
{0x808005a9u, 0x3f7ff4aeu, 0x80000000u, 0x00000000u},
{0x80800001u, 0x3f7ffffeu, 0x80000001u, 0x00000000u},
{0x807fffffu, 0x3f800001u, 0x80000000u, 0x00000000u},
{0x80800000u, 0x3f7fffffu, 0x80000000u, 0x00000000u}
};
constexpr std::uint32_t TinyExpected[32][8] = {
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x80800000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x00800000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x3f7ffffcu, 0x00800000u, 0x00800000u, 0x80800000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x008003e5u, 0x00800000u, 0x00800000u, 0x80800000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00f7fffeu, 0x00800000u, 0x00800000u, 0x80800000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80f7fffeu, 0x80800000u, 0x80800000u, 0x00800000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00800f94u, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00800000u, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00fffffdu, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x008005a7u, 0x00800000u, 0x00800000u, 0x80800000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00f7fffdu, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00fffffdu, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x008005a8u, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x80800000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u},
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x80000000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x00800000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x80800000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0xbf7ffffcu, 0x80800000u, 0x80800000u, 0x00800000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x808003e5u, 0x80800000u, 0x80800000u, 0x00800000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80f7fffeu, 0x80800000u, 0x80800000u, 0x00800000u},
{0x00800000u, 0x00800000u, 0x00800000u, 0x00800000u, 0x00f7fffeu, 0x00800000u, 0x00800000u, 0x80800000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80800f94u, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80800000u, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80fffffdu, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x808005a7u, 0x80800000u, 0x80800000u, 0x00800000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80f7fffdu, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80fffffdu, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x808005a8u, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x80800000u, 0x00800000u},
{0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u},
{0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}
};
constexpr std::uint32_t TinyDirected[3][32][2] = {
{
{0x00800000u, 0x80000000u}, {0x80000000u, 0x00800000u}, {0x00800000u, 0x80000000u}, {0x00800000u, 0x80000000u},
{0x00800000u, 0x80000000u}, {0x80000000u, 0x00800000u}, {0x00800000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u}, {0x00800000u, 0x80000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x00800000u, 0x80000000u}, {0x00800000u, 0x80000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x80000000u, 0x00800000u}, {0x00800000u, 0x80000000u}, {0x80000000u, 0x00800000u}, {0x80000000u, 0x00800000u},
{0x80000000u, 0x00800000u}, {0x00800000u, 0x80000000u}, {0x80000000u, 0x00800000u}, {0x80000000u, 0x00000000u},
{0x80000000u, 0x00000000u}, {0x80000000u, 0x00800000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u},
{0x80000000u, 0x00800000u}, {0x80000000u, 0x00800000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}
},
{
{0x00000000u, 0x80800000u}, {0x80800000u, 0x00000000u}, {0x00000000u, 0x80800000u}, {0x00000000u, 0x80800000u},
{0x00000000u, 0x80800000u}, {0x80800000u, 0x00000000u}, {0x00000000u, 0x80800000u}, {0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u}, {0x00000000u, 0x80800000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x00000000u, 0x80800000u}, {0x00000000u, 0x80800000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x80800000u, 0x00000000u}, {0x00000000u, 0x80800000u}, {0x80800000u, 0x00000000u}, {0x80800000u, 0x00000000u},
{0x80800000u, 0x00000000u}, {0x00000000u, 0x80800000u}, {0x80800000u, 0x00000000u}, {0x80000000u, 0x00000000u},
{0x80000000u, 0x00000000u}, {0x80800000u, 0x00000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u},
{0x80800000u, 0x00000000u}, {0x80800000u, 0x00000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}
},
{
{0x00000000u, 0x80000000u}, {0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u}, {0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u}, {0x00000000u, 0x80000000u},
{0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u},
{0x80000000u, 0x00000000u}, {0x00000000u, 0x80000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u},
{0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u},
{0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}, {0x80000000u, 0x00000000u}
}
};
constexpr std::uint32_t TinyLegacyExpected[32] = {
0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u,
0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u,
0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x80000000u,
0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x80000000u
};
constexpr const char* TinyLegacyNames[2] = {
"v_mad_legacy_f32 v18, v4, v5, v6",
"v_mac_legacy_f32 v19, v4, v5"
};
constexpr const char* TinyNames[8] = {
"v_mul_f32 v10, v4, v5",
"v_mul_legacy_f32 v11, v4, v5",
"v_fma_f32 v12, v4, v5, v6",
"v_fmac_f32 v13, v4, v5",
"v_fmamk_f32 v14, v4, 0x3f7ffffe, v6",
"v_fmaak_f32 v15, v4, v5, 0x80000000",
"v_fma_mix_f32 v16, v4, v5, v6",
"v_mul_f32 v17, -v4, v5"
};
alignas(256) constexpr std::array<std::uint32_t, 28> MixedTinyCode{
0x34020084u, 0x34040086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xcc20000au, 0x141a0b04u, 0xcc20000bu,
0x0c1a0905u, 0xcc20100cu, 0x141a0b04u, 0xcc20080du, 0x0c1a0905u, 0xcc20400eu, 0x141e0b04u, 0xcc20400fu,
0x0c1e0905u, 0x7e1002ffu, 0x3f7ffffeu, 0xcc204010u, 0x041e1104u, 0xcc206011u, 0x041e1104u, 0xe0781000u,
0x80010a02u, 0xe0781010u, 0x80010e02u, 0xbf810000u,
};
constexpr std::uint32_t MixedTinyRows[Threads][Inputs] = {
{0x00801002u, 0x00003bffu, 0x00000000u, 0x00000000u},
{0x80801002u, 0x00003bffu, 0x80000000u, 0x80008000u},
{0x00801000u, 0x3bff3bffu, 0x00000000u, 0x00000000u},
{0x00801001u, 0x3bff3bffu, 0x00000000u, 0x00000000u},
{0x00801002u, 0x3bff3bffu, 0x00000000u, 0x00000000u},
{0x00801003u, 0x3bff3bffu, 0x00000000u, 0x00000000u},
{0x80801000u, 0x3bff3bffu, 0x80000000u, 0x80008000u},
{0x80801001u, 0x3bff3bffu, 0x80000000u, 0x80008000u},
{0x80801002u, 0x3bff3bffu, 0x80000000u, 0x80008000u},
{0x80801003u, 0x3bff3bffu, 0x80000000u, 0x80008000u},
{0x00801000u, 0xbbffbbffu, 0x80000000u, 0x80008000u},
{0x00801001u, 0xbbffbbffu, 0x80000000u, 0x80008000u},
{0x00801002u, 0xbbffbbffu, 0x80000000u, 0x80008000u},
{0x00801003u, 0xbbffbbffu, 0x80000000u, 0x80008000u},
{0x00800001u, 0x3bff3bffu, 0x00000000u, 0x00000000u},
{0x80800001u, 0x3bff3bffu, 0x80000000u, 0x80008000u},
};
constexpr std::uint32_t MixedTinyExpected[16][3] = {
{0x00800000u, 0x00000000u, 0x00801001u},
{0x80800000u, 0x80000000u, 0x80801001u},
{0x00000000u, 0x00000000u, 0x00800fffu},
{0x00000000u, 0x00000000u, 0x00801000u},
{0x00800000u, 0x00800000u, 0x00801001u},
{0x00800001u, 0x00800001u, 0x00801002u},
{0x80000000u, 0x80000000u, 0x80800fffu},
{0x80000000u, 0x80000000u, 0x80801000u},
{0x80800000u, 0x80800000u, 0x80801001u},
{0x80800001u, 0x80800001u, 0x80801002u},
{0x80000000u, 0x80000000u, 0x00800fffu},
{0x80000000u, 0x80000000u, 0x00801000u},
{0x80800000u, 0x80800000u, 0x00801001u},
{0x80800001u, 0x80800001u, 0x00801002u},
{0x00000000u, 0x00000000u, 0x00800000u},
{0x80000000u, 0x80000000u, 0x80800000u},
};
constexpr std::uint32_t MixedTinyDirected[3][16][2] = {
{
{0x00800000u, 0x00000000u},
{0x80000000u, 0x80000000u},
{0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u},
{0x00800000u, 0x00800000u},
{0x00800001u, 0x00800001u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80800000u, 0x80800000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80800000u, 0x80800000u},
{0x00000000u, 0x00000000u},
{0x80000000u, 0x80000000u},
},
{
{0x00000000u, 0x00000000u},
{0x80800000u, 0x80000000u},
{0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u},
{0x00800000u, 0x00800000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80800000u, 0x80800000u},
{0x80800001u, 0x80800001u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80800000u, 0x80800000u},
{0x80800001u, 0x80800001u},
{0x00000000u, 0x00000000u},
{0x80000000u, 0x80000000u},
},
{
{0x00000000u, 0x00000000u},
{0x80000000u, 0x80000000u},
{0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u},
{0x00000000u, 0x00000000u},
{0x00800000u, 0x00800000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80800000u, 0x80800000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80000000u, 0x80000000u},
{0x80800000u, 0x80800000u},
{0x00000000u, 0x00000000u},
{0x80000000u, 0x80000000u},
},
};
constexpr std::uint32_t MixedAddendDirected[3][2] = {
{0x00800000u, 0x80000000u},
{0x00000000u, 0x80800000u},
{0x00000000u, 0x80000000u},
};
constexpr const char* MixedTinyNames[8] = {
"v_fma_mix_f32 v10, v4, v5, v6 op_sel_hi:[0,1,0]",
"v_fma_mix_f32 v11, v5, v4, v6 op_sel_hi:[1,0,0]",
"v_fma_mix_f32 v12, v4, v5, v6 op_sel:[0,1,0] op_sel_hi:[0,1,0]",
"v_fma_mix_f32 v13, v5, v4, v6 op_sel:[1,0,0] op_sel_hi:[1,0,0]",
"v_fma_mix_f32 v14, v4, v5, v7 op_sel_hi:[0,1,1]",
"v_fma_mix_f32 v15, v5, v4, v7 op_sel_hi:[1,0,1]",
"v_fma_mix_f32 v16, v4, v8, v7 op_sel_hi:[0,0,1]",
"v_fma_mix_f32 v17, v4, v8, v7 op_sel:[0,0,1] op_sel_hi:[0,0,1]",
};
std::array<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
@ -147,15 +387,14 @@ bool IsNan(std::uint32_t value) {
return (value & 0x7fffffffu) > 0x7f800000u;
}
void Run(AgcDriver::VulkanDevice& device, const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]);
void Run(AgcDriver::VulkanDevice& device, const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode, std::span<const std::uint32_t> code = Code, const std::uint32_t (*rows)[Inputs] = Rows) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(rows[tid]), std::end(rows[tid]), &Input[tid * Inputs]);
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(Code);
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{
@ -185,6 +424,45 @@ void Check(const char* mode) {
}
}
void CheckTiny(const char* mode) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (std::uint32_t i = 0; i < 8; ++i) Expect(tid, Output[tid * TinyResults + i], TinyExpected[tid][i], (std::string(mode) + " " + TinyNames[i]).c_str());
}
}
void CheckMixedTiny(const std::string& mode, std::uint32_t round = 0u) {
for (std::uint32_t tid = 0; tid < std::size(MixedTinyExpected); ++tid) {
for (std::uint32_t i = 0; i < std::size(MixedTinyNames); ++i) {
const bool highFactor = i == 2u || i == 3u;
std::uint32_t expected = MixedTinyExpected[tid][i >= 6u ? 2u : highFactor ? 1u : 0u];
if (round != 0u) {
if (i >= 6u) {
if (tid < 14u) continue;
expected = MixedAddendDirected[round - 1u][tid - 14u];
} else {
if ((MixedTinyRows[tid][0] & 0x7fffffffu) == 0x00801003u) continue;
expected = MixedTinyDirected[round - 1u][tid][highFactor ? 1u : 0u];
}
}
Expect(tid, Output[tid * TinyResults + i], expected, (mode + " " + MixedTinyNames[i]).c_str());
}
}
}
void CheckTinyLegacy(const std::string& mode) {
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
for (std::uint32_t i = 0; i < 2; ++i) Expect(tid, Output[tid * TinyResults + 8u + i], TinyLegacyExpected[tid], (mode + " " + TinyLegacyNames[i]).c_str());
}
}
void CheckTinyDirected(std::uint32_t round) {
const std::string mode = "round mode " + std::to_string(round) + " ";
for (std::uint32_t tid = 0; tid < Threads; ++tid) {
Expect(tid, Output[tid * TinyResults], TinyDirected[round - 1u][tid][0], (mode + TinyNames[0]).c_str());
Expect(tid, Output[tid * TinyResults + 7u], TinyDirected[round - 1u][tid][1], (mode + TinyNames[7]).c_str());
}
}
}
int main() {
@ -197,6 +475,30 @@ int main() {
Check("IEEE=0 f32 denormals flushed");
Run(*device, ShaderRecompiler::ShaderFloatMode{0x00u, false, true, false});
Check("IEEE=1 all denormals flushed");
Run(*device, std::nullopt, MixedTinyCode, MixedTinyRows);
CheckMixedTiny("no float mode");
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}, MixedTinyCode, MixedTinyRows);
CheckMixedTiny("IEEE=0 mixed sources");
Run(*device, ShaderRecompiler::ShaderFloatMode{0x00u, false, true, false}, MixedTinyCode, MixedTinyRows);
CheckMixedTiny("IEEE=1 mixed sources");
for (std::uint32_t round = 1u; round < 4u; ++round) {
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u | round, true, false, false}, MixedTinyCode, MixedTinyRows);
CheckMixedTiny("mixed sources round mode " + std::to_string(round), round);
}
Run(*device, std::nullopt, TinyCode, TinyRows);
CheckTinyLegacy("no float mode");
CheckTiny("no float mode");
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}, TinyCode, TinyRows);
CheckTinyLegacy("IEEE=0 f32 denormals flushed");
CheckTiny("IEEE=0 f32 denormals flushed");
Run(*device, ShaderRecompiler::ShaderFloatMode{0x00u, false, true, false}, TinyCode, TinyRows);
CheckTinyLegacy("IEEE=1 all denormals flushed");
CheckTiny("IEEE=1 all denormals flushed");
for (std::uint32_t round = 1u; round < 4u; ++round) {
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u | round, true, false, false}, TinyCode, TinyRows);
CheckTinyLegacy("round mode " + std::to_string(round));
CheckTinyDirected(round);
}
for (const std::uint32_t mode : {0xd0u, 0xe0u}) {
bool refused = false;
try {

View File

@ -61,7 +61,7 @@ private:
void writeF16(const RdnaOperand& operand, IrF32 value, std::initializer_list<IrValue*> sources);
IrU32 readU32(const RdnaOperand& operand);
IrU32 flushF32Denormal(IrU32 bits);
IrF32 flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend = nullptr);
IrF32 flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend = nullptr, bool afterRounding = true);
IrU32 quietNan32(IrU32 bits);
IrU32 quietNan16(IrU32 bits);
IrValue* nanResultF32(std::initializer_list<IrValue*> sources, IrValue* result, IrValue* invalidProduct = nullptr);

View File

@ -651,7 +651,7 @@ bool TranslationContext::vFmaLegacyF32(const RdnaInstruction& inst, bool accumul
const auto factor = [&](IrValue* value) { return &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zero.Value(), &ir.ConstantF32(0.0f), value}); };
IrValue* lhsFactor = factor(lhs);
IrValue* rhsFactor = factor(rhs);
IrValue* product = &flushTinyProduct(lhsFactor, rhsFactor, &ir.Emit(IrOpcode::FPMul32, IrType::F32, {lhsFactor, rhsFactor})).Value();
IrValue* product = &flushTinyProduct(lhsFactor, rhsFactor, &ir.Emit(IrOpcode::FPMul32, IrType::F32, {lhsFactor, rhsFactor}), nullptr, false).Value();
const IrU1 zeroSum(ir.LogicalAnd(isZero(product).Value(), isZero(addend).Value()));
IrValue& zeroSumBits = ir.BitwiseAnd(ir.BitwiseAnd(ir.BitCastU32(*product), ir.BitCastU32(*addend)), ir.Constant(0x80000000u));
IrValue* sum = &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&zeroSum.Value(), &ir.BitCastF32(zeroSumBits), &ir.Emit(IrOpcode::FPAdd32, IrType::F32, {product, addend})});

View File

@ -155,7 +155,7 @@ IrU32 TranslationContext::flushF32Denormal(IrU32 bits) {
return IrU32(ir.Select(denormal.Value(), ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), bits.Value()));
}
IrF32 TranslationContext::flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend) {
IrF32 TranslationContext::flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue* product, IrValue* addend, bool afterRounding) {
if ((f32DenormalFlush & 2u) == 0u || keepsProductOutOfTinyRange(*lhs) || keepsProductOutOfTinyRange(*rhs)) return IrF32(*product);
IrValue& lhsBits = ir.BitCastU32(*lhs);
IrValue& rhsBits = ir.BitCastU32(*rhs);
@ -164,11 +164,27 @@ IrF32 TranslationContext::flushTinyProduct(IrValue* lhs, IrValue* rhs, IrValue*
IrValue& lhsExponent = exponent(lhsBits);
IrValue& rhsExponent = exponent(rhsBits);
const auto finiteNonZero = [&](IrValue& value) -> IrValue& { return ir.LogicalAnd(ir.INotEqual(value, ir.Constant(0u)), ir.INotEqual(value, ir.Constant(0xffu))); };
IrValue& carry = hasUnitSignificand(*lhs) || hasUnitSignificand(*rhs) ? ir.Constant(0u) : ir.Select(ir.UGreaterThan(ir.Emit(IrOpcode::UMulHi, IrType::U32, {&significand(lhsBits), &significand(rhsBits)}), ir.Constant(0x7fffu)), ir.Constant(1u), ir.Constant(0u));
IrValue* tiny = &ir.LogicalAnd(ir.LogicalAnd(finiteNonZero(lhsExponent), finiteNonZero(rhsExponent)), ir.ULessThan(ir.IAdd(ir.IAdd(lhsExponent, rhsExponent), carry), ir.Constant(128u)));
const std::uint32_t roundMode = floatMode.has_value() ? floatMode->floatMode & 3u : 0u;
IrValue* carry = &ir.Constant(0u);
IrValue* roundsUp = nullptr;
if (!hasUnitSignificand(*lhs) && !hasUnitSignificand(*rhs)) {
IrValue& lhsSignificand = significand(lhsBits);
IrValue& rhsSignificand = significand(rhsBits);
IrValue& high = ir.Emit(IrOpcode::UMulHi, IrType::U32, {&lhsSignificand, &rhsSignificand});
carry = &ir.Select(ir.UGreaterThan(high, ir.Constant(0x7fffu)), ir.Constant(1u), ir.Constant(0u));
if (afterRounding && roundMode != 3u) roundsUp = &ir.LogicalAnd(ir.IEqual(high, ir.Constant(0x7fffu)), ir.UGreaterThan(ir.IMul(lhsSignificand, rhsSignificand), ir.Constant(roundMode == 0u ? 0xffbfffffu : 0xff800000u)));
}
IrValue& exponentSum = ir.IAdd(ir.IAdd(lhsExponent, rhsExponent), *carry);
IrValue* tiny = &ir.LogicalAnd(ir.LogicalAnd(finiteNonZero(lhsExponent), finiteNonZero(rhsExponent)), ir.ULessThan(exponentSum, ir.Constant(128u)));
if (addend != nullptr) tiny = &ir.LogicalAnd(*tiny, ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*addend), ir.Constant(0x7fffffffu)), ir.Constant(0u)));
IrValue& sign = ir.BitwiseAnd(ir.BitwiseXor(lhsBits, rhsBits), ir.Constant(0x80000000u));
return IrF32(ir.Emit(IrOpcode::SelectF32, IrType::F32, {tiny, &ir.BitCastF32(sign), product}));
IrValue* rounded = &sign;
if (roundsUp != nullptr) {
roundsUp = &ir.LogicalAnd(ir.IEqual(exponentSum, ir.Constant(127u)), *roundsUp);
if (roundMode != 0u) roundsUp = &ir.LogicalAnd(*roundsUp, ir.IEqual(sign, ir.Constant(roundMode == 1u ? 0u : 0x80000000u)));
rounded = &ir.Select(*roundsUp, ir.BitwiseOr(sign, ir.Constant(0x00800000u)), sign);
}
return IrF32(ir.Emit(IrOpcode::SelectF32, IrType::F32, {tiny, &ir.BitCastF32(*rounded), product}));
}
IrValue* TranslationContext::readOperand(const RdnaOperand& operand, IrType type) {

View File

@ -402,7 +402,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.
- [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_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. A product is checked for tininess on its exact value: one 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`, `v_mullit_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. The hardware detects tininess after rounding to 24 bits with an unbounded exponent, measured on RDNA2 (gfx1036) for `v_mul_f32` and `v_mullit_f32` at IEEE=0 and IEEE=1: `0x3f7ffffe * 0x00800001` (2^-126 * (1 - 2^-46)) gives `0x00800000` there and zero here, while `0x3f7fffff * 0x00800000` (2^-126 * (1 - 2^-24)) gives zero on both. `v_fma_mix_f32` was measured with f32 sources only; its f16 sources are not flushed. `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. 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`, `v_div_scale_f32` 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, and `v_ldexp_f32` flushes in every mode.
- [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_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 is computed exactly from the significands for `v_mul_f32`, `v_mul_legacy_f32`, `v_mullit_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` 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`, `v_div_scale_f32` 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, 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.
- [Overlay output bus](../../core/libs/prx/libSceVideoOut/src/VideoOutDriver.cpp) (libSceVideoOut) - flips on a port opened with `SCE_VIDEO_OUT_BUS_TYPE_OVERLAY` complete and signal their events like main-bus flips, but their buffers are not shown: the overlay is not composited over the main output. Presenting them as whole frames made the window alternate between the two buses
- [PA_CL_CLIP_CNTL](../../core/libs/prx/libSceAgcDriver/Graphics/src/State.cpp) (libSceAgcDriver) - `DX_LINEAR_ATTR_CLIP_ENA` (bit 24) clear is drawn like bit 24 set, with Vulkan's clipping of `NoPerspective` outputs; what a clear bit changes on the console is undocumented.