From fe1f7b38afa50cb74d9822b6470153cf8973f39f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Thu, 8 Oct 2026 22:39:17 +0200 Subject: [PATCH] feat(shader): follow IEEE_MODE in f32 min, max, min3, max3 and med3 Co-Authored-By: Claude --- .../tests/execution/F32MinMaxNan.cpp | 145 +++++++++++++++++- core/shader/recompiler/Recompiler.cpp | 1 + .../Translation/InstructionTranslator.hpp | 2 + .../Translation/TranslationContext.hpp | 4 + .../Translation/src/FloatInstructions.cpp | 32 ++++ .../Translation/src/InstructionTranslator.cpp | 1 + .../Translation/src/VectorInstructions.cpp | 10 +- docs/dev/TechnicalDebt.md | 2 +- 8 files changed, 186 insertions(+), 11 deletions(-) diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp index 4592b4920..d427b01f0 100644 --- a/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp +++ b/core/libs/prx/libSceAgcDriver/tests/execution/F32MinMaxNan.cpp @@ -289,8 +289,139 @@ constexpr std::uint32_t Expected[128][7] = { {0x00000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u} }; +constexpr std::uint32_t ExpectedIeee[128][7] = { + {0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0xc0400000u, 0x00000000u, 0xc0400000u, 0x3f800000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0x00000000u, 0x7f800000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x7f800000u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xffc12345u, 0xffc12345u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x3f800000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x80000000u, 0x3f800000u}, + {0xc0400000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0x80000000u}, + {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x80000000u, 0x7f800000u}, + {0xff800000u, 0x80000000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xff800000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x80000000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0x80000000u}, + {0x7fc00001u, 0x7fc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x80000000u, 0x3f800000u, 0x80000000u, 0x7f800000u, 0x3f800000u, 0x80000000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0x3f800000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0xc0400000u, 0x3f800000u}, + {0x3f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u}, + {0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x3f800000u, 0xff800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x3f800000u, 0x3f800000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0x7fc00001u, 0x7fc00001u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u, 0xffc12345u}, + {0xc0400000u, 0x00000000u, 0xc0400000u, 0x00000000u, 0x00000000u, 0xc0400000u, 0x00000000u}, + {0xc0400000u, 0x80000000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0x80000000u}, + {0xc0400000u, 0x3f800000u, 0xc0400000u, 0x7f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x80000000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0x7f800000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0xc0400000u, 0x7f800000u}, + {0xff800000u, 0xc0400000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0xc0400000u, 0xc0400000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xc0400000u, 0xc0400000u}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xffc12345u, 0xffc12345u}, + {0x00000000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x7f800000u}, + {0x80000000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x7f800000u}, + {0x3f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x3f800000u, 0x3f800000u, 0x7f800000u}, + {0xc0400000u, 0x7f800000u, 0xc0400000u, 0x7f800000u, 0x7f800000u, 0xc0400000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x80000000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0x7f800000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0xff800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u}, + {0x7f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x3f800000u, 0x7f800000u, 0x7f800000u}, + {0x7fc00001u, 0x7fc00001u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0xff800000u, 0x00000000u, 0xff800000u, 0x00000000u, 0xff800000u, 0xff800000u, 0x00000000u}, + {0xff800000u, 0x80000000u, 0xff800000u, 0x80000000u, 0xc0400000u, 0xff800000u, 0x80000000u}, + {0xff800000u, 0x3f800000u, 0xff800000u, 0x3f800000u, 0x00000000u, 0xff800000u, 0x3f800000u}, + {0xff800000u, 0xc0400000u, 0xff800000u, 0xc0400000u, 0xff800000u, 0xff800000u, 0xc0400000u}, + {0xff800000u, 0x7f800000u, 0xff800000u, 0x7f800000u, 0x7f800000u, 0xff800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x80000000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0xff800000u, 0xff800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x7fc00001u, 0x7fc00001u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0x00000000u, 0x00000000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0xc0400000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0x00000000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0x7f800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0x7fc12345u, 0x7fc12345u, 0x80000000u, 0x80000000u, 0x80000000u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc12345u, 0x7fc12345u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc12345u, 0x7fc12345u}, + {0x7fc00001u, 0x7fc00001u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xffc12345u, 0xffc12345u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x80000000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x80000000u, 0x80000000u}, + {0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x3f800000u}, + {0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xc0400000u}, + {0x7f800000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x7f800000u, 0x7f800000u}, + {0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u, 0xff800000u}, + {0xffd54321u, 0xffd54321u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xffd54321u, 0xffd54321u}, + {0xffd54321u, 0xffd54321u, 0x80000000u, 0x80000000u, 0x80000000u, 0xffd54321u, 0xffd54321u}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0xff800000u, 0xff800000u, 0xff800000u, 0xffc12345u, 0xffc12345u}, + {0x7fc00001u, 0x7fc00001u, 0xff800000u, 0xff800000u, 0xff800000u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x80000000u, 0x80000000u, 0x80000000u, 0x7fc00001u, 0x7fc00001u}, + {0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u}, + {0xffc12345u, 0xffc12345u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0xff800000u, 0xff800000u, 0xff800000u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0x3f800000u, 0x3f800000u, 0x3f800000u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0xc0400000u, 0xc0400000u, 0xc0400000u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0x7f800000u, 0x7f800000u, 0x7f800000u, 0xffc12345u, 0xffc12345u}, + {0xffc12345u, 0xffc12345u, 0x80000000u, 0x80000000u, 0x80000000u, 0xffc12345u, 0xffc12345u}, + {0x3f800000u, 0x40200000u, 0x00000000u, 0x40200000u, 0x3f800000u, 0x3f800000u, 0x40200000u}, + {0x00800000u, 0x3f800000u, 0x00800000u, 0x3f800000u, 0x3f800000u, 0x00800000u, 0x3f800000u}, + {0xc0400000u, 0xbf800000u, 0xc0400000u, 0x00000000u, 0xbf800000u, 0xc0400000u, 0xbf800000u}, + {0xc0400000u, 0xbf800000u, 0xc0400000u, 0x3f800000u, 0xbf800000u, 0xc0400000u, 0xbf800000u}, + {0x7fc00000u, 0x7fc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00000u, 0x7fc00000u}, + {0xffc00000u, 0xffc00000u, 0x00000000u, 0x00000000u, 0x00000000u, 0xffc00000u, 0xffc00000u}, + {0x7fc00001u, 0x7fc00001u, 0x00000000u, 0x00000000u, 0x00000000u, 0x7fc00001u, 0x7fc00001u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x7f800000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0x7fc00001u, 0x7fc00001u, 0x7fc00001u, 0x00000000u, 0x00000000u}, + {0x00000000u, 0x00000000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x00000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xbf800000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x7f800000u, 0x00000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xff800000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0x80000000u, 0x00000000u, 0x80000000u, 0x80000000u, 0x00000000u}, + {0x80000000u, 0x00000000u, 0xffc12345u, 0xffc12345u, 0xffc12345u, 0x80000000u, 0x00000000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x3f800000u, 0x80000000u, 0x3f800000u, 0x00000000u, 0x00000000u, 0x3f800000u}, + {0x00000000u, 0x3f800000u, 0x00000000u, 0x3f800000u, 0x3f800000u, 0x00000000u, 0x3f800000u}, +}; constexpr std::uint32_t Chunks = 4; std::uint32_t Chunk = 0; +bool Ieee = false; constexpr const char* Names[7] = { "v_min_f32 v10, v4, v5", "v_max_f32 v11, v4, v5", @@ -317,7 +448,7 @@ std::string Hex(std::uint32_t value) { } void Expect(std::uint32_t tid, std::uint32_t actual, std::uint32_t expected, const char* name) { - Require(actual == expected, std::string("f32 min max nan: row ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); + Require(actual == expected, std::string(Ieee ? "f32 min max nan, IEEE mode: row " : "f32 min max nan: row ") + std::to_string(tid) + " " + name + " is " + Hex(actual) + ", expected " + Hex(expected)); } void Run(AgcDriver::VulkanDevice& device) { @@ -338,6 +469,7 @@ void Run(AgcDriver::VulkanDevice& device) { {0, 0, 0, 128} }; request.useCache = false; + if (Ieee) request.context.floatMode = ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}; const auto result = ShaderRecompiler::Recompile(request); device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); device.WaitIdle(); @@ -348,7 +480,7 @@ void Check() { const std::uint32_t* in = &Input[tid * Inputs]; const std::uint32_t* out = &Output[tid * Results]; for (std::uint32_t i = 0; i < 7; ++i) { - Expect(Chunk * Threads + tid, out[i], Expected[Chunk * Threads + tid][i], Names[i]); + Expect(Chunk * Threads + tid, out[i], (Ieee ? ExpectedIeee : Expected)[Chunk * Threads + tid][i], Names[i]); } } } @@ -359,9 +491,12 @@ int main() { try { const auto device = OpenVulkanTestDevice(); if (!device) return VulkanTestSkipped; - for (Chunk = 0; Chunk < Chunks; ++Chunk) { - Run(*device); - Check(); + for (const bool ieee : {false, true}) { + Ieee = ieee; + for (Chunk = 0; Chunk < Chunks; ++Chunk) { + Run(*device); + Check(); + } } std::puts("f32 min max nan tests passed"); return 0; diff --git a/core/shader/recompiler/Recompiler.cpp b/core/shader/recompiler/Recompiler.cpp index 34432451b..358b31a24 100644 --- a/core/shader/recompiler/Recompiler.cpp +++ b/core/shader/recompiler/Recompiler.cpp @@ -122,6 +122,7 @@ IrProgram PrepareResourceProgram(const RecompileRequest& request) { translateOptions.userDataCount = static_cast(request.context.userData.size()); translateOptions.scratchDwords = request.context.compute.has_value() ? request.context.compute->scratchDwords : 0u; translateOptions.fragmentShaderBarycentricEnabled = request.target.fragmentShaderBarycentricEnabled; + translateOptions.floatMode = request.context.floatMode; translateOptions.inputInfo = inputInfo; constexpr InstructionTranslator translator; diff --git a/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp b/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp index 481f5b83a..3085542e0 100644 --- a/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp +++ b/core/shader/recompiler/Translation/include/Translation/InstructionTranslator.hpp @@ -8,6 +8,7 @@ #include "Optimization/ShaderStageInputInfo.hpp" #include "Translation/EmbeddedVertexFetch.hpp" #include +#include namespace ShaderRecompiler { @@ -31,6 +32,7 @@ struct TranslateOptions { std::uint32_t scratchDwords = 0; std::uint64_t shaderHash = 0; bool fragmentShaderBarycentricEnabled = false; + std::optional floatMode; ShaderStageInputInfo inputInfo; const EmbeddedFetchPlan* embeddedFetch = nullptr; }; diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index c83946fa1..5e03d7c6e 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -15,6 +15,7 @@ public: void TranslateEmbeddedFetch(const RdnaInstruction& instruction, std::uint32_t attribute, std::uint32_t components); void TranslateInstruction(const RdnaInstruction& instruction); void SetPixelInput(const ShaderPixelInputInfo* info, bool barycentricEnabled) { pixelInput = info; fragmentShaderBarycentricEnabled = barycentricEnabled; } + void SetFloatMode(const std::optional& mode) { floatMode = mode; ieeeMode = mode.has_value() && mode->ieeeMode; } void AddBranchCondition(const BasicBlock& source, BlockInfo& info); void TranslateCodeTableLoad(const RdnaInstruction& instruction, const ControlFlowGraph::CodeTableLoad& table); @@ -168,6 +169,7 @@ private: bool floatUnary(const RdnaInstruction& inst, IrOpcode opcode); bool floatBinary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse); bool floatTernary(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool mix); + bool ieeeMinMaxF32(const RdnaInstruction& inst, IrOpcode opcode); bool vDivScaleF32(const RdnaInstruction& inst); bool vDivFmasF32(const RdnaInstruction& inst); bool vDivFixupF32(const RdnaInstruction& inst); @@ -305,6 +307,8 @@ private: IrProgram& program; const ShaderPixelInputInfo* pixelInput = nullptr; bool fragmentShaderBarycentricEnabled = false; + std::optional floatMode; + bool ieeeMode = false; IrBuilder ir; IrBlock& block; IrU1 instructionBranchCondition; diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 7893e151c..f2d61789c 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -490,6 +490,38 @@ bool TranslationContext::floatTernary(const RdnaInstruction& inst, IrOpcode opco return true; } +bool TranslationContext::ieeeMinMaxF32(const RdnaInstruction& inst, IrOpcode opcode) { + if (inst.destination.omod != 0u) throw std::runtime_error("IEEE-mode f32 min/max/med3 with an output modifier is not implemented"); + const bool ternary = opcode == IrOpcode::FPMinTri32 || opcode == IrOpcode::FPMaxTri32 || opcode == IrOpcode::FPMedTri32; + std::array args{}; + for (std::uint32_t index = 0u; index < (ternary ? 3u : 2u); ++index) args[index] = readOperand(sourceAt(inst, index), IrType::F32); + const auto signaling = [&](IrValue* value) { + IrValue& bits = ir.BitCastU32(*value); + return &ir.LogicalAnd(ir.IEqual(ir.BitwiseAnd(bits, ir.Constant(0x7fc00000u)), ir.Constant(0x7f800000u)), ir.INotEqual(ir.BitwiseAnd(bits, ir.Constant(0x003fffffu)), ir.Constant(0u))); + }; + const auto quiet = [&](IrValue* value) { return &ir.BitCastF32(ir.BitwiseOr(ir.BitCastU32(*value), ir.Constant(0x00400000u))); }; + const auto pair = [&](IrOpcode pairOpcode, IrValue* lhs, IrValue* rhs) { + IrValue& plain = ir.Emit(pairOpcode, IrType::F32, {lhs, rhs}); + IrValue& rhsChecked = ir.Emit(IrOpcode::SelectF32, IrType::F32, {signaling(rhs), quiet(rhs), &plain}); + return &ir.Emit(IrOpcode::SelectF32, IrType::F32, {signaling(lhs), quiet(lhs), &rhsChecked}); + }; + IrValue* result = nullptr; + if (opcode == IrOpcode::FPMin32 || opcode == IrOpcode::FPMax32) { + result = pair(opcode, args[0], args[1]); + } else if (opcode == IrOpcode::FPMinTri32 || opcode == IrOpcode::FPMaxTri32) { + const auto pairOpcode = opcode == IrOpcode::FPMinTri32 ? IrOpcode::FPMin32 : IrOpcode::FPMax32; + result = pair(pairOpcode, pair(pairOpcode, args[0], args[1]), args[2]); + } else { + const auto isNan = [&](IrValue* value) { return &ir.Emit(IrOpcode::FPIsNan32, IrType::U1, {value}); }; + IrValue& anyNan = ir.LogicalOr(ir.LogicalOr(*isNan(args[0]), *isNan(args[1])), *isNan(args[2])); + IrValue* minimum = pair(IrOpcode::FPMin32, pair(IrOpcode::FPMin32, args[0], args[1]), args[2]); + IrValue& median = ir.Emit(IrOpcode::FPMedTri32, IrType::F32, {args[0], args[1], args[2]}); + result = &ir.Emit(IrOpcode::SelectF32, IrType::F32, {&anyNan, minimum, &median}); + } + writeOperand(inst.destination, result); + return true; +} + bool TranslationContext::vFmaLegacyF32(const RdnaInstruction& inst) { IrValue* lhs = readOperand(sourceAt(inst, 0u), IrType::F32); IrValue* rhs = readOperand(sourceAt(inst, 1u), IrType::F32); diff --git a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp index 8a478453c..2f5a7d6c5 100644 --- a/core/shader/recompiler/Translation/src/InstructionTranslator.cpp +++ b/core/shader/recompiler/Translation/src/InstructionTranslator.cpp @@ -433,6 +433,7 @@ IrProgram InstructionTranslator::Translate(const RdnaProgram& decoded, const Con const auto typedIndex = blockIndices.at(cfgBlock.id); TranslationContext context(program, *blocks[typedIndex], vectorLimit); context.SetPixelInput(options.inputInfo.pixel, options.fragmentShaderBarycentricEnabled); + context.SetFloatMode(options.floatMode); for (std::uint32_t index = cfgBlock.instructionBegin; index < cfgBlock.instructionEnd; index++) { const auto& instruction = decoded.instructions[index]; if (isCodeTableLoad(cfg, instruction.programCounter)) { diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index 1a60e2525..7d232075b 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -933,9 +933,9 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMulF32: return floatBinary(inst, IrOpcode::FPMul32, false); case RdnaOpcode::VMinF32: - return floatBinary(inst, IrOpcode::FPMin32, false); + return ieeeMode ? ieeeMinMaxF32(inst, IrOpcode::FPMin32) : floatBinary(inst, IrOpcode::FPMin32, false); case RdnaOpcode::VMaxF32: - return floatBinary(inst, IrOpcode::FPMax32, false); + return ieeeMode ? ieeeMinMaxF32(inst, IrOpcode::FPMax32) : floatBinary(inst, IrOpcode::FPMax32, false); case RdnaOpcode::VDivScaleF32: return vDivScaleF32(inst); case RdnaOpcode::VDivFmasF32: @@ -960,11 +960,11 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { case RdnaOpcode::VMadLegacyF32: return vFmaLegacyF32(inst); case RdnaOpcode::VMin3F32: - return floatTernary(inst, IrOpcode::FPMinTri32, false, false); + return ieeeMode ? ieeeMinMaxF32(inst, IrOpcode::FPMinTri32) : floatTernary(inst, IrOpcode::FPMinTri32, false, false); case RdnaOpcode::VMax3F32: - return floatTernary(inst, IrOpcode::FPMaxTri32, false, false); + return ieeeMode ? ieeeMinMaxF32(inst, IrOpcode::FPMaxTri32) : floatTernary(inst, IrOpcode::FPMaxTri32, false, false); case RdnaOpcode::VMed3F32: - return floatTernary(inst, IrOpcode::FPMedTri32, false, false); + return ieeeMode ? ieeeMinMaxF32(inst, IrOpcode::FPMedTri32) : floatTernary(inst, IrOpcode::FPMedTri32, false, false); case RdnaOpcode::VDot2cF32F16: return vDot2cF32F16(inst); case RdnaOpcode::VDot2F32F16: diff --git a/docs/dev/TechnicalDebt.md b/docs/dev/TechnicalDebt.md index 749d0e100..acdcbf009 100644 --- a/docs/dev/TechnicalDebt.md +++ b/docs/dev/TechnicalDebt.md @@ -284,7 +284,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, but no translation reads it yet (#981). Every float semantic still follows the single mode it was measured or written in, 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). Only `IEEE_MODE` is read so far, by `v_min_f32`, `v_max_f32`, `v_min3_f32`, `v_max3_f32` and `v_med3_f32`. 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. IEEE=1 with an output modifier throws. 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. - [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. - The executable file that [relinker](../../core/relinker/elfpatcher/src/windows/WindowsPeWriter.cpp) generates opens the console when launched, which is inconvenient for playability.