diff --git a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp index 08ef51116..7c36ca0b4 100644 --- a/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/VectorAluOps.cpp @@ -997,7 +997,8 @@ constexpr Vop2SdwaRule VOP2_SDWA_RULES[] = { {SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false}, {SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false}, {SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), false, false}, - {SdwaSelWords() | SdwaSelFull(), SdwaSelAll(), SdwaSelAll(), true, false}, + // Bitwise: WriteRawU32 inserts a byte or word destination like any other partial write. + {SdwaSelAll(), SdwaSelAll(), SdwaSelAll(), true, false}, }; static_assert(sizeof(VOP2_SDWA_RULES) / sizeof(VOP2_SDWA_RULES[0]) == static_cast(Vop2SdwaProfile::Count)); diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 45c64f61c..4285822c1 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -22142,6 +22142,75 @@ TestCase Vop1SdwaNotPartialSourcesAndDestinations() { return test; } +TestCase Vop2SdwaBitwiseByteDestinations(u32 wave_size) { + using O = ShaderOpcode; + // Low result bytes of AND, OR, XOR, XNOR for the two runtime inputs. + constexpr std::array result_bytes{0xa4u, 0xbeu, 0x1au, 0xe5u}; + constexpr std::array alias_expected{ + 0xc350e5a6u, 0xc3fce5a6u, 0xc3ace5a6u, 0xc353e5a6u}; + constexpr u32 sentinel = 0xa1b2c3d4u; + TestCase test; + test.name = wave_size == 64 ? "Vop2SdwaBitwiseByteDestinationsWave64" + : "Vop2SdwaBitwiseByteDestinationsWave32"; + test.initial = {0xc3d4e5a6u, 0x9b785abcu}; + 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 op = 0; op < 4; ++op) { + AppendVMovU32(&code, 30, 0); + AppendBufferLoadDword(&code, 12, 30); + AppendVMovU32(&code, 30, 4); + AppendBufferLoadDword(&code, 13, 30); + for (u32 selector = 0; selector < 4; ++selector) { + for (u32 unused = 0; unused < 3; ++unused) { + AppendVMovLiteral(&code, 26, sentinel); + code.push_back(EncodeVop2(0x1b + op, 26, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, selector, unused)); + // RDNA2 table 88: pad, sign extend above/zero below, or preserve. + const u32 shift = selector * 8u; + u32 expected = result_bytes[op] << shift; + if (unused == 1 && (result_bytes[op] & 0x80u) != 0) { + expected = (0xffffff00u | result_bytes[op]) << shift; + } else if (unused == 2) { + expected |= sentinel & ~(0xffu << shift); + } + store(26, expected); + } + } + // Read source byte2 and word1 before overwriting source0's byte2. + code.push_back(EncodeVop2(0x1b + op, 12, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, 2, 2, 2, 5)); + store(12, alias_expected[op]); + // Source1 may also alias the destination. + AppendVMovU32(&code, 30, 0); + AppendBufferLoadDword(&code, 12, 30); + code.push_back(EncodeVop2(0x1b + op, 13, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, 1, 2)); + store(13, 0x9b7800bcu | (result_bytes[op] << 8u)); + for (u32 unused = 0; unused < 3; ++unused) { + AppendVMovLiteral(&code, 26, sentinel); + code.push_back(EncodeSMovB32(126, InlineU32(0))); + code.push_back(EncodeVop2(0x1b + op, 26, 249, 13)); + code.push_back(EncodeVop2Sdwa(12, 2, unused)); + code.push_back(EncodeSMovB32(126, InlineU32(1))); + store(26, sentinel); + } + } + AppendEnd(&code); + test.initial.resize(test.expected.size()); + test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, + O::V_AND_B32, O::V_OR_B32, O::V_XOR_B32, O::V_XNOR_B32, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.required_spirv = {"OpBitwiseAnd", "OpBitwiseOr", "OpBitwiseXor", "OpNot", + "OpBitFieldInsert", "OpBitFieldSExtract"}; + test.compute_info.wave_size = wave_size; + test.has_compute_info = true; + return test; +} + TestCase Vop1SdwaMovByteDestinations() { using O = ShaderOpcode; @@ -36334,6 +36403,8 @@ std::vector MakeCases() { AddCase(Vop1SdwaNotCapturedByte0Source); AddCase(Vop1SdwaNotPreservesHighWordDestination); AddCase(Vop1SdwaNotPartialSourcesAndDestinations); + cases.push_back(Vop2SdwaBitwiseByteDestinations(32)); + cases.push_back(Vop2SdwaBitwiseByteDestinations(64)); AddCase(Vop1SdwaMovByteDestinations); cases.push_back(Vop1SdwaMovByteSources(32)); cases.push_back(Vop1SdwaMovByteSources(64)); @@ -42415,6 +42486,13 @@ int main(int argc, char **argv) { RunCase(&vulkan, ScalarZeroShiftWithRuntimeCount()); return 0; } + if (argc == 2 && std::strcmp(argv[1], "--sdwa-bitwise-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, Vop2SdwaBitwiseByteDestinations(32)); + RunCase(&vulkan, Vop2SdwaBitwiseByteDestinations(64)); + RunCase(&vulkan, Vop1SdwaNotPartialSourcesAndDestinations()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--sdwa-mov-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, Vop1SdwaMovByteDestinations());