From b00efdb261fae754e2fb18d0f2e541d1082bf98d Mon Sep 17 00:00:00 2001 From: Alex <270190108+CouchRiv@users.noreply.github.com> Date: Sun, 11 Oct 2026 11:45:26 -0400 Subject: [PATCH] fix(shader): accept abs, neg and op_sel source modifiers on v_cvt_pknorm_i16_f16 and v_cvt_pknorm_u16_f16 v_cvt_pknorm_i16_f16 and v_cvt_pknorm_u16_f16 with |x|, -x or op_sel in the VOP3 encoding stop the recompiler with "VOP3 source modifiers are not implemented", although the RDNA2 hardware executes them. These two opcodes now use the two-source f16 modifier path of v_pack_b32_f16: op_sel picks the high half of either source, then abs and neg apply. Other op_sel bits, clamp and omod are still refused. Matches 15632 of 15632 rows measured on gfx1036 (Radeon 610M) in two float modes, 0 differing. Co-Authored-By: Claude Haiku 5.5 --- .../VCvtPknormF16SourceModifiers.cpp | 174 ++++++++++++++++++ .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 12 +- 2 files changed, 182 insertions(+), 4 deletions(-) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/VCvtPknormF16SourceModifiers.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/VCvtPknormF16SourceModifiers.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/VCvtPknormF16SourceModifiers.cpp new file mode 100644 index 000000000..4e90961e5 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/VCvtPknormF16SourceModifiers.cpp @@ -0,0 +1,174 @@ +#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 + +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 = 2; +constexpr std::uint32_t RowCount = 8; +constexpr std::uint32_t VariantCount = 8; +alignas(256) std::array Input{}; +alignas(256) std::array Output{}; + +alignas(256) std::array Code{ + 0x34020084u, 0x34060083u, 0xe0381000u, 0x80000401u, 0xbf8c3f70u, 0xd712000au, 0x00020b04u, + 0xd713000bu, 0x00020b04u, 0xe0701000u, 0x80010a03u, 0xe0701004u, 0x80010b03u, 0xbf810000u, +}; + +struct Variant { + const char* operands; + std::uint32_t abs; + std::uint32_t neg; + std::uint32_t opSel; +}; + +constexpr Variant Variants[VariantCount] = { + {"|v4|, v5", 1, 0, 0}, + {"v4, |v5|", 2, 0, 0}, + {"-v4, v5", 0, 1, 0}, + {"v4, -v5", 0, 2, 0}, + {"-|v4|, v5", 1, 1, 0}, + {"v4, -|v5|", 2, 2, 0}, + {"v4, v5 op_sel:[1,0,0]", 0, 0, 1}, + {"v4, v5 op_sel:[0,1,0]", 0, 0, 2}, +}; + +constexpr std::uint32_t Rows[RowCount][2] = { + {0x00000400u, 0x00000400u}, + {0x000083ffu, 0x000083ffu}, + {0x00003555u, 0x0000d640u}, + {0x0000bc00u, 0x00003c00u}, + {0x00007bffu, 0x00000400u}, + {0xbeef0000u, 0xbeef3c00u}, + {0xbeef8000u, 0xbeef0000u}, + {0xbeef0000u, 0xbeeffc00u}, +}; + +constexpr std::uint32_t Expected[VariantCount][Results][RowCount] = { + { + {0x00020002u, 0xfffe0002u, 0x80012aa8u, 0x7fff7fffu, 0x00027fffu, 0x7fff0000u, 0x00000000u, 0x80010000u}, + {0x00040004u, 0x00000004u, 0x00005550u, 0xffffffffu, 0x0004ffffu, 0xffff0000u, 0x00000000u, 0x00000000u}, + }, + { + {0x00020002u, 0x0002fffeu, 0x7fff2aa8u, 0x7fff8001u, 0x00027fffu, 0x7fff0000u, 0x00000000u, 0x7fff0000u}, + {0x00040004u, 0x00040000u, 0xffff5550u, 0xffff0000u, 0x0004ffffu, 0xffff0000u, 0x00000000u, 0xffff0000u}, + }, + { + {0x0002fffeu, 0xfffe0002u, 0x8001d558u, 0x7fff7fffu, 0x00028001u, 0x7fff0000u, 0x00000000u, 0x80010000u}, + {0x00040000u, 0x00000004u, 0x00000000u, 0xffffffffu, 0x00040000u, 0xffff0000u, 0x00000000u, 0x00000000u}, + }, + { + {0xfffe0002u, 0x0002fffeu, 0x7fff2aa8u, 0x80018001u, 0xfffe7fffu, 0x80010000u, 0x00000000u, 0x7fff0000u}, + {0x00000004u, 0x00040000u, 0xffff5550u, 0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000000u, 0xffff0000u}, + }, + { + {0x0002fffeu, 0xfffefffeu, 0x8001d558u, 0x7fff8001u, 0x00028001u, 0x7fff0000u, 0x00000000u, 0x80010000u}, + {0x00040000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0x00040000u, 0xffff0000u, 0x00000000u, 0x00000000u}, + }, + { + {0xfffe0002u, 0xfffefffeu, 0x80012aa8u, 0x80018001u, 0xfffe7fffu, 0x80010000u, 0x00000000u, 0x80010000u}, + {0x00000004u, 0x00000000u, 0x00005550u, 0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000000u}, + }, + { + {0x00020000u, 0xfffe0000u, 0x80010000u, 0x7fff0000u, 0x00020000u, 0x7fff8001u, 0x00008001u, 0x80018001u}, + {0x00040000u, 0x00000000u, 0x00000000u, 0xffff0000u, 0x00040000u, 0xffff0000u, 0x00000000u, 0x00000000u}, + }, + { + {0x00000002u, 0x0000fffeu, 0x00002aa8u, 0x00008001u, 0x00007fffu, 0x80010000u, 0x80010000u, 0x80010000u}, + {0x00000004u, 0x00000000u, 0x00005550u, 0x00000000u, 0x0000ffffu, 0x00000000u, 0x00000000u, 0x00000000u}, + }, +}; + +constexpr const char* Opcodes[Results] = {"v_cvt_pknorm_i16_f16_e64 v10, ", "v_cvt_pknorm_u16_f16_e64 v11, "}; + +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 Run(AgcDriver::VulkanDevice& device, ShaderRecompiler::ShaderFloatMode mode, const Variant& variant) { + const std::uint32_t first = 0xd7120000u | variant.opSel << 11u | variant.abs << 8u | 10u; + const std::uint32_t second = 0xd7130000u | variant.opSel << 11u | variant.abs << 8u | 11u; + const std::uint32_t sources = 0x00020b04u | variant.neg << 29u; + Code[5] = first; + Code[6] = sources; + Code[7] = second; + Code[8] = sources; + for (std::uint32_t index = 0; index < RowCount; ++index) { + std::copy(std::begin(Rows[index]), std::end(Rows[index]), &Input[index * Inputs]); + } + for (std::uint32_t index = RowCount * Inputs; index < Input.size(); ++index) Input[index] = 0u; + 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 = mode; + const auto result = ShaderRecompiler::Recompile(request); + device.Dispatch(result, 1, 1, 1, {}, reinterpret_cast(code.data())); + device.WaitIdle(); +} + +void Check(const char* modeName, std::uint32_t variantIndex) { + for (std::uint32_t slot = 0; slot < Results; ++slot) { + for (std::uint32_t index = 0; index < RowCount; ++index) { + const std::uint32_t actual = Output[index * Results + slot]; + const std::uint32_t expected = Expected[variantIndex][slot][index]; + Require(actual == expected, + std::string("v_cvt_pknorm f16 source modifiers: ") + modeName + " row " + std::to_string(index) + " " + + Opcodes[slot] + Variants[variantIndex].operands + " of " + Hex(Rows[index][0]) + " and " + + Hex(Rows[index][1]) + " is " + Hex(actual) + ", expected " + Hex(expected)); + } + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + for (std::uint32_t index = 0; index < VariantCount; ++index) { + Run(*device, ShaderRecompiler::ShaderFloatMode{0xf0u, true, true, false}, Variants[index]); + Check("FLOAT_MODE 0xf0", index); + Run(*device, ShaderRecompiler::ShaderFloatMode{0xc0u, true, false, false}, Variants[index]); + Check("FLOAT_MODE 0xc0", index); + } + std::puts("v_cvt_pknorm f16 source modifiers 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 0e6078d53..d1749f283 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -738,6 +738,10 @@ bool isNativeVop3F16TernaryOpcode(RdnaOpcode opcode) { opcode == RdnaOpcode::VFmaF16; } +bool isNativeVop3F16PackOpcode(RdnaOpcode opcode) { + return opcode == RdnaOpcode::VPackB32F16 || opcode == RdnaOpcode::VCvtPknormI16F16 || opcode == RdnaOpcode::VCvtPknormU16F16; +} + bool isNativeVop3I16TernaryOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VMed3I16 || opcode == RdnaOpcode::VMed3U16 || opcode == RdnaOpcode::VMax3I16 || opcode == RdnaOpcode::VMax3U16 || opcode == RdnaOpcode::VMin3I16 || opcode == RdnaOpcode::VMin3U16 || @@ -1862,7 +1866,7 @@ void applyNativeVop3B16BinaryModifiers(RdnaInstruction& instruction, std::uint32 instruction.destination.sdwaSel = (opSel & 0x8u) != 0u ? 5u : 4u; } -void applyNativeVop3PackB32F16Modifiers(RdnaInstruction& instruction, std::uint32_t opSel, std::uint32_t abs, std::uint32_t neg) { +void applyNativeVop3F16PackModifiers(RdnaInstruction& instruction, std::uint32_t opSel, std::uint32_t abs, std::uint32_t neg) { instruction.source0.opSel = (opSel & 0x1u) != 0u; instruction.source1.opSel = (opSel & 0x2u) != 0u; instruction.source0.negate = (neg & 0x1u) != 0u; @@ -2051,7 +2055,7 @@ void checkNativeVop3Modifiers(RdnaOpcode opcode, bool permlane, bool carryInOut, } return; } - if (opcode == RdnaOpcode::VPackB32F16) { + if (isNativeVop3F16PackOpcode(opcode)) { if ((abs & ~0x3u) != 0u || (opSel & ~0x3u) != 0u || clamp != 0u || omod != 0u || (neg & ~0x3u) != 0u) { throw std::invalid_argument("VOP3 source modifiers are not implemented"); } @@ -2383,8 +2387,8 @@ RdnaInstruction DecodeRdnaVop3(std::uint32_t programCounter, std::span