From b5da7d00e490c7c962e9bb2f7db34ccd881cb35b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sun, 4 Oct 2026 23:53:38 +0200 Subject: [PATCH] fix(shader): apply the f32 output modifier on the bits like the hardware Co-Authored-By: Claude --- .../tests/execution/F32OutputModifier.cpp | 191 ++++++++++++++++++ .../src/TranslationContext/OperandAccess.cpp | 11 +- docs/dev/TechnicalDebt.md | 1 + 3 files changed, 202 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp new file mode 100644 index 000000000..fe668ec80 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32OutputModifier.cpp @@ -0,0 +1,191 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084, 0x34060086, 0xe0381000, 0x80000401, 0xbf8c3f70, 0xd508000a, 0x0a020b04, 0xd508000b, + 0x1a020b04, 0xd508000c, 0x12020b04, 0xd503000d, 0x0a020b04, 0xd503000e, 0x1a020b04, 0xd504000f, + 0x32020b04, 0xd54b0010, 0x0c1a0b04, 0xd54b0011, 0x1c1a0b04, 0xd50f0012, 0x1a020b04, 0xd5100013, + 0x0a020d04, 0xd5a40014, 0x1a010104, 0xd5850015, 0x12010104, 0xd5088016, 0x0a020b04, 0xd5088017, + 0x1a020b04, 0xd5038018, 0x12020d04, 0xd54b0019, 0x341a0b04, 0xe0781000, 0x80010a03, 0xe0781010, + 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x80000000u, 0x3f800000u, 0x80000000u, 0x00000000u}, + {0x80800000u, 0x3f800000u, 0x80800000u, 0x00000000u}, + {0x00ffffffu, 0x3f800000u, 0x80ffffffu, 0x00000000u}, + {0x7f7fffffu, 0x40000000u, 0x7f7fffffu, 0x00000000u}, + {0x7fc12345u, 0xffc00000u, 0xff800001u, 0x00000000u}, + {0x7fc12345u, 0x3f800000u, 0x7fc12345u, 0x00000000u}, + {0x7f800001u, 0x40000000u, 0x7f800001u, 0x00000000u}, + {0xff812345u, 0xbf800000u, 0xff812345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u}, + {0x3f800000u, 0xff000000u, 0x00000000u, 0x00000000u}, + {0x3f000000u, 0xbf800000u, 0x7f800000u, 0x00000000u}, + {0xff800000u, 0x3f000000u, 0x7fc00000u, 0x00000000u}, + {0xff000000u, 0xbf800000u, 0xbf800000u, 0x00000000u}, + {0x3f800000u, 0x01000000u, 0x3f000000u, 0x00000000u}, + {0x00000000u, 0x00800000u, 0x00800000u, 0x00000000u}, + {0x00000000u, 0x80800000u, 0x80800000u, 0x00000000u}, + {0x7f7fffffu, 0x3f000000u, 0x00000000u, 0x00000000u}, + {0x7f800000u, 0x7f800000u, 0x00000000u, 0x00000000u}, + {0x3f000000u, 0xff000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0xff000000u, 0x7f7fffffu, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x01000000u, 0x01000000u, 0x00000000u}, + {0x7fc00000u, 0x7f7fffffu, 0x00000000u, 0x00000000u}, + {0xbf800000u, 0x01000000u, 0x00000000u, 0x00000000u}, + {0x80800000u, 0x80800000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800001u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc12345u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u} +}; +constexpr std::uint32_t Expected[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x81000000u, 0x80000000u, 0x81800000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x81800000u, 0x80800000u, 0x80000000u, 0x81000000u, 0xbf000000u, 0xcfff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x017fffffu, 0x00000000u, 0x01ffffffu, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x017fffffu, 0x00000000u, 0x4c7fffffu, 0x017fffffu, 0x00000000u, 0x00000000u, 0x827fffffu}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x7effffffu, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x4fff8247u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0xffc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f000000u, 0x7fc12345u, 0x7fc12345u, 0x4fff8247u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0xff800001u, 0x7f800001u, 0x7f800001u, 0x3f800000u, 0x7f800001u, 0x7f800001u, 0x4fff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800001u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0xff812345u, 0x7f812345u, 0xff812345u, 0xff812345u, 0xbf000000u, 0xff812345u, 0xff812345u, 0xcbfdb976u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f812345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xff800000u}, + {0xff800000u, 0xfe800000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0xff800000u, 0xfe800000u, 0xfe800000u, 0x40000000u, 0x3f000000u, 0x4f7e0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0xbf800000u, 0xbe800000u, 0xc0000000u, 0xbf800000u, 0xbe800000u, 0x40000000u, 0x7f800000u, 0x7f800000u, 0xbf000000u, 0x7f800000u, 0x00000000u, 0x4f7c0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0x7fc00000u, 0x7fc00000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xcc000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x7f800000u, 0x7e800000u, 0x7f800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0x7f800000u, 0x7e800000u, 0xfe800000u, 0xc0000000u, 0xfe800000u, 0xcc800000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0xff800000u}, + {0x01800000u, 0x00800000u, 0x02000000u, 0x40000000u, 0x3f000000u, 0xc0800000u, 0x3f800000u, 0x3e800000u, 0x00800000u, 0x40000000u, 0x3f000000u, 0x4f7e0000u, 0x01800000u, 0x00800000u, 0x3f800000u, 0x40000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x81800000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x01000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x01800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x81000000u, 0x80000000u, 0x01800000u, 0x81000000u, 0x80000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x81800000u}, + {0x7f7fffffu, 0x7e7fffffu, 0x7f800000u, 0x7f800000u, 0x7effffffu, 0xff800000u, 0x7f7fffffu, 0x7e7fffffu, 0x3e800000u, 0x7f800000u, 0x7effffffu, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x4fff0000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xff800000u}, + {0xff000000u, 0xfe000000u, 0xff800000u, 0xff800000u, 0xfe800000u, 0x7f800000u, 0xff000000u, 0xfe000000u, 0xfe800000u, 0x3f800000u, 0x00000000u, 0x4f7c0000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xd0000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x7f7ffffeu, 0x7e7ffffeu, 0xff800000u, 0x7fc00000u, 0x7fc00000u, 0xfe800000u, 0xff800000u, 0xfe800000u, 0xcc800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x01800000u, 0x00800000u, 0x82000000u, 0x01800000u, 0x00800000u, 0x00000000u, 0x01800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x02000000u, 0x02000000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0xffc00000u, 0x7fc00000u, 0x7fc00000u, 0x7effffffu, 0x00000000u, 0x7fc00000u, 0x4fff8000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u}, + {0x81800000u, 0x80800000u, 0x82000000u, 0xc0000000u, 0xbf000000u, 0x40800000u, 0x81800000u, 0x80800000u, 0xbf000000u, 0x00000000u, 0xbf000000u, 0xcf810000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x02000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x81800000u, 0x80800000u, 0x02000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0xbf000000u, 0xcfff0000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc12345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x7f800000u} +}; +constexpr std::uint32_t Checked[32] = {0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0x7800u, 0x7b00u, 0x7b00u, 0x7b00u, 0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0x7f3fu, 0xffffu, 0x7b00u, 0xffffu, 0x7f3fu, 0xffffu, 0xffffu, 0x7f3fu, 0x7f3fu, 0x7f3fu, 0x7f3fu, 0xffffu}; +constexpr std::uint32_t AnyNan[32] = {0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x84ffu, 0x84ffu, 0x84ffu, 0x84ffu, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x0000u, 0x84ffu, 0x0000u, 0x0000u, 0x0000u, 0x0000u, 0x80c0u, 0x80c0u, 0x80c0u, 0x80c0u, 0x0000u}; +constexpr const char* Names[16] = { + "v_mul_f32_e64 v10, v4, v5 mul:2", + "v_mul_f32_e64 v11, v4, v5 div:2", + "v_mul_f32_e64 v12, v4, v5 mul:4", + "v_add_f32_e64 v13, v4, v5 mul:2", + "v_add_f32_e64 v14, v4, v5 div:2", + "v_sub_f32_e64 v15, -v4, v5 mul:4", + "v_fma_f32 v16, v4, v5, v6 mul:2", + "v_fma_f32 v17, v4, v5, v6 div:2", + "v_min_f32_e64 v18, v4, v5 div:2", + "v_max_f32_e64 v19, v4, v6 mul:2", + "v_floor_f32_e64 v20, v4 div:2", + "v_cvt_f32_i32_e64 v21, v4 mul:4", + "v_mul_f32_e64 v22, v4, v5 clamp mul:2", + "v_mul_f32_e64 v23, v4, v5 clamp div:2", + "v_add_f32_e64 v24, v4, v6 clamp mul:4", + "v_fma_f32 v25, -v4, v5, v6 mul:4", +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x01016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("f32 output modifier: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +void Run(AgcDriver::VulkanDevice& device) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* in = &Input[tid * Inputs]; + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + if ((Checked[tid] >> i) & 1u) Expect(tid, out[i], Expected[tid][i], Names[i]); + if ((AnyNan[tid] >> i) & 1u) Require((out[i] & 0x7fffffffu) > 0x7f800000u, std::string("f32 output modifier: lane ") + std::to_string(tid) + " " + Names[i] + " is " + Hex(out[i]) + ", expected a NaN"); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("f32 output modifier tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index d27ea5491..d1c146873 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -252,7 +252,16 @@ IrF32 TranslationContext::applyF32ResultModifiers(const RdnaOperand& operand, Ir case 2u: multiplier = 4.0f; break; default: break; } - value = IrF32(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(multiplier)})); + const IrF32 scaled(ir.Emit(IrOpcode::FPMul32, IrType::F32, {&value.Value(), &ir.ConstantF32(multiplier)})); + IrValue& bits = ir.BitCastU32(value.Value()); + IrValue& magnitude = ir.BitwiseAnd(bits, ir.Constant(0x7fffffffu)); + IrValue* result = &ir.BitCastU32(scaled.Value()); + if (operand.omod == 3u) { + result = &ir.Select(ir.ULessThan(magnitude, ir.Constant(0x01000000u)), ir.BitwiseAnd(bits, ir.Constant(0x80000000u)), *result); + } + result = &ir.Select(ir.ULessThan(magnitude, ir.Constant(0x00800000u)), ir.Constant(0u), *result); + result = &ir.Select(ir.UGreaterThan(magnitude, ir.Constant(0x7f800000u)), bits, *result); + value = IrF32(ir.BitCastF32(*result)); } if (operand.clamp) { value = IrF32(ir.Emit(IrOpcode::FPSaturate32, IrType::F32, {&value.Value()})); diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 5e7471b7c..b38809ea9 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -87,5 +87,6 @@ Throughout the project, every function at every stage either **does exactly what - [sceKeyboardGetKey2Char](../../core/libs/prx/libSceKeyboard/src/keyboard_impl.cpp) (libSceKeyboard) translates only the 101-key (US) arrangement and throws for the 106-key (Japanese) one; Ctrl and Alt do not change the character. - [libSceAudiodec](../../core/libs/prx/libSceAudiodec/Export.cpp) throws for the 24-bit PCM word size (`iBwPcm` 0): its sample layout is unknown. ATRAC9 decoding is covered only by configuration and error tests, as no ATRAC9 encoder is available for a fixture. - [sceAjmDecMp3ParseFrame](../../core/libs/prx/libSceAjm.native/src/Ajm.cpp) (libSceAjm.native) throws when the original file length is requested (`parse_ofl` non-zero): reading it from the frame's Xing/Info, VBRI or Fraunhofer data is not implemented. +- The [output modifier](../../core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp) (`mul:2`, `mul:4`, `div:2`) follows the hardware with the IEEE mode off and f32 denormals flushed, the LLVM graphics default; the shader's MODE register is not read, and the hardware ignores the modifier in the other modes. On f16 results it is ignored, as on the hardware while f16 denormals are enabled; with them flushed the hardware applies it. - [libScePlayerInvitationDialog](../../core/libs/prx/libScePlayerInvitationDialog/libScePlayerInvitationDialog.cpp) simulates dialog completion without displaying UI or sending invitations; its parameter ABI remains unverified. - [64-bit LDS atomics](../../core/shader/recompiler/SpirvBackend/src/SpirvMemory/SpirvMemoryInstructions.cpp) (`ds_*_u64`, `ds_*_b64`, `ds_*_f64`) read and write the two dwords under a workgroup lock stored after the guest LDS, as LDS is a 32-bit array without 64-bit atomics. They are atomic with respect to each other only: 32-bit accesses to the same dwords issued concurrently are not ordered by the lock. 64-bit GDS atomics throw. `ds_min/max(_rtn)_f64` and `ds_cmpst(_rtn)_f64` follow the NaN and signed-zero rules measured for the f32 forms; the f64 forms were not measured on hardware.