diff --git a/core/libs/prx/libSceAgcDriver/tests/execution/PackedIntegerNegate.cpp b/core/libs/prx/libSceAgcDriver/tests/execution/PackedIntegerNegate.cpp new file mode 100644 index 000000000..b170bc08d --- /dev/null +++ b/core/libs/prx/libSceAgcDriver/tests/execution/PackedIntegerNegate.cpp @@ -0,0 +1,211 @@ +#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 + +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, 0xcc07410a, 0x300208ff, 0x00007fff, + 0xcc0a410b, 0x58020b04, 0xcc03420c, 0x38020b04, 0xcc08430d, 0x78020b04, 0xcc0c420e, 0x58020b04, + 0xcc0d410f, 0x38020b04, 0xcc014210, 0x38020b04, 0xcc004511, 0x9c1a0b04, 0xcc094612, 0x7c1a0b04, + 0xcc044213, 0x58020b04, 0xcc064314, 0x58020b04, 0xcc054215, 0x78020b04, 0xcc024a16, 0x30020b04, + 0xcc0b5317, 0x68020b04, 0xcc02c218, 0x58020b04, 0xcc074919, 0x30020906, 0xe0781000, 0x80010a03, + 0xe0781010, 0x80010e03, 0xe0781020, 0x80011203, 0xe0781030, 0x80011603, 0xbf810000, +}; + +constexpr std::uint32_t Rows[32][4] = { + {0x1234021bu, 0xffff7fffu, 0x8001edccu, 0x00000000u}, + {0xc000021bu, 0x0002021bu, 0x000fedccu, 0x00000000u}, + {0xedcc0001u, 0x00ff0218u, 0x0002fffeu, 0x00000000u}, + {0x000f00ffu, 0x000f0218u, 0x80100218u, 0x00000000u}, + {0x000f8000u, 0x000f021bu, 0x00ff021bu, 0x00000000u}, + {0x7fff0218u, 0x4000000fu, 0x80018000u, 0x00000000u}, + {0xffff4000u, 0x00ff8000u, 0x00021234u, 0x00000000u}, + {0x7ffeffffu, 0x021bffffu, 0x8010c000u, 0x00000000u}, + {0x80107fffu, 0x80010010u, 0xedcc8001u, 0x00000000u}, + {0xc0000000u, 0x80008000u, 0x0218021bu, 0x00000000u}, + {0x00020001u, 0x00027fffu, 0x40000218u, 0x00000000u}, + {0x00ffc000u, 0xffff0002u, 0xfffefffeu, 0x00000000u}, + {0x00100000u, 0x00ff000fu, 0xfffe4000u, 0x00000000u}, + {0x021bfffeu, 0x00ff8001u, 0x00fffffeu, 0x00000000u}, + {0xedccedccu, 0x021b021bu, 0x7ffe8001u, 0x00000000u}, + {0x00007ffeu, 0x12340218u, 0xfffe0001u, 0x00000000u}, + {0xc0000218u, 0x0010ffffu, 0xffff8001u, 0x00000000u}, + {0x400000ffu, 0x7ffec000u, 0x80008000u, 0x00000000u}, + {0x000f4000u, 0xedcc0001u, 0x80008000u, 0x00000000u}, + {0x7fff0218u, 0x8010c000u, 0x80108010u, 0x00000000u}, + {0x80008000u, 0x80000218u, 0x00007ffeu, 0x00000000u}, + {0x021bffffu, 0x000fc000u, 0x7fffedccu, 0x00000000u}, + {0x02180002u, 0x000f8000u, 0x0001edccu, 0x00000000u}, + {0x021b4000u, 0x021b0001u, 0x02188001u, 0x00000000u}, + {0x7fff8000u, 0x40008010u, 0xffff1234u, 0x00000000u}, + {0x80100000u, 0x00010001u, 0x80000218u, 0x00000000u}, + {0x4000c000u, 0xffff021bu, 0x40008010u, 0x00000000u}, + {0x7fff4000u, 0x7ffe0002u, 0x000f00ffu, 0x00000000u}, + {0x000fedccu, 0x8000fffeu, 0x021b4000u, 0x00000000u}, + {0xedcc0001u, 0x8000021bu, 0xedcc8000u, 0x00000000u}, + {0x02180000u, 0x00ffedccu, 0x12344000u, 0x00000000u}, + {0xc000ffffu, 0x80010218u, 0x7fffc000u, 0x00000000u}, +}; + +enum class Kind { MaxI, MinI, MaxU, MinU, AddU, AddI, SubU, SubI, MulLo, MadI, MadU, Lshl, Ashr, Lshr }; + +constexpr std::int32_t Literal = -1; + +struct Case { + const char* name; + Kind kind; + std::array sources; + std::array opSel; + std::array opSelHi; + std::array neg; + std::array negHi; + bool clamp; +}; + +constexpr std::uint32_t LiteralValue = 0x00007fffu; + +constexpr std::array Cases{{ + {"v_pk_max_i16 v10, lit 0x7fff, v4 op_sel_hi:[0,1] neg_lo:[1,0] neg_hi:[1,0]", Kind::MaxI, {Literal, 0, 0}, {false, false, false}, {false, true, true}, {true, false, false}, {true, false, false}, false}, + {"v_pk_add_u16 v11, v4, v5 neg_lo:[0,1] neg_hi:[1,0]", Kind::AddU, {0, 1, 0}, {}, {true, true, true}, {false, true, false}, {true, false, false}, false}, + {"v_pk_sub_i16 v12, v4, v5 neg_lo:[1,0] neg_hi:[0,1]", Kind::SubI, {0, 1, 0}, {}, {true, true, true}, {true, false, false}, {false, true, false}, false}, + {"v_pk_min_i16 v13, v4, v5 neg_lo:[1,1] neg_hi:[1,1]", Kind::MinI, {0, 1, 0}, {}, {true, true, true}, {true, true, false}, {true, true, false}, false}, + {"v_pk_max_u16 v14, v4, v5 neg_lo:[0,1] neg_hi:[0,1]", Kind::MaxU, {0, 1, 0}, {}, {true, true, true}, {false, true, false}, {false, true, false}, false}, + {"v_pk_min_u16 v15, v4, v5 neg_lo:[1,0] neg_hi:[1,0]", Kind::MinU, {0, 1, 0}, {}, {true, true, true}, {true, false, false}, {true, false, false}, false}, + {"v_pk_mul_lo_u16 v16, v4, v5 neg_lo:[1,0] neg_hi:[0,1]", Kind::MulLo, {0, 1, 0}, {}, {true, true, true}, {true, false, false}, {false, true, false}, false}, + {"v_pk_mad_i16 v17, v4, v5, v6 neg_lo:[0,0,1] neg_hi:[1,0,1]", Kind::MadI, {0, 1, 2}, {}, {true, true, true}, {false, false, true}, {true, false, true}, false}, + {"v_pk_mad_u16 v18, v4, v5, v6 neg_lo:[1,1,0] neg_hi:[0,1,1]", Kind::MadU, {0, 1, 2}, {}, {true, true, true}, {true, true, false}, {false, true, true}, false}, + {"v_pk_lshlrev_b16 v19, v4, v5 neg_lo:[0,1] neg_hi:[0,1]", Kind::Lshl, {0, 1, 0}, {}, {true, true, true}, {false, true, false}, {false, true, false}, false}, + {"v_pk_ashrrev_i16 v20, v4, v5 neg_lo:[0,1] neg_hi:[1,1]", Kind::Ashr, {0, 1, 0}, {}, {true, true, true}, {false, true, false}, {true, true, false}, false}, + {"v_pk_lshrrev_b16 v21, v4, v5 neg_lo:[1,1] neg_hi:[0,1]", Kind::Lshr, {0, 1, 0}, {}, {true, true, true}, {true, true, false}, {false, true, false}, false}, + {"v_pk_add_i16 v22, v4, v5 op_sel:[1,0] op_sel_hi:[0,1] neg_lo:[1,0] neg_hi:[0,1]", Kind::AddI, {0, 1, 0}, {true, false, false}, {false, true, true}, {true, false, false}, {false, true, false}, false}, + {"v_pk_sub_u16 v23, v4, v5 op_sel:[0,1] op_sel_hi:[1,0] neg_lo:[1,1] neg_hi:[1,1]", Kind::SubU, {0, 1, 0}, {false, true, false}, {true, false, true}, {true, true, false}, {true, true, false}, false}, + {"v_pk_add_i16 v24, v4, v5 neg_lo:[0,1] neg_hi:[0,1] clamp", Kind::AddI, {0, 1, 0}, {}, {true, true, true}, {false, true, false}, {false, true, false}, true}, + {"v_pk_max_i16 v25, v6, v4 op_sel:[1,0] op_sel_hi:[0,1] neg_lo:[1,0] neg_hi:[1,0]", Kind::MaxI, {2, 0, 0}, {true, false, false}, {false, true, true}, {true, false, false}, {true, false, false}, false}, +}}; + +std::int32_t Signed(std::uint32_t half) { + return static_cast(static_cast(half)); +} + +std::uint32_t Source(const Case& c, const std::uint32_t* in, std::size_t i, bool highLane) { + const std::uint32_t word = c.sources[i] == Literal ? LiteralValue : in[c.sources[i]]; + const bool high = highLane ? c.opSelHi[i] : c.opSel[i]; + const bool negate = highLane ? c.negHi[i] : c.neg[i]; + return ((high ? word >> 16u : word) & 0xffffu) ^ (negate ? 0x8000u : 0u); +} + +std::uint32_t Lane(const Case& c, const std::uint32_t* in, bool highLane) { + const std::uint32_t a = Source(c, in, 0, highLane); + const std::uint32_t b = Source(c, in, 1, highLane); + const std::uint32_t s = Source(c, in, 2, highLane); + std::int64_t value = 0; + switch (c.kind) { + case Kind::MaxI: value = std::max(Signed(a), Signed(b)); break; + case Kind::MinI: value = std::min(Signed(a), Signed(b)); break; + case Kind::MaxU: value = std::max(a, b); break; + case Kind::MinU: value = std::min(a, b); break; + case Kind::AddU: value = a + b; break; + case Kind::AddI: value = static_cast(Signed(a)) + Signed(b); break; + case Kind::SubU: value = static_cast(a) - b; break; + case Kind::SubI: value = static_cast(Signed(a)) - Signed(b); break; + case Kind::MulLo: value = static_cast(a) * b; break; + case Kind::MadI: value = static_cast(Signed(a)) * Signed(b) + Signed(s); break; + case Kind::MadU: value = static_cast(a) * b + s; break; + case Kind::Lshl: value = static_cast(b) << (a & 15u); break; + case Kind::Ashr: value = Signed(b) >> (a & 15u); break; + case Kind::Lshr: value = b >> (a & 15u); break; + } + if (c.clamp) value = std::clamp(value, -32768, 32767); + return static_cast(value) & 0xffffu; +} + +std::uint32_t Reference(const Case& c, const std::uint32_t* in) { + return Lane(c, in, false) | (Lane(c, in, true) << 16u); +} + +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) { + for (std::uint32_t tid = 0; tid < Threads; ++tid) std::copy(std::begin(Rows[tid]), std::end(Rows[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() { + std::uint32_t failures = 0; + std::string report; + 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]; + const auto clampLane = [&](std::uint32_t x) { return static_cast(std::max(-1, Signed(x))) & 0xffffu; }; + const std::uint32_t tsr = clampLane(in[0] & 0xffffu) | (clampLane(in[0] >> 16u) << 16u); + Require(Reference(Cases[0], in) == tsr, "packed integer negate: reference disagrees with max(-1, x)"); + for (std::uint32_t i = 0; i < Results; ++i) { + const std::uint32_t expected = Reference(Cases[i], in); + if (out[i] != expected && failures++ < 24u) { + report += std::string("packed integer negate: lane ") + std::to_string(tid) + " " + Cases[i].name + " is " + Hex(out[i]) + ", expected " + Hex(expected) + "\n"; + } + } + } + Require(failures == 0u, report + std::to_string(failures) + " mismatches"); +} + +} + +int main() { + try { + const auto device = OpenVulkanTestDevice(); + if (!device) return VulkanTestSkipped; + Run(*device); + Check(); + std::puts("packed integer negate tests passed"); + return 0; + } catch (const std::exception& error) { + std::cerr << error.what() << '\n'; + return 1; + } +} diff --git a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp index 8347975fc..12a55ce57 100644 --- a/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp +++ b/core/shader/recompiler/Translation/src/TranslationContext/SubwordPacking.cpp @@ -125,7 +125,10 @@ IrU32 TranslationContext::readU16LaneRaw(const RdnaOperand& operand, bool highLa } IrU32 TranslationContext::readU16LaneAsU32(const RdnaOperand& operand, bool highLane, bool signExtend) { - const IrU32 raw = readU16LaneRaw(operand, highLane ? operand.opSelHi : operand.opSel); + IrU32 raw = readU16LaneRaw(operand, highLane ? operand.opSelHi : operand.opSel); + if (highLane ? operand.negateHi : operand.negate) { + raw = IrU32(ir.BitwiseXor(raw.Value(), ir.Constant(0x8000u))); + } if (!signExtend) { return raw; }