feat(shader): follow FP16_OVFL in every f16 result
With FP16_OVFL=1 the hardware writes an f16 result that would round to infinity as the largest finite value of that sign, unless a source operand is infinite. v_div_fixup_f16 keeps the infinity it writes for a zero denominator or an infinite numerator. Measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f16 denormals kept and flushed. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
parent
e8b47b9270
commit
0bea402a1c
|
|
@ -0,0 +1,241 @@
|
|||
#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 <optional>
|
||||
#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 = 16;
|
||||
alignas(256) std::array<std::uint32_t, Threads * Inputs> Input{};
|
||||
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
|
||||
|
||||
alignas(256) constexpr std::array<std::uint32_t, 52> Code{
|
||||
0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e140280u, 0x7e160280u, 0x7e180280u,
|
||||
0x7e1a0280u, 0x7e1c0280u, 0x7e1e0280u, 0x7e200280u, 0x7e220280u, 0x7e240280u, 0x7e260280u, 0x7e280280u,
|
||||
0x7e2a0280u, 0x7e2c0280u, 0x7e2e0280u, 0x7e300280u, 0x7e320280u, 0x64140b04u, 0x66160b04u, 0x6a180b04u,
|
||||
0xd74b000du, 0x041a0b04u, 0xd75f000eu, 0x041a0b04u, 0xcc10400fu, 0x00020b04u, 0xcc0f4010u, 0x00020b04u,
|
||||
0xcc214011u, 0x1c1a0b04u, 0x64240af9u, 0x04041504u, 0x7e26b104u, 0x7e28af04u, 0x7e2aa904u, 0x7e2cad04u,
|
||||
0x762e0f04u, 0x7e301507u, 0x7e32a107u, 0xe0781000u, 0x80010a03u, 0xe0781010u, 0x80010e03u, 0xe0781020u,
|
||||
0x80011203u, 0xe0781030u, 0x80011603u, 0xbf810000u,
|
||||
};
|
||||
|
||||
constexpr std::uint32_t Rows[32][4] = {
|
||||
{0x00007bffu, 0x00004c00u, 0x00000000u, 0x477ff000u},
|
||||
{0x00007bffu, 0x00004bffu, 0x00000000u, 0x477fefffu},
|
||||
{0x00007bffu, 0x00004c01u, 0x00000000u, 0x0000ffffu},
|
||||
{0x00007bffu, 0x00007bffu, 0x00000000u, 0x0000fff0u},
|
||||
{0x0000fbffu, 0x0000fbffu, 0x00000000u, 0x0000ffefu},
|
||||
{0x00007800u, 0x00004000u, 0x00000000u, 0x00000001u},
|
||||
{0x00007800u, 0x0000c000u, 0x00000000u, 0x00000010u},
|
||||
{0x00007c00u, 0x00003c00u, 0x00000000u, 0x7f800000u},
|
||||
{0x00007c00u, 0x00004000u, 0x00003c00u, 0xff800000u},
|
||||
{0x00007c00u, 0x00000000u, 0x00000000u, 0x7f7fffffu},
|
||||
{0x00007c00u, 0x0000fc00u, 0x00000000u, 0x47800000u},
|
||||
{0x00007e00u, 0x00003c00u, 0x00003c00u, 0x47800001u},
|
||||
{0x00007d00u, 0x00003c00u, 0x00003c00u, 0xc7800000u},
|
||||
{0x00007bffu, 0x00003c00u, 0x00004c00u, 0x00000040u},
|
||||
{0x00007bffu, 0x00004000u, 0x0000fbffu, 0x0000ffc0u},
|
||||
{0x00007bffu, 0x00004000u, 0x0000fc00u, 0x477fe000u},
|
||||
{0x00003c00u, 0x00000000u, 0x00003c00u, 0x00000010u},
|
||||
{0x00003c00u, 0x00003c00u, 0x00007c00u, 0x0000000fu},
|
||||
{0x00007bffu, 0x00000001u, 0x00003c00u, 0x477ff001u},
|
||||
{0x00000000u, 0x00000000u, 0x00000000u, 0x4b800000u},
|
||||
{0x00008000u, 0x00003c00u, 0x00000000u, 0x5f800000u},
|
||||
{0x00000001u, 0x00000001u, 0x00000000u, 0x00000028u},
|
||||
{0x00000100u, 0x00003c00u, 0x00000000u, 0x00000020u},
|
||||
{0x00000200u, 0x00003c00u, 0x00000000u, 0x0000001fu},
|
||||
{0x00004c00u, 0x00003c00u, 0x00003c00u, 0x0000000cu},
|
||||
{0x00004bffu, 0x00003c00u, 0x00003c00u, 0x0000000cu},
|
||||
{0x00005640u, 0x00005640u, 0x00005640u, 0x3f800000u},
|
||||
{0x0000fc00u, 0x00003c00u, 0x00007c00u, 0x00008000u},
|
||||
{0x00007800u, 0x00004400u, 0x00000000u, 0x7fa00000u},
|
||||
{0x0000fbffu, 0x0000cc00u, 0x00000000u, 0x00007fffu},
|
||||
{0x00007bffu, 0x0000cc00u, 0x00000000u, 0x00000002u},
|
||||
{0x00007c00u, 0x00007c00u, 0x00007c00u, 0xfc000000u}
|
||||
};
|
||||
constexpr std::uint32_t ExpectedOverflow[32][16] = {
|
||||
{0x00007bffu, 0x00007bfeu, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x7bff7bffu, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007bffu, 0x00007b80u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x7bff7bffu, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007bffu, 0x00007b80u},
|
||||
{0x00007bffu, 0x00007bfeu, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x7bff7bffu, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x000077ffu, 0x00000000u, 0x00007bffu},
|
||||
{0x00007bffu, 0x00000000u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x7bff7bffu, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00003bffu, 0x00000000u, 0x00007bffu},
|
||||
{0x0000fbffu, 0x00000000u, 0x00007bffu, 0x00007bffu, 0x00008000u, 0x7bff7bffu, 0xfbfffbffu, 0x00007bffu, 0xfbff0000u, 0x00000000u, 0x0000fe00u, 0x00008100u, 0x0000fe00u, 0x0000b7ffu, 0x00000000u, 0x00007bffu},
|
||||
{0x00007800u, 0x00007800u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x7bff7bffu, 0x78007800u, 0x00007bffu, 0x78000000u, 0x00007bffu, 0x00004b80u, 0x00000200u, 0x00001da8u, 0x00007bffu, 0x00000000u, 0x00003c00u},
|
||||
{0x00007800u, 0x00007800u, 0x0000fbffu, 0x0000fbffu, 0x00008000u, 0xfbfffbffu, 0x78007800u, 0x0000fbffu, 0x78000000u, 0x00007bffu, 0x00004b80u, 0x00000200u, 0x00001da8u, 0x00007bffu, 0x00000000u, 0x00004c00u},
|
||||
{0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x00007c00u, 0x00000000u},
|
||||
{0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00007bffu, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x0000fc00u, 0x00000000u},
|
||||
{0x00007c00u, 0x00007c00u, 0x0000fe00u, 0x0000fe00u, 0x0000fe00u, 0xfe00fe00u, 0x7c007c00u, 0x0000fe00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x00007bffu, 0x00007bffu},
|
||||
{0x0000fe00u, 0x00007c00u, 0x0000fc00u, 0x0000fc00u, 0x00008000u, 0xfc00fc00u, 0xfe00fe00u, 0x0000fc00u, 0xfe000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x00007bffu, 0x00000000u},
|
||||
{0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007bffu, 0x7e007e00u, 0x7e007e00u, 0x00007e00u, 0x7e000000u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007bffu, 0x00003c00u},
|
||||
{0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007bffu, 0x7d007d00u, 0x7d007d00u, 0x00007f00u, 0x7d000000u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x0000fbffu, 0x00000000u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007bffu, 0x00007bffu, 0x00007bffu, 0x7bff7bffu, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00007bffu, 0x00000000u, 0x00005400u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007bffu, 0x00007bffu, 0x0000fbffu, 0x7bff7bffu, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00000000u, 0x00007bfeu},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007bffu, 0x0000fc00u, 0x0000fc00u, 0x7bff7bffu, 0x7bff7bffu, 0x0000fc00u, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007bffu, 0x00007b00u},
|
||||
{0x00003c00u, 0x00003c00u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x3c003c00u, 0x00003c00u, 0x3c000000u, 0x00004000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00007bffu, 0x00000000u, 0x00004c00u},
|
||||
{0x00004000u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00007c00u, 0x3c003c00u, 0x40004000u, 0x00007c00u, 0x40000000u, 0x00004000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00007800u, 0x00000000u, 0x00004b80u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00001bffu, 0x00003c04u, 0x00007bffu, 0x1bff1bffu, 0x7bff7bffu, 0x00003c04u, 0x7bff0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007bffu, 0x00007b80u},
|
||||
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fe00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x0000fbffu, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x00007bffu, 0x00000000u},
|
||||
{0x00003c00u, 0x0000bc00u, 0x00008000u, 0x00000000u, 0x00000000u, 0x80008000u, 0x3c003c00u, 0x00000000u, 0x3c000000u, 0x00003c00u, 0x0000fbffu, 0x0000fbffu, 0x0000fbffu, 0x00008000u, 0x00007bffu, 0x00000000u},
|
||||
{0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00020002u, 0x00000000u, 0x00020000u, 0x00003c00u, 0x0000ce00u, 0x00007bffu, 0x00006c00u, 0x00007bffu, 0x00000000u, 0x00005100u},
|
||||
{0x00003c00u, 0x0000bc00u, 0x00000100u, 0x00000100u, 0x00000000u, 0x01000100u, 0x3c003c00u, 0x00000100u, 0x3c000000u, 0x00003c00u, 0x0000cc00u, 0x00007bffu, 0x00005c00u, 0x00007bffu, 0x00000000u, 0x00005000u},
|
||||
{0x00003c00u, 0x0000bc00u, 0x00000200u, 0x00000200u, 0x00000000u, 0x02000200u, 0x3c003c00u, 0x00000200u, 0x3c000000u, 0x00003c00u, 0x0000cb80u, 0x00007800u, 0x000059a8u, 0x00007bffu, 0x00000000u, 0x00004fc0u},
|
||||
{0x00004c40u, 0x00004b80u, 0x00004c00u, 0x00004c40u, 0x00004c00u, 0x4c004c00u, 0x4c404c40u, 0x00004c40u, 0x4c400000u, 0x00007bffu, 0x00004400u, 0x00002c00u, 0x00003400u, 0x00007bffu, 0x00000000u, 0x00004a00u},
|
||||
{0x00004c40u, 0x00004b7fu, 0x00004bffu, 0x00004c40u, 0x00004bffu, 0x4bff4bffu, 0x4c404c40u, 0x00004c40u, 0x4c400000u, 0x00007bf5u, 0x00004400u, 0x00002c01u, 0x00003400u, 0x00007bffu, 0x00000000u, 0x00004a00u},
|
||||
{0x00005a40u, 0x00000000u, 0x000070e2u, 0x000070eeu, 0x00005640u, 0x70e270e2u, 0x5a405a40u, 0x000070eeu, 0x5a400000u, 0x00007bffu, 0x000046a5u, 0x0000211fu, 0x00002e66u, 0x00005640u, 0x00003c00u, 0x00000000u},
|
||||
{0x0000fc00u, 0x0000fc00u, 0x0000fc00u, 0x0000fe00u, 0x00007c00u, 0xfc00fc00u, 0xfc00fc00u, 0x0000fe00u, 0xfc000000u, 0x00000000u, 0x0000fe00u, 0x00008000u, 0x0000fe00u, 0x0000fc00u, 0x00000000u, 0x00007800u},
|
||||
{0x00007800u, 0x00007800u, 0x00007bffu, 0x00007bffu, 0x00000000u, 0x7bff7bffu, 0x78007800u, 0x00007bffu, 0x78000000u, 0x00007bffu, 0x00004b80u, 0x00000200u, 0x00001da8u, 0x00007800u, 0x00007f00u, 0x00000000u},
|
||||
{0x0000fbffu, 0x0000fbfeu, 0x00007bffu, 0x00007bffu, 0x00008000u, 0x7bff7bffu, 0xfbfffbffu, 0x00007bffu, 0xfbff0000u, 0x00000000u, 0x0000fe00u, 0x00008100u, 0x0000fe00u, 0x0000fbffu, 0x00000000u, 0x00007800u},
|
||||
{0x00007bfeu, 0x00007bffu, 0x0000fbffu, 0x0000fbffu, 0x00008000u, 0xfbfffbffu, 0x7bfe7bfeu, 0x0000fbffu, 0x7bfe0000u, 0x00007bffu, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00007bffu, 0x00000000u, 0x00004000u},
|
||||
{0x00007c00u, 0x0000fe00u, 0x00007c00u, 0x00007c00u, 0x0000fe00u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x0000fbffu, 0x00000000u}
|
||||
};
|
||||
constexpr std::uint32_t ExpectedInfinity[32][16] = {
|
||||
{0x00007c00u, 0x00007bfeu, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007c00u, 0x00007b80u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x7bff7bffu, 0x00007c00u, 0x7bff0000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007bffu, 0x00007b80u},
|
||||
{0x00007c00u, 0x00007bfeu, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x000077ffu, 0x00000000u, 0x00007c00u},
|
||||
{0x00007c00u, 0x00000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00003bffu, 0x00000000u, 0x00007c00u},
|
||||
{0x0000fc00u, 0x00000000u, 0x00007c00u, 0x00007c00u, 0x00008000u, 0x7c007c00u, 0xfc00fc00u, 0x00007c00u, 0xfc000000u, 0x00000000u, 0x0000fe00u, 0x00008100u, 0x0000fe00u, 0x0000b7ffu, 0x00000000u, 0x00007bffu},
|
||||
{0x00007800u, 0x00007800u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x78007800u, 0x00007c00u, 0x78000000u, 0x00007c00u, 0x00004b80u, 0x00000200u, 0x00001da8u, 0x00007c00u, 0x00000000u, 0x00003c00u},
|
||||
{0x00007800u, 0x00007800u, 0x0000fc00u, 0x0000fc00u, 0x00008000u, 0xfc00fc00u, 0x78007800u, 0x0000fc00u, 0x78000000u, 0x00007c00u, 0x00004b80u, 0x00000200u, 0x00001da8u, 0x00007c00u, 0x00000000u, 0x00004c00u},
|
||||
{0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x00007c00u, 0x00000000u},
|
||||
{0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x00007c00u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x0000fc00u, 0x00000000u},
|
||||
{0x00007c00u, 0x00007c00u, 0x0000fe00u, 0x0000fe00u, 0x0000fe00u, 0xfe00fe00u, 0x7c007c00u, 0x0000fe00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x00007c00u, 0x00007c00u},
|
||||
{0x0000fe00u, 0x00007c00u, 0x0000fc00u, 0x0000fc00u, 0x00008000u, 0xfc00fc00u, 0xfe00fe00u, 0x0000fc00u, 0xfe000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x00007c00u, 0x00000000u},
|
||||
{0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007c00u, 0x7e007e00u, 0x7e007e00u, 0x00007e00u, 0x7e000000u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007e00u, 0x00007c00u, 0x00003c00u},
|
||||
{0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007c00u, 0x7d007d00u, 0x7d007d00u, 0x00007f00u, 0x7d000000u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x00007d00u, 0x0000fc00u, 0x00000000u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007bffu, 0x00007c00u, 0x00007bffu, 0x7bff7bffu, 0x7bff7bffu, 0x00007c00u, 0x7bff0000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00007c00u, 0x00000000u, 0x00005400u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007c00u, 0x00007bffu, 0x0000fbffu, 0x7c007c00u, 0x7bff7bffu, 0x00007bffu, 0x7bff0000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00000000u, 0x00007bfeu},
|
||||
{0x00007bffu, 0x00007bffu, 0x00007c00u, 0x0000fc00u, 0x0000fc00u, 0x7c007c00u, 0x7bff7bffu, 0x0000fc00u, 0x7bff0000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007bffu, 0x00007b00u},
|
||||
{0x00003c00u, 0x00003c00u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x3c003c00u, 0x00003c00u, 0x3c000000u, 0x00004000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00007c00u, 0x00000000u, 0x00004c00u},
|
||||
{0x00004000u, 0x00000000u, 0x00003c00u, 0x00007c00u, 0x00007c00u, 0x3c003c00u, 0x40004000u, 0x00007c00u, 0x40000000u, 0x00004000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00007800u, 0x00000000u, 0x00004b80u},
|
||||
{0x00007bffu, 0x00007bffu, 0x00001bffu, 0x00003c04u, 0x00007bffu, 0x1bff1bffu, 0x7bff7bffu, 0x00003c04u, 0x7bff0000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00000000u, 0x00007c00u, 0x00007b80u},
|
||||
{0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x0000fe00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x0000fc00u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00007c00u, 0x00000000u},
|
||||
{0x00003c00u, 0x0000bc00u, 0x00008000u, 0x00000000u, 0x00000000u, 0x80008000u, 0x3c003c00u, 0x00000000u, 0x3c000000u, 0x00003c00u, 0x0000fc00u, 0x0000fc00u, 0x0000fc00u, 0x00008000u, 0x00007c00u, 0x00000000u},
|
||||
{0x00000002u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00020002u, 0x00000000u, 0x00020000u, 0x00003c00u, 0x0000ce00u, 0x00007c00u, 0x00006c00u, 0x00007c00u, 0x00000000u, 0x00005100u},
|
||||
{0x00003c00u, 0x0000bc00u, 0x00000100u, 0x00000100u, 0x00000000u, 0x01000100u, 0x3c003c00u, 0x00000100u, 0x3c000000u, 0x00003c00u, 0x0000cc00u, 0x00007c00u, 0x00005c00u, 0x00007c00u, 0x00000000u, 0x00005000u},
|
||||
{0x00003c00u, 0x0000bc00u, 0x00000200u, 0x00000200u, 0x00000000u, 0x02000200u, 0x3c003c00u, 0x00000200u, 0x3c000000u, 0x00003c00u, 0x0000cb80u, 0x00007800u, 0x000059a8u, 0x00007c00u, 0x00000000u, 0x00004fc0u},
|
||||
{0x00004c40u, 0x00004b80u, 0x00004c00u, 0x00004c40u, 0x00004c00u, 0x4c004c00u, 0x4c404c40u, 0x00004c40u, 0x4c400000u, 0x00007c00u, 0x00004400u, 0x00002c00u, 0x00003400u, 0x00007c00u, 0x00000000u, 0x00004a00u},
|
||||
{0x00004c40u, 0x00004b7fu, 0x00004bffu, 0x00004c40u, 0x00004bffu, 0x4bff4bffu, 0x4c404c40u, 0x00004c40u, 0x4c400000u, 0x00007bf5u, 0x00004400u, 0x00002c01u, 0x00003400u, 0x00007bffu, 0x00000000u, 0x00004a00u},
|
||||
{0x00005a40u, 0x00000000u, 0x000070e2u, 0x000070eeu, 0x00005640u, 0x70e270e2u, 0x5a405a40u, 0x000070eeu, 0x5a400000u, 0x00007c00u, 0x000046a5u, 0x0000211fu, 0x00002e66u, 0x00005640u, 0x00003c00u, 0x00000000u},
|
||||
{0x0000fc00u, 0x0000fc00u, 0x0000fc00u, 0x0000fe00u, 0x00007c00u, 0xfc00fc00u, 0xfc00fc00u, 0x0000fe00u, 0xfc000000u, 0x00000000u, 0x0000fe00u, 0x00008000u, 0x0000fe00u, 0x0000fc00u, 0x00000000u, 0x00007800u},
|
||||
{0x00007800u, 0x00007800u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x7c007c00u, 0x78007800u, 0x00007c00u, 0x78000000u, 0x00007c00u, 0x00004b80u, 0x00000200u, 0x00001da8u, 0x00007800u, 0x00007f00u, 0x00000000u},
|
||||
{0x0000fc00u, 0x0000fbfeu, 0x00007c00u, 0x00007c00u, 0x00008000u, 0x7c007c00u, 0xfc00fc00u, 0x00007c00u, 0xfc000000u, 0x00000000u, 0x0000fe00u, 0x00008100u, 0x0000fe00u, 0x0000fc00u, 0x00000000u, 0x00007800u},
|
||||
{0x00007bfeu, 0x00007c00u, 0x0000fc00u, 0x0000fc00u, 0x00008000u, 0xfc00fc00u, 0x7bfe7bfeu, 0x0000fc00u, 0x7bfe0000u, 0x00007c00u, 0x00004c00u, 0x00000100u, 0x00001c00u, 0x00007c00u, 0x00000000u, 0x00004000u},
|
||||
{0x00007c00u, 0x0000fe00u, 0x00007c00u, 0x00007c00u, 0x0000fe00u, 0x7c007c00u, 0x7c007c00u, 0x00007c00u, 0x7c000000u, 0x00007c00u, 0x00007c00u, 0x00000000u, 0x00000000u, 0x00007c00u, 0x0000fc00u, 0x00000000u}
|
||||
};
|
||||
constexpr const char* Names[16] = {
|
||||
"v_add_f16",
|
||||
"v_sub_f16",
|
||||
"v_mul_f16",
|
||||
"v_fma_f16",
|
||||
"v_div_fixup_f16",
|
||||
"v_pk_mul_f16 op_sel_hi:[0,0]",
|
||||
"v_pk_add_f16 op_sel_hi:[0,0]",
|
||||
"v_fma_mixlo_f16",
|
||||
"v_add_f16_sdwa dst_sel:WORD_1",
|
||||
"v_exp_f16",
|
||||
"v_log_f16",
|
||||
"v_rcp_f16",
|
||||
"v_rsq_f16",
|
||||
"v_ldexp_f16",
|
||||
"v_cvt_f16_f32",
|
||||
"v_cvt_f16_u16"
|
||||
};
|
||||
|
||||
bool WholeDword(std::uint32_t column) {
|
||||
return column == 5u || column == 6u || column == 8u;
|
||||
}
|
||||
|
||||
bool DriverNan(std::uint32_t column) {
|
||||
return column != 4u && column != 5u && column != 6u && column != 13u && column != 15u;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
bool IsNan16(std::uint32_t half) {
|
||||
return (half & 0x7fffu) > 0x7c00u;
|
||||
}
|
||||
|
||||
void Run(AgcDriver::VulkanDevice& device, const std::optional<ShaderRecompiler::ShaderFloatMode>& floatMode) {
|
||||
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{
|
||||
{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 = floatMode;
|
||||
const auto result = ShaderRecompiler::Recompile(request);
|
||||
device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast<std::uintptr_t>(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) {
|
||||
for (std::uint32_t column = 0; column < Results; ++column) {
|
||||
const std::uint32_t actual = Output[tid * Results + column];
|
||||
const std::uint32_t wanted = expected[tid][column];
|
||||
const std::string name = std::string("fp16 overflow: lane ") + std::to_string(tid) + " " + mode + " " + Names[column] + " is " + Hex(actual) + ", expected " + Hex(wanted);
|
||||
for (std::uint32_t shift = 0; shift < 32u; shift += 16u) {
|
||||
if (shift == 16u && !WholeDword(column)) break;
|
||||
const std::uint32_t half = (actual >> shift) & 0xffffu;
|
||||
const std::uint32_t expectedHalf = (wanted >> shift) & 0xffffu;
|
||||
if (DriverNan(column) && IsNan16(expectedHalf)) {
|
||||
Require(IsNan16(half), name + " (any NaN)");
|
||||
} else {
|
||||
Require(half == expectedHalf, name);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
int main() {
|
||||
try {
|
||||
const auto device = OpenVulkanTestDevice();
|
||||
if (!device) return VulkanTestSkipped;
|
||||
Run(*device, std::nullopt);
|
||||
Check(ExpectedInfinity, "no float mode");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false});
|
||||
Check(ExpectedInfinity, "FP16_OVFL=0");
|
||||
Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, true});
|
||||
Check(ExpectedOverflow, "FP16_OVFL=1");
|
||||
std::puts("fp16 overflow tests passed");
|
||||
return 0;
|
||||
} catch (const std::exception& error) {
|
||||
std::cerr << error.what() << '\n';
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
|
@ -51,12 +51,14 @@ private:
|
|||
IrF32 applyF16ResultModifiers(const RdnaOperand& operand, IrF32 value);
|
||||
bool outputModifierApplies(std::uint32_t denormalShift) const;
|
||||
bool dx10Clamp() const { return !floatMode.has_value() || floatMode->dx10Clamp; }
|
||||
bool fp16Overflow() const { return floatMode.has_value() && floatMode->fp16Overflow; }
|
||||
IrF32 clampF16Overflow(IrF32 value, std::initializer_list<IrValue*> sources);
|
||||
void rejectHalfOrDoubleOutputModifier(const RdnaOperand& operand) const;
|
||||
IrU32 clampF16Bits(const RdnaOperand& operand, IrU32 bits);
|
||||
void writeOperand(const RdnaOperand& operand, IrValue* value);
|
||||
IrU32 packHalf2x16(IrF32 low, IrF32 high);
|
||||
void write16Bits(const RdnaOperand& operand, IrU32 value);
|
||||
void writeF16(const RdnaOperand& operand, IrF32 value);
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -128,7 +128,9 @@ void TranslationContext::vInterpP1F16(const RdnaInstruction& inst) {
|
|||
void TranslationContext::vInterpP2F16(const RdnaInstruction& inst) {
|
||||
const auto mode = interpolationModeF16(inst);
|
||||
const IrF32 delta = interpolationParameterF16(inst, 1u);
|
||||
writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPInterpolateF16, IrType::F32, {&delta.Value(), readOperand(inst.source0, IrType::F32), readOperand(inst.source3, IrType::F32), &ir.Constant(mode)})));
|
||||
IrValue* coordinate = readOperand(inst.source0, IrType::F32);
|
||||
IrValue* partial = readOperand(inst.source3, IrType::F32);
|
||||
writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPInterpolateF16, IrType::F32, {&delta.Value(), coordinate, partial, &ir.Constant(mode)})), {&delta.Value(), coordinate, partial});
|
||||
}
|
||||
|
||||
void TranslationContext::eXP(const RdnaInstruction& inst) {
|
||||
|
|
|
|||
|
|
@ -79,7 +79,7 @@ void TranslationContext::vCvtI32F32(const RdnaInstruction& inst) {
|
|||
|
||||
void TranslationContext::vCvtF16F32(const RdnaInstruction& inst) {
|
||||
const IrF32 value(*readOperand(sourceAt(inst, 0u), IrType::F32));
|
||||
writeF16(inst.destination, value);
|
||||
writeF16(inst.destination, value, {&value.Value()});
|
||||
}
|
||||
|
||||
void TranslationContext::vCvtF32F16(const RdnaInstruction& inst) {
|
||||
|
|
@ -91,7 +91,7 @@ void TranslationContext::vCvtF1616(const RdnaInstruction& inst, bool signedValue
|
|||
const IrU32 source = readU16AsU32(sourceAt(inst, 0u), signedValue);
|
||||
const IrOpcode opcode = signedValue ? IrOpcode::ConvertF32S32 : IrOpcode::ConvertF32U32;
|
||||
const IrF32 value(ir.Emit(opcode, IrType::F32, {&source.Value()}));
|
||||
writeF16(inst.destination, value);
|
||||
writeF16(inst.destination, value, {});
|
||||
}
|
||||
|
||||
void TranslationContext::vCvt16F16(const RdnaInstruction& inst, bool signedValue) {
|
||||
|
|
|
|||
|
|
@ -22,12 +22,14 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc
|
|||
return IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value(), &third.Value()}));
|
||||
};
|
||||
if (accumulator) {
|
||||
return applyF32ResultModifiers(inst.destination, ternary(readF16LaneAsF32(accumulatorOperand(inst), high, true)));
|
||||
const IrF32 acc = readF16LaneAsF32(accumulatorOperand(inst), high, true);
|
||||
return applyF32ResultModifiers(inst.destination, clampF16Overflow(ternary(acc), {&lhs.Value(), &rhs.Value(), &acc.Value()}));
|
||||
}
|
||||
if (inst.sourceCount == 3u) {
|
||||
return applyF32ResultModifiers(inst.destination, ternary(readF16LaneAsF32(sourceAt(inst, 2u), high, true)));
|
||||
const IrF32 third = readF16LaneAsF32(sourceAt(inst, 2u), high, true);
|
||||
return applyF32ResultModifiers(inst.destination, clampF16Overflow(ternary(third), {&lhs.Value(), &rhs.Value(), &third.Value()}));
|
||||
}
|
||||
return applyF32ResultModifiers(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()})));
|
||||
return applyF32ResultModifiers(inst.destination, clampF16Overflow(IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()})), {&lhs.Value(), &rhs.Value()}));
|
||||
};
|
||||
IrU32 result = packHalf2x16(translateLane(false), translateLane(true));
|
||||
if (!inst.destination.clamp || !dx10Clamp() || quietSnan) {
|
||||
|
|
@ -88,7 +90,7 @@ bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opco
|
|||
const RdnaOperand& operand = sourceAt(inst, 0u);
|
||||
const IrF32 argument = readF16AsF32(operand);
|
||||
const auto [result, invalid] = unaryFloatSpecials(opcode, argument, IrF32(ir.Emit(opcode, IrType::F32, {&argument.Value()})));
|
||||
IrU32 bits = packHalf2x16(applyF16ResultModifiers(inst.destination, result), IrF32(ir.ConstantF32(0.0f)));
|
||||
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()));
|
||||
}
|
||||
|
|
@ -199,6 +201,11 @@ bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) {
|
|||
result = IrU32(ir.Select(ir.LogicalOr(bothZero.Value(), bothInf.Value()), ir.Constant(0xfe00u), result.Value()));
|
||||
result = IrU32(ir.Select(isNan(1u).Value(), quietNan16(bits[1]).Value(), result.Value()));
|
||||
result = IrU32(ir.Select(isNan(2u).Value(), quietNan16(bits[2]).Value(), result.Value()));
|
||||
if (fp16Overflow()) {
|
||||
const IrU1 keep(ir.LogicalOr(isZero(1u).Value(), isInf(2u).Value()));
|
||||
const IrU1 overflow(ir.LogicalAnd(ir.IEqual(ir.BitwiseAnd(result.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u)), ir.LogicalNot(keep.Value())));
|
||||
result = IrU32(ir.Select(overflow.Value(), ir.BitwiseOr(ir.BitwiseAnd(result.Value(), ir.Constant(0x8000u)), ir.Constant(0x7bffu)), result.Value()));
|
||||
}
|
||||
write16Bits(inst.destination, clampF16Bits(inst.destination, result));
|
||||
return true;
|
||||
}
|
||||
|
|
@ -206,7 +213,7 @@ bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) {
|
|||
bool TranslationContext::float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse) {
|
||||
const IrF32 lhs = readF16AsF32(sourceAt(inst, reverse ? 1u : 0u));
|
||||
const IrF32 rhs = readF16AsF32(sourceAt(inst, reverse ? 0u : 1u));
|
||||
writeF16(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()})));
|
||||
writeF16(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()})), {&lhs.Value(), &rhs.Value()});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -220,14 +227,14 @@ bool TranslationContext::float16Ternary(const RdnaInstruction& inst, IrOpcode op
|
|||
args[index] = mix ? &readMixF32(operand).Value() : &readF16AsF32(operand).Value();
|
||||
}
|
||||
if (opcode == IrOpcode::FPFma32 && !mix) {
|
||||
writeF16(inst.destination, fmaF16RoundedToOdd(IrF32(*args[0]), IrF32(*args[1]), IrF32(*args[2])));
|
||||
writeF16(inst.destination, fmaF16RoundedToOdd(IrF32(*args[0]), IrF32(*args[1]), IrF32(*args[2])), {args[0], args[1], args[2]});
|
||||
return true;
|
||||
}
|
||||
if (mix) {
|
||||
writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPInterpolateF16, IrType::F32, {args[0], args[1], args[2], &ir.Constant(0u)})));
|
||||
writeF16(inst.destination, IrF32(ir.Emit(IrOpcode::FPInterpolateF16, IrType::F32, {args[0], args[1], args[2], &ir.Constant(0u)})), {args[0], args[1], args[2]});
|
||||
return true;
|
||||
}
|
||||
writeF16(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {args[0], args[1], args[2]})));
|
||||
writeF16(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {args[0], args[1], args[2]})), {args[0], args[1], args[2]});
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -313,7 +320,7 @@ bool TranslationContext::vLdexpF16(const RdnaInstruction& inst) {
|
|||
const IrU32 exponent(ir.Emit(IrOpcode::BitFieldSExtract, IrType::U32, {&exponentBits.Value(), &ir.Constant(0u), &ir.Constant(16u)}));
|
||||
const IrU32 clamped(ir.Emit(IrOpcode::SMax32, IrType::U32, {&ir.Emit(IrOpcode::SMin32, IrType::U32, {&exponent.Value(), &ir.Constant(64u)}), &ir.Constant(static_cast<std::uint32_t>(-64))}));
|
||||
IrValue& power = ir.BitCastF32(ir.ShiftLeftLogical(ir.IAdd(clamped.Value(), ir.Constant(127u)), ir.Constant(23u)));
|
||||
const IrF16 scaled(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &power})}));
|
||||
const IrF16 scaled(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&clampF16Overflow(IrF32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &power})), {&value.Value()}).Value()}));
|
||||
const IrU32 result(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&scaled.Value()})}));
|
||||
const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u)));
|
||||
write16Bits(inst.destination, clampF16Bits(inst.destination, IrU32(ir.Select(nan.Value(), quietNan16(bits).Value(), result.Value()))));
|
||||
|
|
|
|||
|
|
@ -374,6 +374,21 @@ IrF32 TranslationContext::applyF16ResultModifiers(const RdnaOperand& operand, Ir
|
|||
return dx10Clamp() ? clamped : selectF32(IrU1(ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {&value.Value()})), value, clamped);
|
||||
}
|
||||
|
||||
IrF32 TranslationContext::clampF16Overflow(IrF32 value, std::initializer_list<IrValue*> sources) {
|
||||
if (!fp16Overflow()) {
|
||||
return value;
|
||||
}
|
||||
const IrU32 bits(ir.BitCastU32(value.Value()));
|
||||
const IrU32 magnitude(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffffffu)));
|
||||
IrU1 overflow(ir.LogicalAnd(ir.LogicalNot(ir.ULessThan(magnitude.Value(), ir.Constant(0x477ff000u))), ir.ULessThan(magnitude.Value(), ir.Constant(0x7f800001u))));
|
||||
for (IrValue* source : sources) {
|
||||
const IrU1 infinite(ir.IEqual(ir.BitwiseAnd(ir.BitCastU32(*source), ir.Constant(0x7fffffffu)), ir.Constant(0x7f800000u)));
|
||||
overflow = IrU1(ir.LogicalAnd(overflow.Value(), ir.LogicalNot(infinite.Value())));
|
||||
}
|
||||
const IrU32 largest(ir.BitwiseOr(ir.BitwiseAnd(bits.Value(), ir.Constant(0x80000000u)), ir.Constant(0x477fe000u)));
|
||||
return IrF32(ir.BitCastF32(ir.Select(overflow.Value(), largest.Value(), bits.Value())));
|
||||
}
|
||||
|
||||
IrU32 TranslationContext::clampF16Bits(const RdnaOperand& operand, IrU32 bits) {
|
||||
rejectHalfOrDoubleOutputModifier(operand);
|
||||
if (!operand.clamp) {
|
||||
|
|
|
|||
|
|
@ -156,8 +156,8 @@ void TranslationContext::write16Bits(const RdnaOperand& operand, IrU32 value) {
|
|||
writeRawU32(operand, IrU32(ir.BitwiseOr(cleared.Value(), masked.Value())));
|
||||
}
|
||||
|
||||
void TranslationContext::writeF16(const RdnaOperand& operand, IrF32 value) {
|
||||
const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(operand, value).Value()}));
|
||||
void TranslationContext::writeF16(const RdnaOperand& operand, IrF32 value, std::initializer_list<IrValue*> sources) {
|
||||
const IrF16 half(ir.Emit(IrOpcode::ConvertF16F32, IrType::F16, {&applyF16ResultModifiers(operand, clampF16Overflow(value, sources)).Value()}));
|
||||
const IrU16 bits(ir.Emit(IrOpcode::BitCastU16F16, IrType::U16, {&half.Value()}));
|
||||
write16Bits(operand, IrU32(ir.Emit(IrOpcode::ConvertU32U16, IrType::U32, {&bits.Value()})));
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,7 +42,7 @@ use hyphens; Python keywords use underscores. Record these settings alongside th
|
|||
| `dx10_clamp` | 0 or 1: DX10 clamp disabled or enabled |
|
||||
| `denorm32`, `denorm16` | 0: flush input/output; 1: preserve input, flush output; 2: flush input, preserve output; 3: preserve both |
|
||||
| `round32`, `round16` | 0: nearest even; 1: toward +infinity; 2: toward -infinity; 3: toward zero |
|
||||
| `fp16_overflow` | 0: overflow to infinity; 1: clamp computed overflow to the largest finite value (infinite inputs and division by zero still produce infinity) |
|
||||
| `fp16_overflow` | 0: overflow to infinity; 1: clamp computed overflow to the largest finite value (an infinite source operand still produces infinity, as does `v_div_fixup_f16` for a zero denominator or an infinite numerator, but not for an infinite quotient; `v_rcp_f16`, `v_rsq_f16` and `v_log_f16` of zero are clamped) |
|
||||
|
||||
`denorm16` and `round16` control both f16 and f64. These values follow the
|
||||
[LLVM AMDGPU kernel descriptor documentation](https://llvm.org/docs/AMDGPUUsage.html#amdhsa-kernel-descriptor).
|
||||
|
|
|
|||
|
|
@ -335,7 +335,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- 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.
|
||||
- A [label or store](../../core/libs/prx/libSceAgcDriver/Execution/src/VulkanDevice.cpp) (`RELEASE_MEM`, `WRITE_DATA`, `DMA_DATA`) into guest memory the device has no host import of waits for all recorded work and is then written by the CPU, so the queue worker stalls once per such label. The console's CP stores it in stream order without that stall.
|
||||
- [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`, their f16 forms, `v_min_f64` and `v_max_f64`, `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 f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. With f16/f64 denormals flushed the hardware also flushes their denormal inputs (`v_min_f16(0x0001, 0x8001)` gives 0x8000), which these lowerings do not. 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.
|
||||
- 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`, their f16 forms, `v_min_f64` and `v_max_f64`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, `FP16_OVFL` by every instruction with an f16 result (see its entry), 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 f32 lowering, which matches IEEE=0. The f16 forms choose their NaN results on the bits: a NaN operand returns the other operand unchanged (src0 when both are NaN), and with IEEE=1 a signaling NaN operand returns that NaN quieted, in the same order as f32. `v_min_f64` and `v_max_f64` quiet a signaling NaN operand the same way with IEEE=1; with IEEE=0 they return src0 unchanged when src1 is NaN, otherwise src1 when src0 is NaN. Both were measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f32 denormals kept and flushed and f16/f64 denormals kept; RDNA2 has no f64 min3, max3 or med3. Unknown modes follow IEEE=0. With f16/f64 denormals flushed the hardware also flushes their denormal inputs (`v_min_f16(0x0001, 0x8001)` gives 0x8000), which these lowerings do not. NaN quieting in f16 arithmetic (the unary instructions and the f32 arithmetic follow it, see below) 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); with f32 denormals flushed it follows the f32 denormals entry below, and denormal modes 1 and 2 throw.
|
||||
- [v_mad_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) rounds the product to f32 before the add, gives a +0 product for a zero factor, and flushes its f32 denormal sources, its product and its result to a zero of the same sign in every f32 denormal mode, an exception to the `FLOAT_MODE` model in [f32DenormalFlushFor](../../core/shader/recompiler/Translation/src/VectorInstructions.cpp). Measured on gfx1010 (RX 5700 XT) with `tools/hw-oracle/template.s`, assembled by the AMD Windows driver's `amd_comgr_2.dll` and launched through its `amdhip64_6.dll` instead of `oracle.c` and ROCr, over the 32 rows of the five `FmaLegacy.cpp` encodings: identical in wave32 and wave64 at IEEE 0 and 1 with f32 denormal modes 0 to 3, except that the two signaling NaN rows are quieted only with IEEE=1 (#1281). A sum of two zeros takes its sign on the bits (+0 unless both are -0), as lavapipe returned -0 for +0 + -0. The PS5's gfx1013 is not measured; LLVM decodes the encoding as `v_mad_legacy_f32` there, as on gfx1010. RDNA2 differs: on gfx1031 (RX 6700 XT, measured by pandather in #1281) the encoding is `v_fma_legacy_f32` and fused, with the same zero rule and denormal-source flush, so an RDNA2 measurement is not evidence for the console.
|
||||
- [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.
|
||||
|
|
@ -382,6 +382,7 @@ Throughout the project, every function at every stage either **does exactly what
|
|||
- [ATRAC9 gapless segments](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) - a run job stops after the gapless total of output samples counted from the start of the segment, leaving the rest of its input unconsumed, and the next run starts the next segment with the decoder as it is and without a second skip. FMOD in PPSA29141 sets a total shorter than a sound's frames and reads the frames after it on later runs; with the output held at the total its stream reader waited for them forever and the last buffer repeated. The console's handling of the frames after the total is not documented; a reset (`SetGaplessDecode` with the reset flag, `ClearContext`, initialization) starts a new segment. An ATRAC9 run job without `RUN_MULTIPLE_FRAMES` decodes its whole input as one with it: FMOD in PPSA29141 puts two run jobs in one batch with the second's input right after the whole of the first's. Whether the console stops after one frame there is not verified; MP3 and Opus run jobs still stop after one frame
|
||||
- The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the shader's float mode as measured on RDNA2 (gfx1035): the hardware applies it only with IEEE mode off and output denormals of the result's precision flushed (bit 1 of the `FLOAT_MODE` denormal field clear), and ignores it otherwise. On f32 results it is applied as the hardware does with f32 denormals flushed; a shader without a known float mode is translated with IEEE mode off and f32 denormals flushed, the mode titles use. On f16 and f64 results it is ignored, and it throws in the modes where the hardware applies it.
|
||||
- The [clamp modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows `DX10_CLAMP`, measured on RDNA2 (gfx1035) for f32, f16, packed f16 and f64 results at IEEE=0 and IEEE=1: with `DX10_CLAMP`=1 (and in shaders without a known float mode) a NaN result clamps to +0, including the quieted NaN that `v_pk_min_f16` and `v_pk_max_f16` return for a signaling NaN operand with IEEE=1 (measured on RDNA2 gfx1036 at both `IEEE_MODE` and both `DX10_CLAMP` settings); with `DX10_CLAMP`=0 a NaN result is written unchanged and every other value clamps to [0, 1] as before. f64 results follow the same rule on hardware, but a clamped f64 result with `DX10_CLAMP`=0 throws until it has an execution test. With `DX10_CLAMP`=0 the NaN's bits are those the translation already produces for the instruction without clamp, so where those are left to the driver (f32 and scalar f16 arithmetic) the payload is the driver's, not the hardware's input propagation. `v_dot2_f32_f16` and the integer clamps are unchanged.
|
||||
- [FP16_OVFL](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) follows the shader's float mode as measured on RDNA2 (gfx1036) at IEEE=0 and IEEE=1 with f16 denormals kept and flushed: with `FP16_OVFL`=1 an f16 result that rounds to ±infinity is written as ±0x7bff unless a source operand is infinite, so `v_rcp_f16`, `v_rsq_f16` and `v_log_f16` of ±0 and `v_exp_f16` of 16 or more are clamped while an infinite input passes through, and `v_div_fixup_f16` keeps the infinity it writes for a zero denominator or an infinite numerator and clamps any other infinite quotient, a NaN one included. `v_cvt_pkrtz_f16_f32` rounds toward zero and never overflows. The sources are checked as the f32 values the translation computes with; the rule is applied to `v_fma_mix*_f16` with f32 sources and to `v_interp_p2_f16` without a measurement of infinite f32 sources or parameters. Round modes other than nearest even are not modelled, so the overflow threshold is the nearest-even one (65520).
|
||||
- [Comparison sampling of a color texture](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (`image_sample_c`/`image_sample_c_lz` on a texture that is not R32 float or R16 unorm) is emulated in the shader, since Vulkan compares only depth formats: the red channel is fetched, compared and, for bilinear filtering, blended (compare, then filter). Only 2D and 2D array views, float/unorm/snorm formats, wrap, clamp-to-edge and clamp-to-border addressing (border red 0 for the black borders, 1 for opaque white) and point or bilinear filtering at one level are implemented, with texel offsets added to the texel indices; LOD biases and clamps are accepted, as they cannot select another level of a single-level sample. Half-border addressing, the border color table, gradients, gathers, other dimensions, anisotropy and mip filtering throw. Under bilinear filtering, taps past a clamp-to-border edge compare the border value and blend with the texel taps, so results near the edge depend on the filter weights. AMD documentation does not say how a color format's reference is handled: it is clamped to [0, 1] for unorm and [-1, 1] for snorm, as Vulkan does for unorm depth, and not clamped for float. Bilinear weights are full float, not the hardware's fixed-point subtexel weights
|
||||
- [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations, so `scePlayerInvitationDialogGetResult` reports `USER_CANCELED`; its parameter ABI remains unverified. The 40-byte result layout (error code, result, 32 reserved bytes) is taken from [SharpProspero](https://github.com/SvenGDK/SharpProspero/blob/main/src/SharpProspero/Interop/Dialog/PlayerInvitationDialog.cs) and the error codes from the other common dialogs, not confirmed on a title.
|
||||
- [Converted unorm images](../../core/shader/recompiler/SpirvBackend/src/SpirvImageEmitter.cpp) (10_11_11_UNORM, read and written through an R32_UINT view): samples, gathers, LOD queries, 16-bit data and a DST_SEL of the missing W channel throw.
|
||||
|
|
|
|||
Loading…
Reference in New Issue