shader: support four 16-bit integer ternary operations

This commit is contained in:
nmzik committed 2026-10-28 22:37:24 +01:00
1 parent 138d5b86c4
commit 0386f33bf3
5 files changed
+95 -2

No files matched your search

+2
View File
@@ -696,6 +696,8 @@ if(BUILD_TESTING)
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --vop3-conversions-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --vop3-conversions-only)
add_test(NAME shader_recompiler_quadmask add_test(NAME shader_recompiler_quadmask
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --quadmask-only) COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --quadmask-only)
add_test(NAME shader_recompiler_integer16_ternary
COMMAND $<TARGET_FILE:shader_recompiler_compute_tests> --integer16-ternary-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
@@ -278,16 +278,20 @@ enum class Opcode {
V_MIN3_I32, V_MIN3_I32,
V_MIN3_U32, V_MIN3_U32,
V_MIN3_F16, V_MIN3_F16,
V_MIN3_I16,
V_MIN3_U16, V_MIN3_U16,
V_MAX3_F32, V_MAX3_F32,
V_MAX3_I32, V_MAX3_I32,
V_MAX3_U32, V_MAX3_U32,
V_MAX3_F16, V_MAX3_F16,
V_MAX3_I16,
V_MAX3_U16,
V_MED3_F32, V_MED3_F32,
V_MED3_I32, V_MED3_I32,
V_MED3_U32, V_MED3_U32,
V_MED3_F16, V_MED3_F16,
V_MED3_I16, V_MED3_I16,
V_MED3_U16,
V_SAD_U32, V_SAD_U32,
V_ADD3_U32, V_ADD3_U32,
V_LSHL_ADD_U32, V_LSHL_ADD_U32,
@@ -339,16 +339,20 @@ constexpr OpcodeMap VOP3_OPCODE_LIST[] = {
{0x152u, Opcode::V_MIN3_I32}, {0x152u, Opcode::V_MIN3_I32},
{0x153u, Opcode::V_MIN3_U32}, {0x153u, Opcode::V_MIN3_U32},
{0x351u, Opcode::V_MIN3_F16}, {0x351u, Opcode::V_MIN3_F16},
{0x352u, Opcode::V_MIN3_I16},
{0x353u, Opcode::V_MIN3_U16}, {0x353u, Opcode::V_MIN3_U16},
{0x154u, Opcode::V_MAX3_F32}, {0x154u, Opcode::V_MAX3_F32},
{0x155u, Opcode::V_MAX3_I32}, {0x155u, Opcode::V_MAX3_I32},
{0x156u, Opcode::V_MAX3_U32}, {0x156u, Opcode::V_MAX3_U32},
{0x354u, Opcode::V_MAX3_F16}, {0x354u, Opcode::V_MAX3_F16},
{0x355u, Opcode::V_MAX3_I16},
{0x356u, Opcode::V_MAX3_U16},
{0x157u, Opcode::V_MED3_F32}, {0x157u, Opcode::V_MED3_F32},
{0x158u, Opcode::V_MED3_I32}, {0x158u, Opcode::V_MED3_I32},
{0x159u, Opcode::V_MED3_U32}, {0x159u, Opcode::V_MED3_U32},
{0x357u, Opcode::V_MED3_F16}, {0x357u, Opcode::V_MED3_F16},
{0x358u, Opcode::V_MED3_I16}, {0x358u, Opcode::V_MED3_I16},
{0x359u, Opcode::V_MED3_U16},
{0x15du, Opcode::V_SAD_U32}, {0x15du, Opcode::V_SAD_U32},
{0x15eu, Opcode::V_CVT_PK_U8_F32}, {0x15eu, Opcode::V_CVT_PK_U8_F32},
{0x178u, Opcode::V_XOR3_B32}, {0x178u, Opcode::V_XOR3_B32},
@@ -494,7 +498,9 @@ bool IsNativeVop3F16TernaryOpcode(Opcode opcode) {
} }
bool IsNativeVop3B16TernaryOpcode(Opcode opcode) { bool IsNativeVop3B16TernaryOpcode(Opcode opcode) {
return opcode == Opcode::V_MIN3_U16 || opcode == Opcode::V_MED3_I16 || return opcode == Opcode::V_MIN3_I16 || opcode == Opcode::V_MIN3_U16 ||
opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 ||
opcode == Opcode::V_MED3_I16 || opcode == Opcode::V_MED3_U16 ||
opcode == Opcode::V_MAD_I16; opcode == Opcode::V_MAD_I16;
} }
@@ -1530,7 +1536,9 @@ bool SupportsNativeVop3ResultModifiers(Opcode opcode) {
bool SupportsNativeVop3Clamp(Opcode opcode) { bool SupportsNativeVop3Clamp(Opcode opcode) {
return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) || return SupportsNativeVop3ResultModifiers(opcode) || UsesInexactClampControl(opcode) ||
opcode == Opcode::V_MAD_I16; opcode == Opcode::V_MAD_I16 || opcode == Opcode::V_MIN3_I16 ||
opcode == Opcode::V_MAX3_I16 || opcode == Opcode::V_MAX3_U16 ||
opcode == Opcode::V_MED3_U16;
} }
bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix, bool HasUnsupportedNativeVop3Modifiers(Opcode opcode, bool permlane, bool mad_mix,
@@ -378,6 +378,10 @@ void Translator::EmitVector(const Decoder::Instruction& inst) {
case O::V_MAD_I16: return V_MAD_I16(inst); case O::V_MAD_I16: return V_MAD_I16(inst);
case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true); case O::V_MED3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMedTri32, true);
case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false); case O::V_MIN3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMinTri32, false);
case O::V_MIN3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMinTri32, true);
case O::V_MAX3_I16: return Integer16Ternary(inst, IR::ValueOpcode::SMaxTri32, true);
case O::V_MAX3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMaxTri32, false);
case O::V_MED3_U16: return Integer16Ternary(inst, IR::ValueOpcode::UMedTri32, false);
case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true); case O::V_MIN_I16: return Integer16Binary(inst, IR::ValueOpcode::SMin32, true);
case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true); case O::V_MAX_I16: return Integer16Binary(inst, IR::ValueOpcode::SMax32, true);
case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false); case O::V_MIN_U16: return Integer16Binary(inst, IR::ValueOpcode::UMin32, false);
+75
View File
@@ -26810,6 +26810,71 @@ TestCase Vop3MadI16CapturedSelectorsAndSaturation() {
return test; return test;
} }
TestCase Vop3Integer16Ternary(u32 wave_size) {
using O = ShaderOpcode;
namespace D = ShaderRecompiler::Decoder;
struct Operation { u32 encoding; O opcode; bool sign; u32 sorted_index; };
constexpr std::array operations{
Operation{0x352, O::V_MIN3_I16, true, 0},
Operation{0x355, O::V_MAX3_I16, true, 2},
Operation{0x356, O::V_MAX3_U16, false, 2},
Operation{0x359, O::V_MED3_U16, false, 1}};
constexpr std::array<std::array<u32, 3>, 4> inputs{{
{0x80007fffu, 0xffff0000u, 0x0001ffffu},
{0x00098000u, 0xffff0007u, 0x7fff0005u},
{0xffff8000u, 0xffff8000u, 0x00008000u},
{0x00000000u, 0x7fff7fffu, 0x7fff7fffu}}};
TestCase test;
test.name = wave_size == 32 ? "Vop3Integer16TernaryWave32" : "Vop3Integer16TernaryWave64";
for (const auto &input : inputs) test.initial.insert(test.initial.end(), input.begin(), input.end());
test.expected = test.initial;
auto &code = test.code;
for (const auto &operation : operations) {
// Integer ternaries accept half selectors, but no floating-point modifiers.
for (u32 modifier = 0; modifier < 9; ++modifier) {
const u32 words[] = {
EncodeVop3Word0(operation.encoding, 9, modifier < 3 ? 1u << modifier : 0),
EncodeVop3Word1(Vgpr(2), Vgpr(3), Vgpr(4),
modifier >= 6 ? 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,
"16-bit integer ternary accepted a floating-point modifier");
}
for (u32 i = 0; i < inputs.size(); ++i) {
for (u32 variant = 0; variant < 32; ++variant) {
const u32 selectors = variant & 15u;
std::array<int32_t, 3> selected{};
for (u32 operand = 0; operand < 3; ++operand) {
AppendVMovU32(&code, 30, (i * 3 + operand) * sizeof(u32));
AppendBufferLoadDword(&code, 2 + operand, 30);
const u32 half = (inputs[i][operand] >> (((selectors >> operand) & 1u) * 16)) & 0xffffu;
selected[operand] = operation.sign && half >= 0x8000u
? static_cast<int32_t>(half) - 0x10000 : static_cast<int32_t>(half);
}
code.push_back(EncodeVop1(0x02, 12, Vgpr(3))); // Broadcast source 1 from an SGPR.
const u32 dst = selectors & 1u ? 2u : 9u; // Also cover source/destination aliasing.
AppendVMovLiteral(&code, 9, 0xa5a5beefu);
AppendVop3(&code, operation.encoding, dst, Vgpr(2), 12, Vgpr(4), 0, selectors, variant >= 16);
AppendStoreVgpr(&code, dst, static_cast<u32>(test.expected.size()));
std::sort(selected.begin(), selected.end());
const u32 result = static_cast<u32>(selected[operation.sorted_index]) & 0xffffu;
const u32 old = dst == 2 ? inputs[i][0] : 0xa5a5beefu;
test.expected.push_back(selectors & 8u ? (old & 0xffffu) | (result << 16)
: (old & 0xffff0000u) | result);
}
}
test.opcodes.push_back(operation.opcode);
}
AppendEnd(&code);
test.opcodes.insert(test.opcodes.end(), {O::V_MOV_B32, O::V_READFIRSTLANE_B32,
O::BUFFER_LOAD_DWORD, O::BUFFER_STORE_DWORD, O::S_ENDPGM});
test.compute_info.wave_size = wave_size;
test.has_compute_info = true;
return test;
}
TestCase Vop3Min3U16CapturedAndSelectors() { TestCase Vop3Min3U16CapturedAndSelectors() {
using O = ShaderOpcode; using O = ShaderOpcode;
struct MinCase { struct MinCase {
@@ -46174,6 +46239,8 @@ std::vector<TestCase> MakeCases() {
AddCase(Vop3MulLoU16CapturedAndSelectors); AddCase(Vop3MulLoU16CapturedAndSelectors);
AddCase(Vop3MadI16CapturedSelectorsAndSaturation); AddCase(Vop3MadI16CapturedSelectorsAndSaturation);
AddCase(Vop3F32ClampFlushesNaN); AddCase(Vop3F32ClampFlushesNaN);
cases.push_back(Vop3Integer16Ternary(32));
cases.push_back(Vop3Integer16Ternary(64));
AddCase(Vop3Min3U16CapturedAndSelectors); AddCase(Vop3Min3U16CapturedAndSelectors);
AddCase(Vop3Med3I16Captured); AddCase(Vop3Med3I16Captured);
AddCase(Vop2SdwaMinU32PreservesWordDestination); AddCase(Vop2SdwaMinU32PreservesWordDestination);
@@ -52604,6 +52671,14 @@ 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], "--integer16-ternary-only") == 0) {
VulkanHarness vulkan;
RunCase(&vulkan, Vop3Integer16Ternary(32));
RunCase(&vulkan, Vop3Integer16Ternary(64));
RunCase(&vulkan, Vop3Min3U16CapturedAndSelectors());
RunCase(&vulkan, Vop3Med3I16Captured());
return 0;
}
if (argc == 2 && std::strcmp(argv[1], "--quadmask-only") == 0) { if (argc == 2 && std::strcmp(argv[1], "--quadmask-only") == 0) {
VulkanHarness vulkan; VulkanHarness vulkan;
RunCase(&vulkan, ScalarQuadmaskB32(32)); RunCase(&vulkan, ScalarQuadmaskB32(32));