Merge pull request #4268 from AlxcBTW/fix/vop3p-int-neg

fix(shader): apply VOP3P neg_lo/neg_hi to packed 16-bit integer sources
This commit is contained in:
Dean 2026-10-13 07:35:10 +13:00 committed by GitHub
commit 9d3629cc91
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 215 additions and 1 deletions

View File

@ -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 <algorithm>
#include <array>
#include <cstdint>
#include <cstdio>
#include <iostream>
#include <string>
#include <vector>
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<std::uint32_t, Threads * Inputs> Input{};
alignas(256) std::array<std::uint32_t, Threads * Results> Output{};
alignas(256) constexpr std::array<std::uint32_t, 47> 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<std::int32_t, 3> sources;
std::array<bool, 3> opSel;
std::array<bool, 3> opSelHi;
std::array<bool, 3> neg;
std::array<bool, 3> negHi;
bool clamp;
};
constexpr std::uint32_t LiteralValue = 0x00007fffu;
constexpr std::array<Case, Results> 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<std::int16_t>(static_cast<std::uint16_t>(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<std::int64_t>(Signed(a)) + Signed(b); break;
case Kind::SubU: value = static_cast<std::int64_t>(a) - b; break;
case Kind::SubI: value = static_cast<std::int64_t>(Signed(a)) - Signed(b); break;
case Kind::MulLo: value = static_cast<std::int64_t>(a) * b; break;
case Kind::MadI: value = static_cast<std::int64_t>(Signed(a)) * Signed(b) + Signed(s); break;
case Kind::MadU: value = static_cast<std::int64_t>(a) * b + s; break;
case Kind::Lshl: value = static_cast<std::int64_t>(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<std::int64_t>(value, -32768, 32767);
return static_cast<std::uint32_t>(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<std::uint32_t, 4> BufferDescriptor(const void* data, std::uint32_t bytes) {
const auto address = reinterpret_cast<std::uintptr_t>(data);
return {static_cast<std::uint32_t>(address), static_cast<std::uint32_t>((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<std::uint32_t> userData(8, 0u);
const auto input = BufferDescriptor(Input.data(), static_cast<std::uint32_t>(Input.size() * 4u));
const auto output = BufferDescriptor(Output.data(), static_cast<std::uint32_t>(Output.size() * 4u));
std::copy(input.begin(), input.end(), userData.begin());
std::copy(output.begin(), output.end(), userData.begin() + 4);
const std::span<const std::uint32_t> code(Code);
const std::array<ShaderRecompiler::MemoryRegion, 1> memory{{{reinterpret_cast<std::uintptr_t>(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<std::uintptr_t>(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<std::uintptr_t>(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::uint32_t>(std::max<std::int32_t>(-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;
}
}

View File

@ -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;
}