From c23ead647883aa8d5fc7a16cc50d4aedef98c46f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adri=C3=A0=20Franch?= Date: Sat, 3 Oct 2026 09:19:12 +0200 Subject: [PATCH] feat(shader): implement v_div_fixup_f16 Decode VOP3 opcode 0x35f and translate it on the raw half bits, matching the RDNA2 hardware: NaN numerator or denominator is returned quieted, 0/0 and inf/inf give 0xfe00, a NaN quotient becomes a signed infinity, and op_sel, abs and neg apply as for the other f16 ternary ops. Co-Authored-By: Claude --- .../tests/execution/DivFixupF16.cpp | 169 ++++++++++++++++++ .../include/RdnaDecoder/RdnaOpcode.hpp | 1 + .../recompiler/RdnaDecoder/src/RdnaOpcode.cpp | 1 + .../RdnaDecoder/src/RdnaVectorOpDecoder.cpp | 2 + .../Translation/TranslationContext.hpp | 1 + .../Translation/src/FloatInstructions.cpp | 32 ++++ .../Translation/src/VectorInstructions.cpp | 2 + 7 files changed, 208 insertions(+) create mode 100644 core/libs/prx/libSceAgcDriver/tests/execution/DivFixupF16.cpp diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/DivFixupF16.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/DivFixupF16.cpp new file mode 100644 index 000000000..175c75e45 --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/DivFixupF16.cpp @@ -0,0 +1,169 @@ +#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, 0x7e140307, 0xd75f000a, 0x041a0b04, + 0x7e160307, 0xd75f380b, 0x041a0b04, 0x7e180307, 0xd75f400c, 0x041a0b04, 0x7e1a0307, 0xd75f060d, + 0xa41a0b04, 0x7e1c0307, 0xd75f280e, 0x04160d04, 0x7e1e0307, 0xd75f100f, 0x44160906, 0x7e200307, + 0xd75f3010, 0x04120af2, 0x7e220307, 0xd75f0011, 0x0419e705, 0xe0701000, 0x80010a03, 0xe0701004, + 0x80010b03, 0xe0701008, 0x80010c03, 0xe070100c, 0x80010d03, 0xe0701010, 0x80010e03, 0xe0701014, + 0x80010f03, 0xe0701018, 0x80011003, 0xe070101c, 0x80011103, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0xaa591773u, 0x0912d5a5u, 0x04005e85u, 0x1b9c7ec8u}, + {0x482c74f4u, 0x71a1f91au, 0xd69e0400u, 0x0cacc82cu}, + {0x7c018000u, 0x90086eaeu, 0x00007bffu, 0xce9da0ddu}, + {0x8000fc00u, 0x7c0183ffu, 0x7c010001u, 0x5a145979u}, + {0xba5f5640u, 0x4fc2fc00u, 0x4d3d82bcu, 0x9ffb2a93u}, + {0x7b327e00u, 0x0c5c7e00u, 0x00008000u, 0x4449e54eu}, + {0x7c001694u, 0x7c000000u, 0xa8617c00u, 0x3ebf7466u}, + {0x000149ddu, 0x80007e00u, 0xc0006dd1u, 0xea791f92u}, + {0x00015b7bu, 0x66bd3966u, 0x7e008000u, 0x0beb2994u}, + {0xe5c1fc00u, 0xc0003c00u, 0x56400001u, 0x8b2852b0u}, + {0xd82c3263u, 0x94600000u, 0x3c000000u, 0x384242e0u}, + {0x5b660400u, 0x5c84dea4u, 0x8000d995u, 0xea68a771u}, + {0x7c000001u, 0x48979303u, 0x00000979u, 0xbbf3011du}, + {0x0001f0fbu, 0x25117bffu, 0x329dad28u, 0x2a5a6250u}, + {0x2b697c01u, 0x463f9f63u, 0x80008000u, 0x5c0b4c22u}, + {0x83ff7bffu, 0xc000c000u, 0xfc007e00u, 0x95e109b3u}, + {0x3c000ecau, 0xc000f24eu, 0xc9823c00u, 0xa2d1d5ffu}, + {0x3c008000u, 0x7c007c01u, 0x7bff7c00u, 0x02165d7fu}, + {0x7bff3c00u, 0xec467c00u, 0x00009243u, 0x1424a54fu}, + {0xc0007bffu, 0x7e0096a1u, 0x0400fc00u, 0x48ce86a2u}, + {0x56407bffu, 0x9bbd7de0u, 0x0400c000u, 0xb7fc780fu}, + {0x56403956u, 0x1deca74eu, 0xfe558000u, 0xdd3cfee3u}, + {0x3c005cb0u, 0xd9133f9du, 0x24353c00u, 0x4aca4572u}, + {0xf932c000u, 0x7e008000u, 0x0d2bde75u, 0x0a2697e0u}, + {0x0a72fc00u, 0x41aad71fu, 0xa7bd0000u, 0xc33a46f2u}, + {0x67d70400u, 0x7c017c01u, 0x86ec7c01u, 0xd107ab16u}, + {0x40aaa7dfu, 0xd6653312u, 0xc0000400u, 0xabd58d2fu}, + {0xc60483ffu, 0xe2bec000u, 0x879b0400u, 0x1dc7aeecu}, + {0xc00083ffu, 0xea3bcb39u, 0x7e003c00u, 0x275116b8u}, + {0x005b7ebau, 0x5335775eu, 0xbe8b7c00u, 0x99f7867fu}, + {0x0001c000u, 0xfe55e3cdu, 0x0000fc00u, 0xbb3d5c51u}, + {0x55f94db8u, 0x38bd1e8eu, 0x0001fc00u, 0x7e4c716cu} +}; +constexpr std::uint32_t Expected[32][8] = { + {0x1b9c9773u, 0x1b9c2a59u, 0x97737ec8u, 0x1b9c9773u, 0x1b9c2a59u, 0x1b9cde85u, 0x1b9cbc00u, 0x1b9cd5a5u}, + {0x0cacf4f4u, 0x0cacc82cu, 0xf4f4c82cu, 0x0cacf4f4u, 0x0cac482cu, 0x0cac0400u, 0x0cac3c00u, 0x0cacf91au}, + {0xce9d0000u, 0xce9d8000u, 0x0000a0ddu, 0xce9d8000u, 0xce9dfc00u, 0xce9dfe01u, 0xce9d7e01u, 0xce9deeaeu}, + {0x5a14fc00u, 0x5a147e01u, 0xfc005979u, 0x5a14fc00u, 0x5a147e01u, 0x5a14fc00u, 0x5a147e01u, 0x5a1483ffu}, + {0x9ffb0000u, 0x9ffb3a5fu, 0x00002a93u, 0x9ffb8000u, 0x9ffbba5fu, 0x9ffbfc00u, 0x9ffbbc00u, 0x9ffb7c00u}, + {0x44497e00u, 0x44490000u, 0x7e00e54eu, 0x44497e00u, 0x4449fc00u, 0x44497e00u, 0x44493c00u, 0x44490000u}, + {0x3ebf7c00u, 0x3ebf8000u, 0x7c007466u, 0x3ebffc00u, 0x3ebffe00u, 0x3ebf8000u, 0x3ebffe00u, 0x3ebffc00u}, + {0xea797e00u, 0xea797c00u, 0x7e001f92u, 0xea797e00u, 0xea798000u, 0xea797e00u, 0xea79fc00u, 0xea79fc00u}, + {0x0beb8000u, 0x0beb7e00u, 0x80002994u, 0x0beb8000u, 0x0bebfc00u, 0x0beb8000u, 0x0beb3c00u, 0x0beb0000u}, + {0x8b287c00u, 0x8b28e5c1u, 0x7c0052b0u, 0x8b28fc00u, 0x8b28e5c1u, 0x8b280001u, 0x8b283c00u, 0x8b28bc00u}, + {0x3842fe00u, 0x3842d82cu, 0xfe0042e0u, 0x3842fe00u, 0x3842fc00u, 0x38420000u, 0x38423c00u, 0x38428000u}, + {0xea680400u, 0xea688000u, 0x0400a771u, 0xea688400u, 0xea68db66u, 0xea685995u, 0xea683c00u, 0xea685ea4u}, + {0xbbf38001u, 0xbbf30000u, 0x8001011du, 0xbbf38001u, 0xbbf37c00u, 0xbbf30000u, 0xbbf37c00u, 0xbbf39303u}, + {0x2a5af0fbu, 0x2a5a0001u, 0xf0fb6250u, 0x2a5af0fbu, 0x2a5a8001u, 0x2a5aad28u, 0x2a5a3c00u, 0x2a5a7bffu}, + {0x5c0b0000u, 0x5c0b8000u, 0x00004c22u, 0x5c0b8000u, 0x5c0bfc00u, 0x5c0b0000u, 0x5c0b3c00u, 0x5c0b0000u}, + {0x95e17e00u, 0x95e17c00u, 0x7e0009b3u, 0x95e1fe00u, 0x95e17e00u, 0x95e1fc00u, 0x95e13c00u, 0x95e17e00u}, + {0xa2d18ecau, 0xa2d13c00u, 0x8ecad5ffu, 0xa2d18ecau, 0xa2d1bc00u, 0xa2d13c00u, 0xa2d1bc00u, 0xa2d1f24eu}, + {0x02167e01u, 0x02160000u, 0x7e015d7fu, 0x02167e01u, 0x0216fe00u, 0x02167e01u, 0x02160000u, 0x0216fc00u}, + {0x14248000u, 0x14248000u, 0x8000a54fu, 0x14248000u, 0x14247bffu, 0x1424fc00u, 0x1424bc00u, 0x14247c00u}, + {0x48ce7c00u, 0x48ce7e00u, 0x7c0086a2u, 0x48cefc00u, 0x48ce7e00u, 0x48cefc00u, 0x48ce7e00u, 0x48ce7c00u}, + {0xb7fc7fe0u, 0xb7fcd640u, 0x7fe0780fu, 0xb7fc7fe0u, 0xb7fc5640u, 0xb7fc7fe0u, 0xb7fcbc00u, 0xb7fc7c00u}, + {0xdd3c0000u, 0xdd3cfe55u, 0x0000fee3u, 0xdd3c8000u, 0xdd3cfc00u, 0xdd3c0000u, 0xdd3c3c00u, 0xdd3c0000u}, + {0x4aca5cb0u, 0x4acabc00u, 0x5cb04572u, 0x4acadcb0u, 0x4acabc00u, 0x4acabc00u, 0x4acabc00u, 0x4acabf9du}, + {0x0a267c00u, 0x0a267e00u, 0x7c0097e0u, 0x0a26fc00u, 0x0a267e00u, 0x0a268000u, 0x0a267e00u, 0x0a260000u}, + {0xc33a8000u, 0xc33a8a72u, 0x800046f2u, 0xc33a8000u, 0xc33a7c00u, 0xc33a0000u, 0xc33a3c00u, 0xc33a8000u}, + {0xd1077e01u, 0xd1077e01u, 0x7e01ab16u, 0xd107fe01u, 0xd1077e01u, 0xd1077e01u, 0xd1077e01u, 0xd1077e01u}, + {0xabd527dfu, 0xabd540aau, 0x27df8d2fu, 0xabd5a7dfu, 0xabd5c0aau, 0xabd58400u, 0xabd5bc00u, 0xabd5b312u}, + {0x1dc783ffu, 0x1dc74604u, 0x83ffaeecu, 0x1dc783ffu, 0x1dc7c604u, 0x1dc78400u, 0x1dc73c00u, 0x1dc7c000u}, + {0x275183ffu, 0x27517e00u, 0x83ff16b8u, 0x275183ffu, 0x2751c000u, 0x2751bc00u, 0x27513c00u, 0x2751cb39u}, + {0x99f77c00u, 0x99f7805bu, 0x7c00867fu, 0x99f7fc00u, 0x99f70000u, 0x99f7fc00u, 0x99f73c00u, 0x99f7fc00u}, + {0xbb3d7c00u, 0xbb3dfe55u, 0x7c005c51u, 0xbb3dfc00u, 0xbb3dfe55u, 0xbb3d7c00u, 0xbb3dfe55u, 0xbb3d7c00u}, + {0x7e4cfc00u, 0x7e4c55f9u, 0xfc00716cu, 0x7e4cfc00u, 0x7e4c8000u, 0x7e4cfc00u, 0x7e4c3c00u, 0x7e4c7c00u} +}; +constexpr const char* Names[8] = {"lo", "hi sources", "hi dst", "neg abs", "mixed halves", "neg denominator", "literal quotient", "literal denominator"}; + +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("div fixup f16: 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 < 8; ++i) Expect(tid, out[i], Expected[tid][i], Names[i]); + } +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("div fixup f16 tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp index 648edcbaf..5da2481cd 100644 --- a/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp +++ b/core/shader/recompiler/RdnaDecoder/include/RdnaDecoder/RdnaOpcode.hpp @@ -341,6 +341,7 @@ enum class RdnaOpcode : std::uint16_t { VMed3F16, VMed3I16, VMed3U16, + VDivFixupF16, VAdd3U32, VLshlAddU32, VAddLshlU32, diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp index d8829f978..d4aa4a93d 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaOpcode.cpp @@ -291,6 +291,7 @@ bool IsVectorAluOpcode(RdnaOpcode opcode) { case RdnaOpcode::VMed3F16: case RdnaOpcode::VMed3I16: case RdnaOpcode::VMed3U16: + case RdnaOpcode::VDivFixupF16: case RdnaOpcode::VAdd3U32: case RdnaOpcode::VLshlAddU32: case RdnaOpcode::VAddLshlU32: diff --git a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp index 393595a51..0c0552e6f 100644 --- a/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp +++ b/core/shader/recompiler/RdnaDecoder/src/RdnaVectorOpDecoder.cpp @@ -405,6 +405,7 @@ constexpr VectorOpcodeInfo vop3Opcodes[] = { {0x357u, RdnaOpcode::VMed3F16}, {0x358u, RdnaOpcode::VMed3I16}, {0x359u, RdnaOpcode::VMed3U16}, + {0x35fu, RdnaOpcode::VDivFixupF16}, {0x15au, RdnaOpcode::VSadU8}, {0x15bu, RdnaOpcode::VSadHiU8}, {0x15cu, RdnaOpcode::VSadU16}, @@ -580,6 +581,7 @@ bool isPermlaneOpcode(RdnaOpcode opcode) { bool isNativeVop3F16TernaryOpcode(RdnaOpcode opcode) { return opcode == RdnaOpcode::VMin3F16 || opcode == RdnaOpcode::VMax3F16 || opcode == RdnaOpcode::VMed3F16 || + opcode == RdnaOpcode::VDivFixupF16 || opcode == RdnaOpcode::VFmaF16; } diff --git a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp index bfd8533cf..aaba92733 100644 --- a/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp +++ b/core/shader/recompiler/Translation/include/Translation/TranslationContext.hpp @@ -140,6 +140,7 @@ private: bool packedFloat16(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool quietSnan); bool float16Unary(const RdnaInstruction& inst, IrOpcode opcode, bool invalidNegative); bool float16Trig(const RdnaInstruction& inst, IrOpcode opcode); + bool vDivFixupF16(const RdnaInstruction& inst); bool float16Binary(const RdnaInstruction& inst, IrOpcode opcode, bool reverse); bool float16Ternary(const RdnaInstruction& inst, IrOpcode opcode, bool accumulator, bool mix); bool floatUnary(const RdnaInstruction& inst, IrOpcode opcode); diff --git a/core/shader/recompiler/Translation/src/FloatInstructions.cpp b/core/shader/recompiler/Translation/src/FloatInstructions.cpp index 64ca4ff4e..685a2d4eb 100644 --- a/core/shader/recompiler/Translation/src/FloatInstructions.cpp +++ b/core/shader/recompiler/Translation/src/FloatInstructions.cpp @@ -63,6 +63,38 @@ bool TranslationContext::float16Unary(const RdnaInstruction& inst, IrOpcode opco return true; } +bool TranslationContext::vDivFixupF16(const RdnaInstruction& inst) { + std::array bits{}; + std::array magnitude{}; + for (std::uint32_t index = 0u; index < bits.size(); ++index) { + const RdnaOperand& operand = sourceAt(inst, index); + const IrU32 source = readF16SourceBits(operand); + bits[index] = IrU32(ir.Emit(IrOpcode::BitFieldUExtract, IrType::U32, {&source.Value(), &ir.Constant(operand.opSel ? 16u : 0u), &ir.Constant(16u)})); + if (operand.absolute) { + bits[index] = IrU32(ir.BitwiseAnd(bits[index].Value(), ir.Constant(0x7fffu))); + } + if (operand.negate) { + bits[index] = IrU32(ir.BitwiseXor(bits[index].Value(), ir.Constant(0x8000u))); + } + magnitude[index] = IrU32(ir.BitwiseAnd(bits[index].Value(), ir.Constant(0x7fffu))); + } + const auto isNan = [&](std::uint32_t index) { return IrU1(ir.UGreaterThan(magnitude[index].Value(), ir.Constant(0x7c00u))); }; + const auto isInf = [&](std::uint32_t index) { return IrU1(ir.IEqual(magnitude[index].Value(), ir.Constant(0x7c00u))); }; + const auto isZero = [&](std::uint32_t index) { return IrU1(ir.IEqual(magnitude[index].Value(), ir.Constant(0u))); }; + const IrU32 sign(ir.BitwiseAnd(ir.BitwiseXor(bits[1].Value(), bits[2].Value()), ir.Constant(0x8000u))); + const IrU32 infinity(ir.BitwiseOr(sign.Value(), ir.Constant(0x7c00u))); + IrU32 result(ir.Select(isNan(0u).Value(), infinity.Value(), ir.BitwiseOr(sign.Value(), magnitude[0].Value()))); + result = IrU32(ir.Select(ir.LogicalOr(isInf(1u).Value(), isZero(2u).Value()), sign.Value(), result.Value())); + result = IrU32(ir.Select(ir.LogicalOr(isZero(1u).Value(), isInf(2u).Value()), infinity.Value(), result.Value())); + const IrU1 bothZero(ir.LogicalAnd(isZero(1u).Value(), isZero(2u).Value())); + const IrU1 bothInf(ir.LogicalAnd(isInf(1u).Value(), isInf(2u).Value())); + result = IrU32(ir.Select(ir.LogicalOr(bothZero.Value(), bothInf.Value()), ir.Constant(0xfe00u), result.Value())); + result = IrU32(ir.Select(isNan(1u).Value(), ir.BitwiseOr(bits[1].Value(), ir.Constant(0x0200u)), result.Value())); + result = IrU32(ir.Select(isNan(2u).Value(), ir.BitwiseOr(bits[2].Value(), ir.Constant(0x0200u)), result.Value())); + write16Bits(inst.destination, result); + return true; +} + bool TranslationContext::float16Trig(const RdnaInstruction& inst, IrOpcode opcode) { const IrF32 argument = readF16AsF32(sourceAt(inst, 0u)); const IrU32 magnitude(ir.BitwiseAnd(ir.BitCastU32(argument.Value()), ir.Constant(0x7fffffffu))); diff --git a/core/shader/recompiler/Translation/src/VectorInstructions.cpp b/core/shader/recompiler/Translation/src/VectorInstructions.cpp index d371a1111..4e4279cf0 100644 --- a/core/shader/recompiler/Translation/src/VectorInstructions.cpp +++ b/core/shader/recompiler/Translation/src/VectorInstructions.cpp @@ -689,6 +689,8 @@ bool TranslationContext::emitVector(const RdnaInstruction& inst) { return float16Ternary(inst, IrOpcode::FPMaxTri32, false, false); case RdnaOpcode::VMed3F16: return float16Ternary(inst, IrOpcode::FPMedTri32, false, false); + case RdnaOpcode::VDivFixupF16: + return vDivFixupF16(inst); case RdnaOpcode::VFrexpMantF32: return vFrexpMantF32(inst); case RdnaOpcode::VRcpF32: