From f04ce007b6dccbafa1b0f48520423292a163787b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Fri, 9 Oct 2026 00:13:09 +0200 Subject: [PATCH] feat(shader): follow DX10_CLAMP in the clamp modifier Co-Authored-By: Claude --- .../tests/execution/Dx10Clamp.cpp | 243 ++++++++++++++++++ .../Translation/TranslationContext.hpp | 1 + .../Translation/src/Float64Instructions.cpp | 1 + .../Translation/src/FloatInstructions.cpp | 7 +- .../src/TranslationContext/OperandAccess.cpp | 9 +- docs/dev/TechnicalDebt.md | 3 +- 6 files changed, 257 insertions(+), 7 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Dx10Clamp.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Dx10Clamp.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Dx10Clamp.cpp new file mode 100644 index 0000000000..d4910b9915 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Dx10Clamp.cpp @@ -0,0 +1,243 @@ +#include "prx/libSceAgcDriver/Execution/include/VulkanDevice.hpp" +#include "prx/libSceAgcDriver/Graphics/include/Draw.hpp" +#include "Recompiler.hpp" +#include "VulkanTestDevice.hpp" +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +using AgcDriver::Graphics::Require; +using ShaderRecompiler::ShaderStage; + +constexpr std::uint32_t Threads = 32; +constexpr std::uint32_t Inputs = 4; +constexpr std::uint32_t Results = 16; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array Code{ + 0x34020084u, 0x34060086u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0x7e140280u, 0x7e160280u, 0x7e180280u, + 0x7e1a0280u, 0x7e1c0280u, 0x7e1e0280u, 0x7e200280u, 0x7e220280u, 0x7e240280u, 0x7e260280u, 0x7e280280u, + 0x7e2a0280u, 0x7e2c0280u, 0x7e2e0280u, 0x7e300280u, 0x7e320280u, 0xd503800au, 0x02020b04u, 0xd508800bu, + 0x02020b04u, 0xd54b800cu, 0x041a0b04u, 0xd510800du, 0x02020b04u, 0xd557800eu, 0x041a0b04u, 0xd5a0800fu, + 0x02010104u, 0xd5048010u, 0x02020d05u, 0xd5088011u, 0x0a020d04u, 0x2c3c0e90u, 0xd5328012u, 0x02023d07u, + 0xd5398013u, 0x02023d07u, 0xd74b9014u, 0x041e0f07u, 0xd58a8015u, 0x02010106u, 0xcc10d016u, 0x0a020f07u, + 0xcc0fd017u, 0x0a020f07u, 0xcc12d018u, 0x0a020f07u, 0xcc0ed019u, 0x0c1e0f07u, 0xe0781000u, 0x80010a03u, + 0xe0781010u, 0x80010e03u, 0xe0781020u, 0x80011203u, 0xe0781030u, 0x80011603u, 0xbf810000u, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x7fc00000u, 0xff812345u, 0xffc00000u, 0x7c007e00u}, + {0xffc00000u, 0x40400000u, 0xff800000u, 0x7e00fd23u}, + {0x7f800001u, 0xffc00000u, 0x00000000u, 0x0000fc00u}, + {0xff812345u, 0xbf800000u, 0x7fc00000u, 0xfc003800u}, + {0x7fc12345u, 0x3f800001u, 0x7f800000u, 0xfe008000u}, + {0x7f800000u, 0xff800000u, 0x40400000u, 0x80003c01u}, + {0xff800000u, 0xbe800000u, 0x3f800001u, 0x3c007c01u}, + {0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0x7c017c00u}, + {0xbf800000u, 0x00000000u, 0x3f000000u, 0xb400bc00u}, + {0x3f000000u, 0x7f800001u, 0x3f7fffffu, 0xbc000000u}, + {0x40400000u, 0x3f000000u, 0xff812345u, 0xfd233bffu}, + {0x00000000u, 0x7fc00000u, 0xbf800000u, 0x3bfffe00u}, + {0x80000000u, 0x3f800000u, 0xbe800000u, 0x38007e55u}, + {0xbe800000u, 0x3f7fffffu, 0x7f800001u, 0x7e553c00u}, + {0x3f7fffffu, 0x7f800000u, 0x3f800000u, 0x3c014200u}, + {0x3f800001u, 0x80000000u, 0x80000000u, 0x4200b400u}, + {0x7fc00000u, 0xff812345u, 0xffc00000u, 0x7c007e00u}, + {0xffc00000u, 0x40400000u, 0xff800000u, 0x7e00fd23u}, + {0x7f800001u, 0xffc00000u, 0x00000000u, 0x0000fc00u}, + {0xff812345u, 0xbf800000u, 0x7fc00000u, 0xfc003800u}, + {0x7fc12345u, 0x3f800001u, 0x7f800000u, 0xfe008000u}, + {0x7f800000u, 0xff800000u, 0x40400000u, 0x80003c01u}, + {0xff800000u, 0xbe800000u, 0x3f800001u, 0x3c007c01u}, + {0x3f800000u, 0x7fc12345u, 0x7fc12345u, 0x7c017c00u}, + {0xbf800000u, 0x00000000u, 0x3f000000u, 0xb400bc00u}, + {0x3f000000u, 0x7f800001u, 0x3f7fffffu, 0xbc000000u}, + {0x40400000u, 0x3f000000u, 0xff812345u, 0xfd233bffu}, + {0x00000000u, 0x7fc00000u, 0xbf800000u, 0x3bfffe00u}, + {0x80000000u, 0x3f800000u, 0xbe800000u, 0x38007e55u}, + {0xbe800000u, 0x3f7fffffu, 0x7f800001u, 0x7e553c00u}, + {0x3f7fffffu, 0x7f800000u, 0x3f800000u, 0x3c014200u}, + {0x3f800001u, 0x80000000u, 0x80000000u, 0x4200b400u} +}; +constexpr std::uint32_t ExpectedDx10[32][16] = { + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 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, 0x00003800u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x38003800u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x00003c00u}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003800u, 0x34003400u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003bffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3bff3bffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003bffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3bff3bffu, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00003800u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x38003800u, 0x00000000u}, + {0x3f3fffffu, 0x00000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u, 0x3f400000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f7fffffu, 0x3f800000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x34000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x3c000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 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, 0x00003800u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x38003800u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x00003c00u}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003800u, 0x34003400u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003bffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3bff3bffu, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003bffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3bff3bffu, 0x00000000u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00003800u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x38003800u, 0x00000000u}, + {0x3f3fffffu, 0x00000000u, 0x00000000u, 0x3f7fffffu, 0x00000000u, 0x3f400000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f7fffffu, 0x3f800000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x34000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x3c000000u} +}; +constexpr std::uint32_t ExpectedNoDx10[32][16] = { + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0xff812345u, 0x7fc00000u, 0x00007e00u, 0x00003c00u, 0x00007e00u, 0x0000fe00u, 0x7e007e00u, 0x7e007e00u, 0x3c003c00u, 0x7e007e00u}, + {0xffc00000u, 0xffc00000u, 0xffc00000u, 0x3f800000u, 0x00000000u, 0xffc00000u, 0x3f800000u, 0xffc00000u, 0x0000fd23u, 0x0000fd23u, 0x0000fd23u, 0x00000000u, 0x7e00fd23u, 0x7e00fd23u, 0x7e00fd23u, 0x7e00fd23u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x00000000u, 0x7f800001u, 0xffc00000u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x0000fe00u, 0x00000000u, 0xfe00fe00u, 0x00000000u, 0x00000000u, 0xfe00fe00u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0x00000000u, 0x00000000u, 0xff812345u, 0xffc00000u, 0xff812345u, 0x00000000u, 0x00003800u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00000000u, 0x38003800u, 0x00000000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x3f800000u, 0x7fc12345u, 0x00000000u, 0x7fc12345u, 0x0000fe00u, 0x00000000u, 0x0000fe00u, 0x00003c00u, 0xfe00fe00u, 0xfe00fe00u, 0x00000000u, 0xfe00fe00u}, + {0xffc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x00003c00u}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00007c01u, 0x00003c00u, 0x00007c01u, 0x00003c00u, 0x7c017c01u, 0x7c017c01u, 0x3c003c00u, 0x7c017c01u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0x00007c01u, 0x00003c00u, 0x00007c01u, 0x00007e09u, 0x7c017c01u, 0x7c017c01u, 0x3c003c00u, 0x7c017c01u}, + {0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003800u, 0x34003400u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x7f800001u, 0x3f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0xff812345u, 0x3f800000u, 0x3f000000u, 0x00000000u, 0x7f812345u, 0xff812345u, 0x0000fd23u, 0x00003bffu, 0x0000fd23u, 0x0000fe09u, 0xfd23fd23u, 0xfd23fd23u, 0x3bff3bffu, 0xfd23fd23u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x0000fe00u, 0x00003bffu, 0x0000fe00u, 0x00000000u, 0xfe00fe00u, 0xfe00fe00u, 0x3bff3bffu, 0xfe00fe00u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00007e55u, 0x00003800u, 0x00007e55u, 0x00000000u, 0x7e557e55u, 0x7e557e55u, 0x38003800u, 0x7e557e55u}, + {0x3f3fffffu, 0x00000000u, 0x7f800001u, 0x3f7fffffu, 0x00000000u, 0x3f400000u, 0xff800001u, 0x7f800001u, 0x00007e55u, 0x00003c00u, 0x00007e55u, 0x00007e00u, 0x7e557e55u, 0x7e557e55u, 0x3c003c00u, 0x7e557e55u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f7fffffu, 0x3f800000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x34000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x3c000000u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0xff812345u, 0x7fc00000u, 0x00007e00u, 0x00003c00u, 0x00007e00u, 0x0000fe00u, 0x7e007e00u, 0x7e007e00u, 0x3c003c00u, 0x7e007e00u}, + {0xffc00000u, 0xffc00000u, 0xffc00000u, 0x3f800000u, 0x00000000u, 0xffc00000u, 0x3f800000u, 0xffc00000u, 0x0000fd23u, 0x0000fd23u, 0x0000fd23u, 0x00000000u, 0x7e00fd23u, 0x7e00fd23u, 0x7e00fd23u, 0x7e00fd23u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x7f800001u, 0x00000000u, 0x7f800001u, 0xffc00000u, 0x7f800001u, 0x00000000u, 0x00000000u, 0x0000fe00u, 0x00000000u, 0xfe00fe00u, 0x00000000u, 0x00000000u, 0xfe00fe00u}, + {0xff812345u, 0xff812345u, 0xff812345u, 0x00000000u, 0x00000000u, 0xff812345u, 0xffc00000u, 0xff812345u, 0x00000000u, 0x00003800u, 0x00000000u, 0x00007e00u, 0x00000000u, 0x00000000u, 0x38003800u, 0x00000000u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x3f800000u, 0x7fc12345u, 0x00000000u, 0x7fc12345u, 0x0000fe00u, 0x00000000u, 0x0000fe00u, 0x00003c00u, 0xfe00fe00u, 0xfe00fe00u, 0x00000000u, 0xfe00fe00u}, + {0xffc00000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0xffc00000u, 0x00000000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x00003c00u}, + {0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00007c01u, 0x00003c00u, 0x00007c01u, 0x00003c00u, 0x7c017c01u, 0x7c017c01u, 0x3c003c00u, 0x7c017c01u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc12345u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x7fc12345u, 0x7fc12345u, 0x00007c01u, 0x00003c00u, 0x00007c01u, 0x00007e09u, 0x7c017c01u, 0x7c017c01u, 0x3c003c00u, 0x7c017c01u}, + {0x00000000u, 0x00000000u, 0x3f000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003800u, 0x34003400u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7f800001u, 0x7f800001u, 0x7f800001u, 0x3f000000u, 0x3f000000u, 0x3f000000u, 0x7f800001u, 0x3f7fffffu, 0x00000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x3f800000u, 0x3f800000u, 0xff812345u, 0x3f800000u, 0x3f000000u, 0x00000000u, 0x7f812345u, 0xff812345u, 0x0000fd23u, 0x00003bffu, 0x0000fd23u, 0x0000fe09u, 0xfd23fd23u, 0xfd23fd23u, 0x3bff3bffu, 0xfd23fd23u}, + {0x7fc00000u, 0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x00000000u, 0x0000fe00u, 0x00003bffu, 0x0000fe00u, 0x00000000u, 0xfe00fe00u, 0xfe00fe00u, 0x3bff3bffu, 0xfe00fe00u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00007e55u, 0x00003800u, 0x00007e55u, 0x00000000u, 0x7e557e55u, 0x7e557e55u, 0x38003800u, 0x7e557e55u}, + {0x3f3fffffu, 0x00000000u, 0x7f800001u, 0x3f7fffffu, 0x00000000u, 0x3f400000u, 0xff800001u, 0x7f800001u, 0x00007e55u, 0x00003c00u, 0x00007e55u, 0x00007e00u, 0x7e557e55u, 0x7e557e55u, 0x3c003c00u, 0x7e557e55u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f7fffffu, 0x3f800000u, 0x3f800000u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x00003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u, 0x3c003c00u}, + {0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x34000000u, 0x00000000u, 0x00000000u, 0x00003c00u, 0x00003c00u, 0x00000000u, 0x00000000u, 0x00000000u, 0x3c003c00u, 0x3c003c00u, 0x3c000000u} +}; +constexpr const char* Names[16] = { + "v_add_f32 clamp", + "v_mul_f32 clamp", + "v_fma_f32 clamp", + "v_max_f32 clamp", + "v_med3_f32 clamp", + "v_fract_f32 clamp", + "v_sub_f32 clamp", + "v_mul_f32 clamp mul:2", + "v_add_f16 clamp", + "v_max_f16 clamp", + "v_fma_f16 clamp", + "v_cvt_f16_f32 clamp", + "v_pk_mul_f16 clamp", + "v_pk_add_f16 clamp", + "v_pk_max_f16 clamp", + "v_pk_fma_f16 clamp" +}; + +void Fill(std::uint32_t tid, std::uint32_t* words) { + std::copy(std::begin(Rows[tid]), std::end(Rows[tid]), words); +} + +std::array BufferDescriptor(const void* data, std::uint32_t bytes) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu), bytes, 0x31016facu}; +} + +std::string Hex(std::uint32_t value) { + char text[16]; + std::snprintf(text, sizeof(text), "0x%08x", value); + return text; +} + +void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { + Require(actual == expected, std::string("dx10 clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); +} + +bool IsNan32(std::uint32_t value) { + return (value & 0x7fffffffu) > 0x7f800000u; +} + +bool IsNan16(std::uint32_t value) { + return (value & 0x7fffu) > 0x7c00u; +} + +void Run(AgcDriver::VulkanDevice& device, const std::optional& floatMode) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) Fill(tid, &Input[tid * Inputs]); + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size() * 4u)); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size() * 4u)); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(Code); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0u, {false, false, false}, false, 1}; + ShaderRecompiler::RecompileRequest request{ + {ShaderStage::Compute, reinterpret_cast(code.data()), code, 0, {}}, + {32, 0, userData, compute, std::nullopt, std::nullopt, memory}, + device.Target(), + {0, 0, 0, 128} + }; + request.useCache = false; + request.context.floatMode = floatMode; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const std::uint32_t (&expected)[32][16], const char* mode) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + const std::uint32_t* out = &Output[tid * Results]; + for (std::uint32_t i = 0; i < 16; ++i) { + const std::string name = std::string(mode) + " " + Names[i]; + const bool driverNan = (i < 8u && IsNan32(expected[tid][i])) || (i >= 8u && i < 12u && IsNan16(expected[tid][i])); + if (driverNan) { + Require(i < 8u ? IsNan32(out[i]) : IsNan16(out[i]), std::string("dx10 clamp: lane ") + std::to_string(tid) + " " + name + " is " + Hex(out[i]) + ", expected a NaN"); + continue; + } + Expect(tid, i >= 8u && i < 12u ? out[i] & 0xffffu : out[i], i >= 8u && i < 12u ? expected[tid][i] & 0xffffu : expected[tid][i], name.c_str()); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device, std::nullopt); + Check(ExpectedDx10, "no float mode"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}); + Check(ExpectedDx10, "DX10_CLAMP=1"); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, false, false, false}); + Check(ExpectedNoDx10, "DX10_CLAMP=0"); + std::puts("dx10 clamp tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index d0bb3366ee..d1d94b4ea1 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -49,6 +49,7 @@ private: IrF32 applyF32ResultModifiers(const RdnaOperand& operand, IrF32 value); IrF32 applyF16ResultModifiers(const RdnaOperand& operand, IrF32 value); bool outputModifierApplies(std::uint32_t denormalShift) const; + bool dx10Clamp() const { return !floatMode.has_value() || floatMode->dx10Clamp; } void rejectHalfOrDoubleOutputModifier(const RdnaOperand& operand) const; IrU32 clampF16Bits(const RdnaOperand& operand, IrU32 bits); void writeOperand(const RdnaOperand& operand, IrValue* value); diff --git a/core/shader/recompiler/Translation/src/Float64Instructions.cpp b/core/shader/recompiler/Translation/src/Float64Instructions.cpp index 885d0c30b8..47e19cf033 100644 --- a/core/shader/recompiler/Translation/src/Float64Instructions.cpp +++ b/core/shader/recompiler/Translation/src/Float64Instructions.cpp @@ -39,6 +39,7 @@ void TranslationContext::writeF64Result(const RdnaOperand& operand, IrValue& val destination.omod = 0u; IrValue* result = &value; if (destination.clamp) { + if (!dx10Clamp()) throw std::runtime_error("clamp on an f64 result with DX10_CLAMP=0 is not implemented"); result = &ir.Emit(IrOpcode::FPSaturate64, IrType::U64, {result}); } writeOperand(destination, result); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index b29a5cc4eb..f904367e46 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -28,7 +28,7 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc return applyF32ResultModifiers(inst.destination, IrF32(ir.Emit(opcode, IrType::F32, {&lhs.Value(), &rhs.Value()}))); }; IrU32 result = packHalf2x16(translateLane(false), translateLane(true)); - if (!inst.destination.clamp) { + if (!inst.destination.clamp || !dx10Clamp()) { const auto laneBits = [&](const RdnaOperand& operand, bool high) { const IrU32 source = readF16SourceBits(operand); const bool selectHigh = high ? operand.opSelHi : operand.opSel; @@ -75,7 +75,8 @@ bool TranslationContext::packedFloat16(const RdnaInstruction& inst, IrOpcode opc } return lane; }; - result = packU16Lanes(nanLane(false), nanLane(true)); + const auto clampedLane = [&](bool high) { return inst.destination.clamp ? clampF16Bits(inst.destination, nanLane(high)) : nanLane(high); }; + result = packU16Lanes(clampedLane(false), clampedLane(true)); } writeRawU32(inst.destination, result); return true; @@ -92,7 +93,7 @@ bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opco const IrU32 half = readF16Bits(operand); const IrU1 nan(ir.UGreaterThan(ir.BitwiseAnd(half.Value(), ir.Constant(0x7fffu)), ir.Constant(0x7c00u))); const IrU32 special(ir.Select(nan.Value(), ir.BitwiseOr(half.Value(), ir.Constant(0x0200u)), ir.Constant(0xfe00u))); - const IrU32 value(ir.Select(ir.LogicalOr(nan.Value(), invalid.Value()), inst.destination.clamp ? ir.Constant(0u) : special.Value(), bits.Value())); + const IrU32 value(ir.Select(ir.LogicalOr(nan.Value(), invalid.Value()), inst.destination.clamp && dx10Clamp() ? ir.Constant(0u) : special.Value(), bits.Value())); write16Bits(inst.destination, value); return true; } diff --git a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp index 6914fa28d8..3e949f17ca 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/OperandAccess.cpp @@ -299,7 +299,8 @@ IrF32 TranslationContext::applyF32ResultModifiers(const RdnaOperand& operand, Ir value = IrF32(ir.BitCastF32(*result)); } if (operand.clamp) { - value = IrF32(ir.Emit(IrOpcode::FPSaturate32, IrType::F32, {&value.Value()})); + const IrF32 saturated(ir.Emit(IrOpcode::FPSaturate32, IrType::F32, {&value.Value()})); + value = dx10Clamp() ? saturated : selectF32(IrU1(ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {&value.Value()})), value, saturated); } return value; } @@ -312,7 +313,8 @@ IrF32 TranslationContext::applyF16ResultModifiers(const RdnaOperand& operand, Ir const IrF32 zero(ir.ConstantF32(0.0f)); const IrU1 positive(ir.Emit(IrOpcode::FPOrdGreaterThan32, IrType::U1, {&value.Value(), &zero.Value()})); const IrF32 limited(ir.Emit(IrOpcode::FPMin32, IrType::F32, {&value.Value(), &ir.ConstantF32(1.0f)})); - return selectF32(positive, limited, zero); + const IrF32 clamped = selectF32(positive, limited, zero); + return dx10Clamp() ? clamped : selectF32(IrU1(ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {&value.Value()})), value, clamped); } IrU32 TranslationContext::clampF16Bits(const RdnaOperand& operand, IrU32 bits) { @@ -323,7 +325,8 @@ IrU32 TranslationContext::clampF16Bits(const RdnaOperand& operand, IrU32 bits) { const IrU32 magnitude(ir.BitwiseAnd(bits.Value(), ir.Constant(0x7fffu))); const IrU1 zero(ir.LogicalOr(ir.UGreaterThan(bits.Value(), ir.Constant(0x7fffu)), ir.UGreaterThan(magnitude.Value(), ir.Constant(0x7c00u)))); const IrU32 limited(ir.Select(ir.UGreaterThan(magnitude.Value(), ir.Constant(0x3c00u)), ir.Constant(0x3c00u), bits.Value())); - return IrU32(ir.Select(zero.Value(), ir.Constant(0u), limited.Value())); + const IrU32 clamped(ir.Select(zero.Value(), ir.Constant(0u), limited.Value())); + return dx10Clamp() ? clamped : IrU32(ir.Select(ir.UGreaterThan(magnitude.Value(), ir.Constant(0x7c00u)), bits.Value(), clamped.Value())); } IrU32 TranslationContext::readScalarCode(std::uint32_t code) { diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 8f00dfc80d..ce81a835a4 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -286,7 +286,7 @@ Throughout the project, every function at every stage either **does exactly what - A [COND_EXEC range](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) must hold whole packets and end inside its command buffer. A chained INDIRECT_BUFFER or a REWIND inside a range throws, as what the CP does with the skipped dwords then is unknown. - A [COND_INDIRECT_BUFFER](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (`sceAgcCbBranch`) runs only with the always-pass compare function; a comparison or a predicated packet throws. Modes 1 and 2 run the first buffer, assumed to be the if-then and if-then-else modes of AMD's PM4 packet. [sceAgcCbBranch](../../core/libs/prx/libSceAgc/Cb/src/Branch.cpp) does not check the compare address of an always-pass branch or the address of an empty buffer, as PPSA14632 passes an uninitialised compare address and a null else buffer of 0 dwords. - [SET_*_REG_INDIRECT](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Queues/Submission.cpp) (libSceAgcDriver) reads its register list when the command buffer is submitted, with the packets. The console's graphics PFP reads it when it reaches the packet, so a list written after the submission (by the CPU, or by GPU work ahead of the packet behind PFP_SYNC_ME) is read here before that write. -- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. The f16 and f64 min/max/med3, NaN quieting in arithmetic, `DX10_CLAMP`, `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. +- The [shader float mode](../../core/libs/prx/libSceAgcDriver/Execution/src/Driver/Shaders/ShaderRegistry.cpp) (`FLOAT_MODE`, `DX10_CLAMP`, `IEEE_MODE` and `FP16_OVFL` of `COMPUTE_PGM_RSRC1` and `SPI_SHADER_PGM_RSRC1_PS/GS/HS`, bit positions from Mesa's `gfx10.json` and `gfx103.json`) is decoded into `GuestContext::floatMode` and is part of the shader cache keys and the serialized request (#981). `IEEE_MODE` is read by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`, `IEEE_MODE` and the `FLOAT_MODE` output denormal bits by the output modifier, `DX10_CLAMP` by the clamp modifier, and the whole mode by the 16-bit interpolation instructions (see their entries). With IEEE=1 a signaling NaN operand returns that NaN quieted (the first operand checked first, min3/max3 as two nested pairs, med3 as min3 when any operand is NaN), measured on RDNA2 (gfx1035) over every combination of ±0, ±1, ±inf, denormals, quiet and signaling NaNs with f32 denormals kept and flushed. IEEE=0 and unknown modes keep the previous lowering, which matches IEEE=0. The f16 and f64 min/max/med3, NaN quieting in arithmetic, `FP16_OVFL` and `FLOAT_MODE` (f32 denormals flushed before the min/max compare in Astro's Playroom's 0xc0) still follow a single mode, as the entries below record. The mode comes from the registered shader header's `sh_registers`, the same source at registration and at draw/dispatch so the prepared artifact keys match. A title that rewrites RSRC1 at run time, or a raw compute program with no header, compiles with that mode or none. - [v_mullit_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) follows the hardware measured with the MODE register at IEEE=1 and denormals kept (a signaling NaN src0 is quieted, a positive denormal src2 counts as positive): the recompiler does not read MODE, so other IEEE/denormal settings are not modelled. - [v_fma_legacy_f32](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) (VOP3 0x140) is assumed to be fused, which has not been measured on hardware. LLVM decodes this encoding as `v_mad_legacy_f32` for gfx1013, and `v_mac_legacy_f32` (VOP2 0x06) was measured to round its product before the add, so 0x140 may do the same. - [v_fma_f16, v_pk_fma_f16 and v_pk_fmac_f16](../../core/shader/recompiler/Translation/src/FloatInstructions.cpp) are an f32 FMA rounded to f16, which rounds twice: 0x62e9 * 0xd159 + 0x23ec gives 0xf89e, the hardware 0xf89f. @@ -323,6 +323,7 @@ Throughout the project, every function at every stage either **does exactly what - [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) for a layer other than III, for a CRC-protected frame, or for a VBRI header outside MPEG-1 stereo: shadPS4 looks for the tag after the CRC and after the side information, while LAME and FFmpeg place it without the CRC and VBRI always at byte 36. - 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; 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. - [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.