diff --git a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp index 33988749e..6a1b70210 100644 --- a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp @@ -84,6 +84,7 @@ constexpr MemoryOpcodeInfo FLAT_OPCODE_LIST[] = { {0x18u, Opcode::FLAT_STORE_BYTE, 1, 8}, {0x1au, Opcode::FLAT_STORE_SHORT, 1, 16}, {0x1cu, Opcode::FLAT_STORE_DWORD, 1, 32}, {0x1du, Opcode::FLAT_STORE_DWORDX2, 2, 32}, {0x1eu, Opcode::FLAT_STORE_DWORDX4, 4, 32}, {0x1fu, Opcode::FLAT_STORE_DWORDX3, 3, 32}, + {0x24u, Opcode::FLAT_LOAD_SHORT_D16, 1, 16}, }; constexpr MemoryOpcodeInfo DS_OPCODE_LIST[] = { @@ -373,6 +374,10 @@ void DecodeFlat(uint32_t pc, std::span code, uint32_t word_index } DecodeVectorGpr(IsFlatStoreOpcode(inst.opcode) ? data : vdst, inst.dst); + if (inst.opcode == Opcode::FLAT_LOAD_SHORT_D16) { + // D16 loads reuse partial destinations to preserve the untouched high half. + inst.dst.sdwa_sel = 4u; + } DecodeVectorGpr(addr, inst.src0); inst.src_count = 1; if (seg == 0u || saddr == 0x7du || saddr == 0x7fu) { diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index c33637879..aa7b1e5f2 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -663,6 +663,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::FLAT_LOAD_SBYTE: case Opcode::FLAT_LOAD_USHORT: case Opcode::FLAT_LOAD_SSHORT: + case Opcode::FLAT_LOAD_SHORT_D16: case Opcode::FLAT_LOAD_DWORD: case Opcode::FLAT_LOAD_DWORDX2: case Opcode::FLAT_LOAD_DWORDX3: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index edabd831b..11e6c4ff6 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -532,6 +532,7 @@ enum class Opcode { FLAT_LOAD_SBYTE, FLAT_LOAD_USHORT, FLAT_LOAD_SSHORT, + FLAT_LOAD_SHORT_D16, FLAT_LOAD_DWORD, FLAT_LOAD_DWORDX2, FLAT_LOAD_DWORDX3, diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index f1bd226f3..817b6903e 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -1077,6 +1077,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) { case Decoder::Opcode::FLAT_LOAD_SBYTE: case Decoder::Opcode::FLAT_LOAD_USHORT: case Decoder::Opcode::FLAT_LOAD_SSHORT: + case Decoder::Opcode::FLAT_LOAD_SHORT_D16: case Decoder::Opcode::FLAT_LOAD_DWORD: case Decoder::Opcode::FLAT_LOAD_DWORDX2: case Decoder::Opcode::FLAT_LOAD_DWORDX3: diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 5d6a25f5d..937c0c80b 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -18672,6 +18672,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::FLAT_LOAD_SBYTE: case Opcode::FLAT_LOAD_USHORT: case Opcode::FLAT_LOAD_SSHORT: + case Opcode::FLAT_LOAD_SHORT_D16: case Opcode::FLAT_LOAD_DWORD: case Opcode::FLAT_LOAD_DWORDX2: case Opcode::FLAT_LOAD_DWORDX3: @@ -29171,6 +29172,89 @@ TestCase FlatSubdwordLoadsApplyByteOffset() { return test; } +TestCase GlobalLoadShortD16Captured(u32 wave_size) { + using O = ShaderOpcode; + constexpr uint64_t GuestBase = 0x0000000110000000ull; + constexpr u32 Marker = 0xa5a50000u; + constexpr uint64_t ScalarBase = GuestBase - Marker - 0x70u; + constexpr uint64_t ExecMask = 0xaaaaaaaa55555555ull; + constexpr u32 Values[] = {0u, 0x7fffu, 0x8000u, 0xffffu}; + TestCase test; + test.name = wave_size == 32u ? "GlobalLoadShortD16CapturedWave32" + : "GlobalLoadShortD16CapturedWave64"; + test.initial.resize(2u * wave_size + wave_size / 2u); + test.expected.resize(2u * wave_size); + for (u32 lane = 0; lane < wave_size; ++lane) { + const u32 value = Values[lane % std::size(Values)]; + test.initial[2u * wave_size + lane / 2u] |= value << (16u * (lane % 2u)); + test.expected[lane] = Marker | value; + test.expected[wave_size + lane] = Marker | + ((ExecMask & (uint64_t{1} << lane)) != 0u ? value : lane * 2u); + } + auto &code = test.code; + AppendSMovLiteral(&code, 2, static_cast(ScalarBase)); + AppendSMovLiteral(&code, 3, static_cast(ScalarBase >> 32u)); + code.push_back(EncodeVop2(0x1a, 6, InlineU32(1), 0)); + AppendVMovLiteral(&code, 5, Marker); + code.push_back(EncodeVop2(0x25, 6, Vgpr(5), 6)); + for (bool masked : {false, true}) { + code.push_back(EncodeVop1(0x01, 5, Vgpr(6))); + if (masked) { + code.push_back(EncodeSop1(0x04, 8, 126)); + AppendSMovLiteral(&code, 126, static_cast(ExecMask)); + AppendSMovLiteral(&code, 127, static_cast(ExecMask >> 32u)); + } + // Captured GLOBAL_LOAD_SHORT_D16: v5 supplies the byte offset and destination. + code.insert(code.end(), {0xdc908070u, 0x05020005u}); + if (masked) code.push_back(EncodeSop1(0x04, 126, 8)); + AppendStoreVgprAtLaneDwordOffset(&code, 5, 0, masked ? wave_size : 0u); + } + AppendEnd(&code); + test.bda_mappings = {{GuestBase, 8u * 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.compute_info.wave_size = wave_size; + test.has_compute_info = true; + test.opcodes = {O::S_MOV_B32, O::S_MOV_B64, O::V_MOV_B32, O::V_LSHLREV_B32, + O::V_ADD_NC_U32, O::FLAT_LOAD_SHORT_D16, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + test.decoded_counts = {{"FLAT_LOAD_SHORT_D16", 2}}; + return test; +} + +TestCase FlatLoadShortD16AddressSegments() { + using O = ShaderOpcode; + constexpr uint64_t GuestBase = 0x0000000110000000ull; + std::vector code; + AppendVMovLiteral(&code, 20, static_cast(GuestBase) + 2u); + AppendVMovLiteral(&code, 21, static_cast(GuestBase >> 32u)); + for (u32 segment : {0u, 2u}) { + AppendVMovLiteral(&code, 3, 0x12345678u); + code.push_back(EncodeFlat0(0x24, segment, segment == 0u ? 0u : 0xffeu)); + code.push_back(EncodeFlat1(3, 0x7d, 0, 20)); + AppendStoreVgpr(&code, 3, segment == 0u ? 0u : 1u); + } + AppendVMovU32(&code, 20, 0u); + AppendVMovLiteral(&code, 3, 0xbeef5678u); + code.push_back(EncodeFlat0(0x1c, 1, 0)); + code.push_back(EncodeFlat1(0, 0x7d, 3, 20)); + AppendVMovLiteral(&code, 3, 0x98761234u); + code.push_back(EncodeFlat0(0x24, 1, 2)); + code.push_back(EncodeFlat1(3, 0x7d, 0, 20)); + AppendStoreVgpr(&code, 3, 2); + AppendEnd(&code); + TestCase test{"FlatLoadShortD16AddressSegments", code, + {0, 0, 0, 0, 0x80017fffu}, + {0x12348001u, 0x12347fffu, 0x9876beefu}, + {O::V_MOV_B32, O::FLAT_LOAD_SHORT_D16, O::FLAT_STORE_DWORD, + O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; + test.bda_mappings = {{GuestBase, 16}}; + test.compute_info.scratch_size_dwords = 1; + return test; +} + TestCase BranchVccnzUsesCarryProducedWaveMask() { using O = ShaderOpcode; @@ -34720,6 +34804,9 @@ std::vector MakeCases() { AddCase(TBufferStoreVariants); AddCase(FlatLoadVariants); AddCase(FlatSubdwordLoadsApplyByteOffset); + cases.push_back(GlobalLoadShortD16Captured(32)); + cases.push_back(GlobalLoadShortD16Captured(64)); + AddCase(FlatLoadShortD16AddressSegments); AddCase(FlatVirtualAddressRebasesGuestAllocation); AddCase(GlobalSignedImmediateRebasesBeforeSaddr); AddCase(FlatSegmentIgnoresSaddrAndMasksOffsetMsb); @@ -40007,6 +40094,18 @@ int main(int argc, char **argv) { } return 0; } + if (argc == 2 && std::strcmp(argv[1], "--flat-d16-only") == 0) { + VulkanHarness vulkan; + RunCase(&vulkan, GlobalLoadShortD16Captured(32)); + RunCase(&vulkan, GlobalLoadShortD16Captured(64)); + RunCase(&vulkan, FlatLoadShortD16AddressSegments()); + RunCase(&vulkan, FlatSubdwordLoadsApplyByteOffset()); + RunCase(&vulkan, FlatVirtualAddressRebasesGuestAllocation()); + RunCase(&vulkan, GlobalSignedImmediateRebasesBeforeSaddr()); + RunCase(&vulkan, FlatSegmentIgnoresSaddrAndMasksOffsetMsb()); + RunCase(&vulkan, ScratchIsPrivatePerInvocation()); + return 0; + } if (argc == 2 && std::strcmp(argv[1], "--indirect-buffer-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, ScalarBufferFromLoopReadlane(32));