From 703afd5b100cfab3ce2777ad39f675db3bd1eb6e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Thu, 1 Oct 2026 22:20:44 +0200 Subject: [PATCH] fix(shader): accept neg on VOP3-encoded float compares VOP3 compares have a scalar destination, so checkNativeVop3Modifiers sent them through the carry/scalar-destination branch, which rejects neg. abs already passed and both are applied to the sources by the VOPC path, so v_cmp_lt_f32_e64 vcc_lo, -v1, v2 threw "VOP3 source modifiers are not implemented". Float compares now accept neg there; integer ones still don't. Co-Authored-By: Claude --- .../tests/execution/Vop3CompareModifiers.cpp | 137 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 3 +- 2 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/Vop3CompareModifiers.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CompareModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CompareModifiers.cpp new file mode 100644 index 000000000..096295e0c --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/Vop3CompareModifiers.cpp @@ -0,0 +1,137 @@ +#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 = 4; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) constexpr std::array ModifierCode{ + 0x34020082, 0x34060082, 0xe0302000, 0x80000401, 0xe0302004, 0x80000501, 0xe0302008, 0x80000e01, + 0xe030200c, 0x80000f01, 0x7e140280, 0xbf8c3f70, 0xd401006a, 0x20020b04, 0xd5010014, 0x01a90280, + 0xd76f000a, 0x04290114, 0xd404016a, 0x60020b04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290314, + 0xd409026a, 0x40020b04, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290514, 0xd4020014, 0x20020b04, + 0xd5010014, 0x00510280, 0xd76f000a, 0x04290714, 0xd4ca006a, 0x20021f0e, 0xd5010014, 0x01a90280, + 0xd76f000a, 0x04290914, 0xd4c9016a, 0x60021f0e, 0xd5010014, 0x01a90280, 0xd76f000a, 0x04290b14, + 0xd4ed0014, 0x40021f0e, 0xd5010014, 0x00510280, 0xd76f000a, 0x04290d14, 0xbe9e037e, 0x7e280280, + 0xd413007e, 0x20020b04, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04290f14, 0xbe9e037e, 0x7e280280, + 0xd4dc027e, 0x20021f0e, 0x7e280281, 0xbefe031e, 0xd76f000a, 0x04291114, 0xe0702000, 0x80010a03, + 0xbf810000, +}; + +constexpr std::array FloatEdges{ + 0x7fc00000u, 0x3f800000u, 0x80000000u, 0x00000000u, 0x7f800000u, 0xff800000u, + 0xbf800000u, 0x3f800000u, 0x00800000u, 0x40490fdbu, 0xffc00001u, 0xc0000000u, +}; + +constexpr std::array HalfEdges{ + 0x7e00u, 0x3c00u, 0x8000u, 0x0000u, 0x7c00u, 0xfc00u, 0xbc00u, 0x3c00u, 0x0001u, 0x4248u, 0xfe01u, 0xc000u, +}; + +float HalfValue(std::uint32_t bits) { + const auto exponent = (bits >> 10u) & 0x1fu; + const auto fraction = bits & 0x3ffu; + float magnitude = 0.0f; + if (exponent == 0x1fu) { + magnitude = fraction != 0u ? std::numeric_limits::quiet_NaN() : std::numeric_limits::infinity(); + } else { + magnitude = exponent == 0u ? std::ldexp(static_cast(fraction), -24) : std::ldexp(static_cast(fraction | 0x400u), static_cast(exponent) - 25); + } + return (bits & 0x8000u) != 0u ? -magnitude : magnitude; +} + +void FillInput() { + for (std::uint32_t tid = 0; tid < Threads; ++tid) { + auto* words = &Input[tid * Inputs]; + words[0] = FloatEdges[tid % FloatEdges.size()]; + words[1] = FloatEdges[(tid / FloatEdges.size() + tid * 7u) % FloatEdges.size()] ^ ((tid & 4u) != 0u ? 0x80000000u : 0u); + words[2] = 0xabcd0000u | HalfEdges[tid % HalfEdges.size()]; + words[3] = 0x12340000u | (HalfEdges[(tid / HalfEdges.size() + tid * 5u) % HalfEdges.size()] ^ ((tid & 8u) != 0u ? 0x8000u : 0u)); + } +} + +std::array BufferDescriptor(const void* data, std::uint32_t count) { + const auto address = reinterpret_cast(data); + return {static_cast(address), static_cast((address >> 32u) & 0xffffu) | (4u << 16u), count, 0x01016facu}; +} + +void Run(AgcDriver::VulkanDevice& device) { + Output.fill(0xdeadbeefu); + std::vector userData(8, 0u); + const auto input = BufferDescriptor(Input.data(), static_cast(Input.size())); + const auto output = BufferDescriptor(Output.data(), static_cast(Output.size())); + std::copy(input.begin(), input.end(), userData.begin()); + std::copy(output.begin(), output.end(), userData.begin() + 4); + const std::span code(ModifierCode); + const std::array memory{{{reinterpret_cast(code.data()), std::as_bytes(code)}}}; + const ShaderRecompiler::ShaderComputeStageInfo compute{{Threads, 1, 1}, 0, {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 auto* in = &Input[tid * Inputs]; + const float fa = std::bit_cast(in[0]); + const float fb = std::bit_cast(in[1]); + const float ha = HalfValue(in[2] & 0xffffu); + const float hb = HalfValue(in[3] & 0xffffu); + const std::array expected{ + -fa < fb, + -std::fabs(fa) > -fb, + !(fa >= -std::fabs(fb)), + -fa == fb, + -ha == hb, + -std::fabs(ha) < -hb, + !(ha == -hb), + -fa <= fb, + -ha > std::fabs(hb), + }; + for (std::uint32_t k = 0; k < expected.size(); ++k) { + const bool actual = ((Output[tid * Results] >> k) & 1u) != 0u; + Require(actual == expected[k], "vop3 compare modifiers: thread " + std::to_string(tid) + " compare " + std::to_string(k) + " is " + std::to_string(actual) + ", expected " + std::to_string(expected[k])); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + FillInput(); + Run(*device); + Check(); + std::puts("vop3 compare modifier tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index a4d6ec2bc..ffc164f2c 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -1424,7 +1424,8 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, return; } if (carryInOut || scalarDst) { - if (clamp != 0u || omod != 0u || neg != 0u) { + const bool floatCompare = scalarDst && isVopcFloatCompareOpcode(opcode); + if (clamp != 0u || omod != 0u || (neg != 0u && !floatCompare)) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } return;