From e0586cc85a2895a85b56c8bdccddedcc39cf4151 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 14 Oct 2026 18:21:58 +0200 Subject: [PATCH] shader: decode unsigned byte loads into preserved low halves Co-authored-by: fxpw --- .../recompiler/frontend/decode/MemoryOps.cpp | 2 + .../frontend/decode/ShaderDecoder.cpp | 1 + .../frontend/decode/ShaderDecoder.h | 1 + .../recompiler/frontend/translate/Memory.cpp | 1 + tests/ShaderRecompilerComputeTests.cpp | 60 +++++++++++++++---- tests/shaderCfgTests.cpp | 19 ++++++ 6 files changed, 71 insertions(+), 13 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp index 104c24c91..ce0594786 100644 --- a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp @@ -48,6 +48,7 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = { {0x1du, Opcode::BUFFER_STORE_DWORDX2, 2, 32}, {0x1eu, Opcode::BUFFER_STORE_DWORDX4, 4, 32}, {0x1fu, Opcode::BUFFER_STORE_DWORDX3, 3, 32}, + {0x20u, Opcode::BUFFER_LOAD_UBYTE_D16, 1, 8}, {0x24u, Opcode::BUFFER_LOAD_SHORT_D16, 1, 16}, {0x25u, Opcode::BUFFER_LOAD_SHORT_D16_HI, 1, 16}, {0x30u, Opcode::BUFFER_ATOMIC_SWAP, 1, 32}, @@ -310,6 +311,7 @@ void DecodeMubuf(uint32_t pc, std::span code, uint32_t word_inde inst.data_components = 4u; } if (inst.opcode == Opcode::BUFFER_LOAD_FORMAT_D16_X || + inst.opcode == Opcode::BUFFER_LOAD_UBYTE_D16 || inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16 || inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI) { inst.dst.sdwa_sel = inst.opcode == Opcode::BUFFER_LOAD_SHORT_D16_HI ? 5u : 4u; diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index da6390a41..665331490 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -638,6 +638,7 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::BUFFER_STORE_FORMAT_XYZW: case Opcode::BUFFER_STORE_FORMAT_D16_X: case Opcode::BUFFER_LOAD_UBYTE: + case Opcode::BUFFER_LOAD_UBYTE_D16: case Opcode::BUFFER_LOAD_USHORT: case Opcode::BUFFER_LOAD_SHORT_D16: case Opcode::BUFFER_LOAD_SHORT_D16_HI: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index ffac3e163..49da4296c 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -554,6 +554,7 @@ enum class Opcode { BUFFER_STORE_FORMAT_XYZW, BUFFER_STORE_FORMAT_D16_X, BUFFER_LOAD_UBYTE, + BUFFER_LOAD_UBYTE_D16, BUFFER_LOAD_SBYTE, BUFFER_LOAD_USHORT, BUFFER_LOAD_SSHORT, diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index 980ff870f..cc03f20ce 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -973,6 +973,7 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) { } case Decoder::Opcode::BUFFER_LOAD_UBYTE: + case Decoder::Opcode::BUFFER_LOAD_UBYTE_D16: case Decoder::Opcode::BUFFER_LOAD_SBYTE: case Decoder::Opcode::BUFFER_LOAD_USHORT: case Decoder::Opcode::BUFFER_LOAD_SSHORT: diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index ecccb775c..5d5a4b0fc 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -20209,6 +20209,7 @@ CoverageClass ClassifyOpcode(ShaderOpcode opcode, case Opcode::BUFFER_STORE_FORMAT_XYZ: case Opcode::BUFFER_STORE_FORMAT_XYZW: case Opcode::BUFFER_LOAD_UBYTE: + case Opcode::BUFFER_LOAD_UBYTE_D16: case Opcode::BUFFER_LOAD_SBYTE: case Opcode::BUFFER_LOAD_USHORT: case Opcode::BUFFER_LOAD_SSHORT: @@ -30020,12 +30021,17 @@ TestCase BufferLoadVariants() { O::BUFFER_STORE_DWORD, O::S_ENDPGM}}; } -TestCase BufferLoadShortD16Captured(bool high) { +TestCase BufferLoadD16Captured(ShaderOpcode opcode, u32 wave_size = 32) { + const bool high = opcode == ShaderOpcode::BUFFER_LOAD_SHORT_D16_HI; + const bool byte = opcode == ShaderOpcode::BUFFER_LOAD_UBYTE_D16; TestCase test; - test.name = high ? "BufferLoadShortD16HiCaptured" : "BufferLoadShortD16Captured"; + test.name = byte ? (wave_size == 64 ? "BufferLoadUbyteD16Captured64" : "BufferLoadUbyteD16Captured32") + : high ? "BufferLoadShortD16HiCaptured" : "BufferLoadShortD16Captured"; auto& code = test.code; - const u32 data_reg = high ? 20u : 1u, index_reg = high ? 21u : 15u; - const std::array load = high ? std::array{0xe0942006u, 0x80021415u} + const u32 data_reg = byte ? 0u : high ? 20u : 1u; + const u32 index_reg = byte ? 3u : high ? 21u : 15u; + const std::array load = byte ? std::array{0xe080e000u, 0x80040003u} + : high ? std::array{0xe0942006u, 0x80021415u} : std::array{0xe090200eu, 0x8000010fu}; AppendVMovU32(&code, index_reg, 1); AppendVMovLiteral(&code, data_reg, 0xabcd1234u); @@ -30043,27 +30049,49 @@ TestCase BufferLoadShortD16Captured(bool high) { AppendVMovLiteral(&code, data_reg, 0xbeef5678u); code.insert(code.end(), load.begin(), load.end()); AppendStoreVgpr(&code, data_reg, 2); + if (byte) { + AppendVMovU32(&code, index_reg, 1); + for (u32 offset = 1; offset <= 3; ++offset) { + AppendVMovLiteral(&code, data_reg, 0xa1b2ffffu); + code.insert(code.end(), {load[0] | offset, load[1]}); + AppendStoreVgpr(&code, data_reg, 2 + offset); + } + code.insert(code.end(), {load[0], load[1] | (index_reg << 8u)}); + AppendStoreVgpr(&code, index_reg, 6); + } AppendEnd(&code); const u32 stride = high ? 16u : 32u; test.initial.assign(high ? 12u : 20u, 0xa5a5a5a5u); - test.initial[high ? 5u : 11u] = 0x8001cafeu; + test.initial[byte ? 8u : high ? 5u : 11u] = byte ? 0xff007f80u : 0x8001cafeu; if (high) test.initial[7] = 0xcafefaceu; test.expected = test.initial; - test.expected[0] = high ? 0x8001cafeu : 0xabcd8001u; + test.expected[0] = byte ? 0xabcd0080u : high ? 0x8001cafeu : 0xabcd8001u; test.expected[1] = 0xdeaf4321u; test.expected[2] = high ? 0x00005678u : 0xbeef0000u; test.user_data = MakeStructuredStorageBufferData(stride, 2); if (high) std::copy_n(test.user_data.begin(), 4, test.user_data.begin() + 8); + if (byte) { + std::copy_n(test.user_data.begin(), 4, test.user_data.begin() + 16); + test.expected[3] = 0xa1b2007fu; + test.expected[4] = 0xa1b20000u; + test.expected[5] = 0xa1b200ffu; + test.expected[6] = 0x80u; + test.compute_info.threads_num[0] = test.compute_info.threads_num[1] = + test.compute_info.threads_num[2] = 1; + test.compute_info.wave_size = static_cast(wave_size); + test.has_compute_info = true; + test.required_spirv = {"Volatile"}; + } test.user_data[50] = static_cast(test.initial.size() * sizeof(u32)); test.user_data[51] = 3u << 28u; test.has_user_data = true; test.storage_buffer_range_bytes = stride * 2u; test.opcodes = {ShaderOpcode::V_MOV_B32, ShaderOpcode::S_MOV_B64, - ShaderOpcode::BUFFER_LOAD_SHORT_D16, ShaderOpcode::BUFFER_STORE_DWORD, - ShaderOpcode::S_ENDPGM}; - if (high) test.opcodes.push_back(ShaderOpcode::BUFFER_LOAD_SHORT_D16_HI); - test.decoded_counts = {{high ? "BUFFER_LOAD_SHORT_D16_HI v20.sdwa(sel=5,sext=0)" - : "BUFFER_LOAD_SHORT_D16 v1.sdwa(sel=4,sext=0)", 3}}; + opcode, ShaderOpcode::BUFFER_STORE_DWORD, ShaderOpcode::S_ENDPGM}; + if (high) test.opcodes.push_back(ShaderOpcode::BUFFER_LOAD_SHORT_D16); + test.decoded_counts = {{byte ? "BUFFER_LOAD_UBYTE_D16 v0.sdwa(sel=4,sext=0)" + : high ? "BUFFER_LOAD_SHORT_D16_HI v20.sdwa(sel=5,sext=0)" + : "BUFFER_LOAD_SHORT_D16 v1.sdwa(sel=4,sext=0)", byte ? 6u : 3u}}; return test; } @@ -40364,7 +40392,10 @@ std::vector MakeCases() { cases.push_back(BufferOffsetsUsePackedWords(false)); cases.push_back(BufferOffsetsUsePackedWords(true)); AddCase(BufferLoadVariants); - for (const bool high : {false, true}) cases.push_back(BufferLoadShortD16Captured(high)); + for (const auto opcode : {ShaderOpcode::BUFFER_LOAD_SHORT_D16, ShaderOpcode::BUFFER_LOAD_SHORT_D16_HI}) + cases.push_back(BufferLoadD16Captured(opcode)); + for (const u32 wave : {32u, 64u}) + cases.push_back(BufferLoadD16Captured(ShaderOpcode::BUFFER_LOAD_UBYTE_D16, wave)); for (const u32 component : {4u, 2u, 0u}) { cases.push_back(BufferSubwordLoadsAtHostOffset(2, component, false)); } @@ -46279,7 +46310,10 @@ int main(int argc, char **argv) { } if (argc == 2 && std::strcmp(argv[1], "--flat-d16-only") == 0) { VulkanHarness vulkan; - for (const bool high : {false, true}) RunCase(&vulkan, BufferLoadShortD16Captured(high)); + for (const auto opcode : {ShaderOpcode::BUFFER_LOAD_SHORT_D16, ShaderOpcode::BUFFER_LOAD_SHORT_D16_HI}) + RunCase(&vulkan, BufferLoadD16Captured(opcode)); + for (const u32 wave : {32u, 64u}) + RunCase(&vulkan, BufferLoadD16Captured(ShaderOpcode::BUFFER_LOAD_UBYTE_D16, wave)); RunCase(&vulkan, FlatStoreSlcCaptured()); RunCase(&vulkan, FlatLoadDlcCaptured(32)); RunCase(&vulkan, FlatLoadDlcCaptured(64)); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index aca9dbe11..0631ef46b 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -15357,6 +15357,24 @@ void TestGraphicsPushConstantPlacement() { CheckSpirvBinaryValidates(spill.spirv); } +void TestCapturedBufferLoadUbyteD16Decode() { + namespace Decoder = ShaderRecompiler::Decoder; + const uint32_t code[] = {0xe080e000u, 0x80040003u}; + Decoder::Instruction inst; + Decoder::DecodeInstruction(code, 0, inst); + Check(inst.opcode == Decoder::Opcode::BUFFER_LOAD_UBYTE_D16 && + inst.word_count == 2 && inst.data_dwords == 1 && inst.data_bits == 8 && + !inst.data_signed && !inst.formatted && !inst.typed, + "captured BUFFER_LOAD_UBYTE_D16 lost its unsigned byte payload"); + Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 0 && + inst.dst.sdwa_sel == 4 && inst.dst.sdwa_dst_unused == 2 && + inst.src0.kind == Decoder::OperandKind::Vgpr && inst.src0.reg == 3 && + inst.src1.kind == Decoder::OperandKind::Sgpr && inst.src1.reg == 16 && + inst.src2.kind == Decoder::OperandKind::IntegerInlineConstant && inst.src2.value == 0 && + inst.idxen && !inst.offen && inst.offset == 0 && inst.glc && inst.dlc && !inst.slc, + "captured BUFFER_LOAD_UBYTE_D16 lost its preserved half, address, or coherence flags"); +} + void TestNewShaderRecompilerUnsupportedMemoryDecode() { const uint32_t mubuf_unknown[] = {EncodeMubuf0(0x7b), EncodeMubuf1(0, 0, 1), 0xbf810000u}; @@ -16040,6 +16058,7 @@ int main() { TestComputeLdsAllocationIdentity(); TestPixelProgramCacheBindingIdentity(); TestGraphicsPushConstantPlacement(); + TestCapturedBufferLoadUbyteD16Decode(); TestNewShaderRecompilerUnsupportedMemoryDecode(); TestNewShaderRecompilerVop3LaneReadDestinationEncoding();