From 9f25e3c23ece1bfd3631aadc994eba443ee7d386 Mon Sep 17 00:00:00 2001 From: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com> Date: Sat, 19 Sep 2026 15:15:40 +0200 Subject: [PATCH] shader: support partial SDWA multiply and byte-source moves --- .../frontend/decode/VectorAluOps.cpp | 4 +- tests/ShaderRecompilerComputeTests.cpp | 157 ++++++++++++++++++ 2 files changed, 159 insertions(+), 2 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 75cfbd300..08ef51116 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -36,7 +36,7 @@ constexpr Vop2OpcodeInfo VOP2_OPCODE_LIST[] = { {0x05u, Opcode::V_SUBREV_F32}, {0x08u, Opcode::V_MUL_F32, Vop2SdwaProfile::Float32}, {0x09u, Opcode::V_MUL_I32_I24, Vop2SdwaProfile::IntegerPartialDestination}, - {0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerFullDestination}, + {0x0bu, Opcode::V_MUL_U32_U24, Vop2SdwaProfile::IntegerPartialDestination}, {0x0fu, Opcode::V_MIN_F32}, {0x10u, Opcode::V_MAX_F32}, {0x11u, Opcode::V_MIN_I32}, @@ -579,7 +579,7 @@ struct Vop1SdwaRule { constexpr Vop1SdwaRule VOP1_SDWA_RULES[] = { {Opcode::V_MOV_B32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), SdwaSelBytes() | SdwaSelWords(), - SdwaSelWords() | SdwaSelFull(), false}, + SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), false}, {Opcode::V_CVT_F32_U32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false}, {Opcode::V_CVT_F32_I32, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false}, {Opcode::V_CVT_F32_UBYTE0, SdwaSelBytes() | SdwaSelWords() | SdwaSelFull(), 0, 0, false}, diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 9cd933028..e272ae399 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -22094,6 +22094,80 @@ TestCase Vop1SdwaMovByteDestinations() { return test; } +TestCase Vop1SdwaMovByteSources(u32 wave_size) { + using O = ShaderOpcode; + // Select 0x80 from each byte position, with nonzero neighboring bytes. + constexpr std::array sources{ + 0x12345680u, 0x12348056u, 0x12803456u, 0x80341256u}; + // RDNA2 table 88 / PS5 SDWA-related options: PAD, SEXT, PRESERVE. + constexpr std::array partial_expected{ + 0x00000080u, 0xffffff80u, 0xa1b2c380u, + 0x00008000u, 0xffff8000u, 0xa1b280d4u, + 0x00800000u, 0xff800000u, 0xa180c3d4u, + 0x80000000u, 0x80000000u, 0x80b2c3d4u, + 0x00000080u, 0x00000080u, 0xa1b20080u, + 0x00800000u, 0x00800000u, 0x0080c3d4u}; + constexpr std::array sext_word_expected{ + 0x0000ff80u, 0xffffff80u, 0xa1b2ff80u, + 0xff800000u, 0xff800000u, 0xff80c3d4u}; + TestCase test; + test.name = wave_size == 64 ? "Vop1SdwaMovByteSourcesWave64" + : "Vop1SdwaMovByteSourcesWave32"; + test.initial.assign(sources.begin(), sources.end()); + test.expected = test.initial; + auto &code = test.code; + const auto store = [&](u32 expected) { + AppendStoreVgpr(&code, 1, static_cast(test.expected.size())); + test.expected.push_back(expected); + }; + for (u32 src_sel = 0; src_sel < sources.size(); ++src_sel) { + AppendVMovU32(&code, 30, src_sel * sizeof(u32)); + AppendBufferLoadDword(&code, 2, 30); + for (u32 sext = 0; sext < 2; ++sext) { + for (u32 dst_sel = 0; dst_sel < 6; ++dst_sel) { + for (u32 unused = 0; unused < 3; ++unused) { + AppendVMovLiteral(&code, 1, 0xa1b2c3d4u); + code.push_back(EncodeVop1(0x01, 1, 249)); + code.push_back(EncodeVop1Sdwa(2, dst_sel, unused, src_sel, sext)); + store(sext && dst_sel >= 4 ? sext_word_expected[(dst_sel - 4u) * 3u + unused] + : partial_expected[dst_sel * 3u + unused]); + } + } + } + } + // Aliasing must read the source byte before replacing the destination byte. + AppendVMovU32(&code, 30, 0); + AppendBufferLoadDword(&code, 1, 30); + code.push_back(EncodeVop1(0x01, 1, 249)); + code.push_back(EncodeVop1Sdwa(1, 3, 2, 1)); + store(0x56345680u); + // Scalar and inline sources use the same independent selectors. + AppendSMovLiteral(&code, 18, 0x12348056u); + code.push_back(EncodeVop1(0x01, 1, 249)); + code.push_back(EncodeVop1Sdwa(18, 5, 0, 1, 1, 0, 0, 1)); + store(0xff800000u); + AppendVMovLiteral(&code, 1, 0xa1b2c3d4u); + code.push_back(EncodeVop1(0x01, 1, 249)); + code.push_back(EncodeVop1Sdwa(193, 0, 2, 3, 0, 0, 0, 1)); // Inline -1. + store(0xa1b2c3ffu); + for (u32 unused = 0; unused < 3; ++unused) { + AppendVMovLiteral(&code, 1, 0xa1b2c3d4u); + code.push_back(EncodeSMovB32(126, InlineU32(0))); + code.push_back(EncodeVop1(0x01, 1, 249)); + code.push_back(EncodeVop1Sdwa(2, 4, unused, 0)); + code.push_back(EncodeSMovB32(126, InlineU32(1))); + store(0xa1b2c3d4u); + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpBitFieldUExtract", "OpBitFieldSExtract", "OpBitFieldInsert"}; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + return test; +} + TestCase Vop2SdwaSubNcExactByte2Destination() { using O = ShaderOpcode; @@ -22266,6 +22340,74 @@ TestCase Vop2SdwaMulI24Destinations(u32 wave_size) { return test; } +TestCase Vop2SdwaMulU24Destinations(u32 wave_size) { + using O = ShaderOpcode; + // The high source bytes are discarded: 3 * 0xe080 = 0x2a180. + constexpr std::array partial_expected{ + 0x00000080u, 0xffffff80u, 0xa1b2c380u, + 0x00008000u, 0xffff8000u, 0xa1b280d4u, + 0x00800000u, 0xff800000u, 0xa180c3d4u, + 0x80000000u, 0x80000000u, 0x80b2c3d4u, + 0x0000a180u, 0xffffa180u, 0xa1b2a180u, + 0xa1800000u, 0xa1800000u, 0xa180c3d4u}; + TestCase test; + test.name = wave_size == 64 ? "Vop2SdwaMulU24DestinationsWave64" + : "Vop2SdwaMulU24DestinationsWave32"; + test.initial = {0xab000003u, 0xcd00e080u, 0xffffffffu}; + test.expected = test.initial; + auto &code = test.code; + const auto store = [&](u32 reg, u32 expected) { + AppendStoreVgpr(&code, reg, static_cast(test.expected.size())); + test.expected.push_back(expected); + }; + for (u32 i = 0; i < test.initial.size(); ++i) { + AppendVMovU32(&code, 30, i * sizeof(u32)); + AppendBufferLoadDword(&code, 12u + i, 30); + } + for (u32 selector = 0; selector < 6; ++selector) { + for (u32 unused = 0; unused < 3; ++unused) { + AppendVMovLiteral(&code, 26, 0xa1b2c3d4u); + code.push_back(EncodeVop2(0x0b, 26, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, selector, unused)); + store(26, partial_expected[selector * 3u + unused]); + } + } + // Full result still truncates the 48-bit unsigned product to 32 bits. + code.push_back(EncodeVop2(0x0b, 26, 249, 14)); + code.push_back(EncodeVop2Sdwa(14)); + store(26, 0xfe000001u); + // Either multiplicand may alias the partial destination. + code.push_back(EncodeVop2(0x0b, 12, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, 5, 2)); + store(12, 0xa1800003u); + code.push_back(EncodeVop2(0x0b, 13, 249, 13)); + code.push_back(EncodeVop2Sdwa(InlineU32(3), 2, 2, 6, 4, + 0, 0, 0, 0, 0, 0, 1)); + store(13, 0xcd80e080u); + // Source-byte sign extension happens before the unsigned 24-bit multiply. + AppendSMovLiteral(&code, 18, 0x12348056u); + code.push_back(EncodeVop2(0x0b, 26, 249, InlineU32(3))); + code.push_back(EncodeVop2Sdwa(18, 5, 0, 1, 6, 1, 0, 0, 0, 0, 0, 1, 1)); + store(26, 0xfe800000u); + for (u32 unused = 0; unused < 3; ++unused) { + AppendVMovLiteral(&code, 26, 0xa1b2c3d4u); + code.push_back(EncodeSMovB32(126, InlineU32(0))); + code.push_back(EncodeVop2(0x0b, 26, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, 4, unused)); + code.push_back(EncodeSMovB32(126, InlineU32(1))); + store(26, 0xa1b2c3d4u); + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, + O::V_MUL_U32_U24, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"V_MUL_U32_U24", 25u}}; + test.required_spirv = {"OpIMul", "OpBitFieldUExtract", "OpBitFieldSExtract"}; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + return test; +} + TestCase Vop2SdwaLshrrevCapturedByte1Source() { using O = ShaderOpcode; @@ -35937,6 +36079,8 @@ std::vector MakeCases() { AddCase(Vop1SdwaNotPreservesHighWordDestination); AddCase(Vop1SdwaNotPartialSourcesAndDestinations); AddCase(Vop1SdwaMovByteDestinations); + cases.push_back(Vop1SdwaMovByteSources(32)); + cases.push_back(Vop1SdwaMovByteSources(64)); AddCase(Vop2SdwaSubNcExactByte2Destination); AddCase(Vop2SdwaAddNcCapturedHighWordDestination); AddCase(Vop2SdwaAshrrevCapturedWord0SignExtends); @@ -35944,6 +36088,8 @@ std::vector MakeCases() { cases.push_back(Vop2SdwaMaxI32CapturedHighWord(64)); cases.push_back(Vop2SdwaMulI24Destinations(32)); cases.push_back(Vop2SdwaMulI24Destinations(64)); + cases.push_back(Vop2SdwaMulU24Destinations(32)); + cases.push_back(Vop2SdwaMulU24Destinations(64)); AddCase(Vop2SdwaLshrrevCapturedByte1Source); AddCase(Vop2SdwaSubNcPreservesByteAndWordDestinations); AddCase(Vop3CvtPkI16I32Captured); @@ -41935,6 +42081,8 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--sdwa-mov-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, Vop1SdwaMovByteDestinations()); + RunCase(&vulkan, Vop1SdwaMovByteSources(32)); + RunCase(&vulkan, Vop1SdwaMovByteSources(64)); return 0; } if (argc == 2 && std::strcmp(argv[1], "--sdwa-ffbh-only") == 0) { @@ -41984,6 +42132,15 @@ int main(int argc, char **argv) { RunCase(&vulkan, VectorIntegerOps()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--sdwa-mul-u24-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, Vop2SdwaMulU24Destinations(32)); + RunCase(&vulkan, Vop2SdwaMulU24Destinations(64)); + RunCase(&vulkan, Vop2SdwaMulI24Destinations(32)); + RunCase(&vulkan, Vop2SdwaMulI24Destinations(64)); + RunCase(&vulkan, VectorIntegerOps()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--cvt-pk-i16-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, Vop3CvtPkI16I32Captured());