shader: support high products of 24-bit integers

This commit is contained in:
nmzik
2026-10-28 22:49:21 +01:00
parent 22610cb93e
commit 54b937c85f
7 changed files with 128 additions and 4 deletions
+2
View File
@@ -700,6 +700,8 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-only)
add_test(NAME shader_recompiler_mad_u16 add_test(NAME shader_recompiler_mad_u16
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mad-u16-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mad-u16-only)
add_test(NAME shader_recompiler_mul_hi24
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --mul-hi24-only)
add_test(NAME shader_recompiler_alignbyte add_test(NAME shader_recompiler_alignbyte
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --alignbyte-only)
add_test(NAME graphics_pipeline_rasterization add_test(NAME graphics_pipeline_rasterization
@@ -249,7 +249,9 @@ enum class Opcode {
V_MIN_I32, V_MIN_I32,
V_MAX_I32, V_MAX_I32,
V_MUL_I32_I24, V_MUL_I32_I24,
V_MUL_HI_I32_I24,
V_MUL_U32_U24, V_MUL_U32_U24,
V_MUL_HI_U32_U24,
V_MUL_LO_U32, V_MUL_LO_U32,
V_MUL_HI_U32, V_MUL_HI_U32,
V_MUL_LO_I32, V_MUL_LO_I32,
@@ -35,7 +35,9 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = {
{0x05u, Opcode::V_SUBREV_F32}, {0x05u, Opcode::V_SUBREV_F32},
{0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32}, {0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32},
{0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination}, {0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0au, Opcode::V_MUL_HI_I32_I24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination}, {0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0cu, Opcode::V_MUL_HI_U32_U24, Vop2SdwaProfile::IntegerPartialDestination},
{0x0fu, Opcode::V_MIN_F32}, {0x0fu, Opcode::V_MIN_F32},
{0x10u, Opcode::V_MAX_F32}, {0x10u, Opcode::V_MAX_F32},
{0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination}, {0x11u, Opcode::V_MIN_I32, Vop2SdwaProfile::IntegerFullDestination},
@@ -1539,7 +1541,8 @@ bool SupportsNativeVop3Clamp(Opcode opcode) {
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 || opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MAD_U16 ||
opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MAX3_I16 ||
opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16; opcode == Opcode::V_MAX3_U16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MUL_HI_I32_I24 || opcode == Opcode::V_MUL_HI_U32_U24;
} }
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
@@ -1570,6 +1573,10 @@ bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mi
return clamp != 0u || omod != 0u || neg != 0u; return clamp != 0u || omod != 0u || neg != 0u;
} }
switch (opcode) { switch (opcode) {
case Opcode::V_MUL_HI_I32_I24:
case Opcode::V_MUL_HI_U32_U24:
// The high product fits in 32 bits, so clamp has no effect.
return abs != 0u || op_sel != 0u || omod != 0u || neg != 0u;
case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u; case Opcode::V_LDEXP_F32: return (abs & ~1u) != 0u || op_sel != 0u || (neg & ~1u) != 0u;
case Opcode::V_CVT_F32_UBYTE0: case Opcode::V_CVT_F32_UBYTE0:
case Opcode::V_CVT_F32_UBYTE1: case Opcode::V_CVT_F32_UBYTE1:
@@ -269,7 +269,7 @@ void Translator::V_FFBH_32(const Decoder::Instruction& inst, bool sign) {
WriteOperand(DestinationOperand(inst), result); WriteOperand(DestinationOperand(inst), result);
} }
void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend) { void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high) {
const auto extract24 = [&](IR::U32 value) { const auto extract24 = [&](IR::U32 value) {
return IR::U32( return IR::U32(
ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract, ir.Emit(sign ? IR::ValueOpcode::BitFieldSExtract : IR::ValueOpcode::BitFieldUExtract,
@@ -277,7 +277,9 @@ void Translator::Integer24(const Decoder::Instruction& inst, bool sign, bool add
}; };
const auto lhs = extract24(ReadU32(inst.src0)); const auto lhs = extract24(ReadU32(inst.src0));
const auto rhs = extract24(ReadU32(inst.src1)); const auto rhs = extract24(ReadU32(inst.src1));
auto result = ir.IMul(lhs, rhs); const auto multiply = high ? (sign ? IR::ValueOpcode::SMulHi : IR::ValueOpcode::UMulHi)
: IR::ValueOpcode::IMul32;
auto result = IR::U32(ir.Emit(multiply, {lhs, rhs}));
if (addend) { if (addend) {
result = ir.IAdd(result, ReadU32(inst.src2)); result = ir.IAdd(result, ReadU32(inst.src2));
} }
@@ -187,7 +187,7 @@ private:
void V_XOR3_B32(const Decoder::Instruction& inst); void V_XOR3_B32(const Decoder::Instruction& inst);
void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign); void S_FF_I32_B64(const Decoder::Instruction& inst, bool find_zero, bool from_msb, bool sign);
void V_FFBH_32(const Decoder::Instruction& inst, bool sign); void V_FFBH_32(const Decoder::Instruction& inst, bool sign);
void Integer24(const Decoder::Instruction& inst, bool sign, bool addend); void Integer24(const Decoder::Instruction& inst, bool sign, bool addend, bool high = false);
void V_MAD_U64_U32(const Decoder::Instruction& inst); void V_MAD_U64_U32(const Decoder::Instruction& inst);
void V_SAD_U32(const Decoder::Instruction& inst); void V_SAD_U32(const Decoder::Instruction& inst);
void V_ADD3_U32(const Decoder::Instruction& inst); void V_ADD3_U32(const Decoder::Instruction& inst);
@@ -586,6 +586,8 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MAD_U32_U24: return Integer24(inst, false, true); case O::V_MAD_U32_U24: return Integer24(inst, false, true);
case O::V_MUL_I32_I24: return Integer24(inst, true, false); case O::V_MUL_I32_I24: return Integer24(inst, true, false);
case O::V_MUL_U32_U24: return Integer24(inst, false, false); case O::V_MUL_U32_U24: return Integer24(inst, false, false);
case O::V_MUL_HI_I32_I24: return Integer24(inst, true, false, true);
case O::V_MUL_HI_U32_U24: return Integer24(inst, false, false, true);
case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst); case O::V_MAD_U64_U32: return V_MAD_U64_U32(inst);
case O::V_SAD_U32: return V_SAD_U32(inst); case O::V_SAD_U32: return V_SAD_U32(inst);
case O::V_ADD3_U32: return V_ADD3_U32(inst); case O::V_ADD3_U32: return V_ADD3_U32(inst);
+109
View File
@@ -26425,6 +26425,102 @@ TestCase Vop2SdwaMinI32CapturedByte0(u32 wave_size) {
return test; return test;
} }
TestCase VectorMulHi24(u32 wave_size) {
using O = ShaderOpcode;
namespace D = ShaderRecompiler::Decoder;
constexpr std::array<u32, 8> lhs{0x7f7fffffu, 0xa5800000u, 0x12ffffffu, 0xab000000u,
0x00010000u, 0xffabcdefu, 0x80000001u, 0x7fffffffu};
constexpr std::array<u32, 8> rhs{0x00800000u, 0xcd800000u, 0x55000001u, 0x99ffffffu,
0xab010000u, 0x00ffffffu, 0xfffffffeu, 0xffffffffu};
TestCase test;
test.name = wave_size == 32 ? "VectorMulHi24Wave32" : "VectorMulHi24Wave64";
for (u32 lane = 0; lane < wave_size; ++lane) test.initial.push_back(lhs[lane % lhs.size()]);
for (u32 lane = 0; lane < wave_size; ++lane) test.initial.push_back(rhs[lane % rhs.size()]);
test.expected = test.initial;
auto &code = test.code;
code.push_back(EncodeVop2(0x1a, 30, InlineU32(2), 0));
AppendBufferLoadDword(&code, 2, 30);
AppendVMovU32(&code, 20, wave_size * sizeof(u32));
code.push_back(EncodeVop2(0x25, 30, Vgpr(20), 30));
AppendBufferLoadDword(&code, 3, 30);
for (bool sign : {true, false}) {
const u32 opcode = sign ? 0x0a : 0x0c;
for (u32 modifier = 0; modifier < 13; ++modifier) {
const u32 words[] = {
EncodeVop3Word0(0x100 + opcode, 7, modifier < 3 ? 1u << modifier : 0,
modifier >= 9 ? 1u << (modifier - 9) : 0),
EncodeVop3Word1(Vgpr(5), Vgpr(6), 0,
modifier >= 6 && modifier < 9 ? modifier - 5 : 0,
modifier >= 3 && modifier < 6 ? 1u << (modifier - 3) : 0)};
D::Instruction decoded;
D::DecodeInstruction(words, 0, decoded);
Require(test.name, "integer modifiers", decoded.opcode == O::UNSUPPORTED,
"24-bit high multiply accepted unsupported modifiers");
}
for (u32 variant = 0; variant < 10; ++variant) {
code.push_back(EncodeVop1(0x01, 5, Vgpr(2)));
code.push_back(EncodeVop1(0x01, 6, Vgpr(3)));
AppendVMovLiteral(&code, 7, 0xa5a5beefu);
u32 dst = 7;
switch (variant) {
case 0: dst = 5; code.push_back(EncodeVop2(opcode, dst, Vgpr(5), 6)); break;
case 1: dst = 6; AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), Vgpr(6)); break;
case 2: AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), Vgpr(6), 0, 0, 0, true); break;
case 3:
code.push_back(EncodeVop2(opcode, dst, 255, 6));
code.push_back(0x00ffffffu);
break;
case 4:
AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), 255);
code.push_back(0x00800000u);
break;
case 5:
AppendSMovLiteral(&code, 20, 0xffff0001u);
AppendVop3(&code, 0x100 + opcode, dst, Vgpr(5), 20);
break;
case 6:
case 7:
case 8:
code.push_back(EncodeVop2(opcode, dst, 249, 6));
code.push_back(EncodeVop2Sdwa(5, variant == 7 ? 5 : 6, variant == 7 ? 2 : 0,
variant == 8 ? 4 : 6, 6, variant == 8));
break;
case 9:
code.push_back(EncodeVop2(opcode, dst, 250, 6));
code.push_back(EncodeVop2Dpp(5, 0x01b)); // Reverse each quad's source lanes.
break;
}
AppendStoreVgprAtLaneDwordOffset(&code, dst, 0, static_cast<u32>(test.expected.size()));
for (u32 lane = 0; lane < wave_size; ++lane) {
const u32 source_lane = variant == 9 ? lane ^ 3u : lane;
u32 a = lhs[source_lane % lhs.size()], b = rhs[lane % rhs.size()];
if (variant == 3) a = 0x00ffffffu;
if (variant == 4) b = 0x00800000u;
if (variant == 5) b = 0xffff0001u;
if (variant == 8) a = (a & 0x8000u) ? a | 0xffff0000u : a & 0xffffu;
const auto extend24 = [sign](u32 value) -> int64_t {
const int64_t low = value & 0x00ffffffu;
return sign && (value & 0x00800000u) ? low - 0x01000000 : low;
};
const uint64_t product = static_cast<uint64_t>(extend24(a) * extend24(b));
const u32 high = static_cast<u32>(product >> 32);
test.expected.push_back(variant == 7 ? 0xbeefu | (high << 16) : high);
}
}
}
AppendEnd(&code);
test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::V_LSHLREV_B32, O::V_ADD_NC_U32,
O::BUFFER_LOAD_DWORD, O::V_MUL_HI_I32_I24, O::V_MUL_HI_U32_U24,
O::BUFFER_STORE_DWORD, O::S_ENDPGM};
test.compute_info.wave_size = wave_size;
test.compute_info.threads_num[0] = wave_size;
test.compute_info.threads_num[1] = 1;
test.compute_info.threads_num[2] = 1;
test.compute_info.thread_ids_num = 1;
test.has_compute_info = true;
return test;
}
TestCase Vop2SdwaMulI24Destinations(u32 wave_size) { TestCase Vop2SdwaMulI24Destinations(u32 wave_size) {
using O = ShaderOpcode; using O = ShaderOpcode;
constexpr std::array<u32, 5> sources{ constexpr std::array<u32, 5> sources{
@@ -46282,6 +46378,8 @@ std::vector<TestCase> MakeCases() {
cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64)); cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64));
cases.push_back(Vop2SdwaMinI32CapturedByte0(32)); cases.push_back(Vop2SdwaMinI32CapturedByte0(32));
cases.push_back(Vop2SdwaMinI32CapturedByte0(64)); cases.push_back(Vop2SdwaMinI32CapturedByte0(64));
cases.push_back(VectorMulHi24(32));
cases.push_back(VectorMulHi24(64));
cases.push_back(Vop2SdwaMulI24Destinations(32)); cases.push_back(Vop2SdwaMulI24Destinations(32));
cases.push_back(Vop2SdwaMulI24Destinations(64)); cases.push_back(Vop2SdwaMulI24Destinations(64));
cases.push_back(Vop2SdwaMulU24Destinations(32)); cases.push_back(Vop2SdwaMulU24Destinations(32));
@@ -52727,6 +52825,17 @@ int main(int argc, char **argv) {
std::setvbuf(stdout, nullptr, _IONBF, 0); std::setvbuf(stdout, nullptr, _IONBF, 0);
EnsureConfigInitialized(); EnsureConfigInitialized();
CheckLeastRecentlyUsedCacheOrdering(); CheckLeastRecentlyUsedCacheOrdering();
if (argc == 2 && std::strcmp(argv[1], "--mul-hi24-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, VectorMulHi24(32));
RunCase(&vulkan, VectorMulHi24(64));
RunCase(&vulkan, Vop2SdwaMulI24Destinations(32));
RunCase(&vulkan, Vop2SdwaMulI24Destinations(64));
RunCase(&vulkan, Vop2SdwaMulU24Destinations(32));
RunCase(&vulkan, Vop2SdwaMulU24Destinations(64));
RunCase(&vulkan, VectorVop3IntegerOps());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--mad-u16-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--mad-u16-only") == 0) {
VulkanHarness vulkan; VulkanHarness vulkan;
RunCase(&vulkan, Vop3MadU16(32)); RunCase(&vulkan, Vop3MadU16(32));