From c5016d9455fe52bdd090cef7d80beb15fad6a3a5 Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 22 Oct 2026 05:13:26 +0200 Subject: [PATCH] shader: support D16 XY and XYZ formatted buffer loads and stores --- .../recompiler/frontend/decode/MemoryOps.cpp | 4 ++ .../frontend/decode/ShaderDecoder.cpp | 4 ++ .../frontend/decode/ShaderDecoder.h | 4 ++ .../recompiler/frontend/translate/Memory.cpp | 4 ++ src/graphics/shader/recompiler/ir/Program.cpp | 2 +- tests/ShaderRecompilerComputeTests.cpp | 51 +++++++++++++++---- tests/shaderCfgTests.cpp | 18 +++++++ 7 files changed, 76 insertions(+), 11 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp index 4fe12d4fb..cca92e1d1 100644 --- a/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/MemoryOps.cpp @@ -74,8 +74,12 @@ constexpr MemoryOpcodeInfo MUBUF_OPCODE_LIST[] = { {0x71u, Opcode::BUFFER_GL0_INV, 0, 0}, {0x72u, Opcode::BUFFER_GL1_INV, 0, 0}, {0x80u, Opcode::BUFFER_LOAD_FORMAT_D16_X, 1, 16, false, false, true}, + {0x81u, Opcode::BUFFER_LOAD_FORMAT_D16_XY, 1, 16, false, false, true, 2}, + {0x82u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZ, 2, 16, false, false, true, 3}, {0x83u, Opcode::BUFFER_LOAD_FORMAT_D16_XYZW, 2, 16, false, false, true, 4}, {0x84u, Opcode::BUFFER_STORE_FORMAT_D16_X, 1, 16, false, false, true}, + {0x85u, Opcode::BUFFER_STORE_FORMAT_D16_XY, 1, 16, false, false, true, 2}, + {0x86u, Opcode::BUFFER_STORE_FORMAT_D16_XYZ, 2, 16, false, false, true, 3}, {0x87u, Opcode::BUFFER_STORE_FORMAT_D16_XYZW, 2, 16, false, false, true, 4}, }; diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp index 77ac23085..2974f389b 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.cpp @@ -636,6 +636,8 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::BUFFER_LOAD_FORMAT_XYZ: case Opcode::BUFFER_LOAD_FORMAT_XYZW: case Opcode::BUFFER_LOAD_FORMAT_D16_X: + case Opcode::BUFFER_LOAD_FORMAT_D16_XY: + case Opcode::BUFFER_LOAD_FORMAT_D16_XYZ: case Opcode::BUFFER_LOAD_FORMAT_D16_XYZW: case Opcode::BUFFER_STORE_FORMAT_X: case Opcode::BUFFER_STORE_FORMAT_XY: @@ -643,6 +645,8 @@ std::string InstructionToString(const Instruction& inst) { case Opcode::BUFFER_STORE_FORMAT_XYZW: case Opcode::BUFFER_STORE_FORMAT_D16_X: case Opcode::BUFFER_STORE_FORMAT_D16_HI_X: + case Opcode::BUFFER_STORE_FORMAT_D16_XY: + case Opcode::BUFFER_STORE_FORMAT_D16_XYZ: case Opcode::BUFFER_STORE_FORMAT_D16_XYZW: case Opcode::BUFFER_LOAD_UBYTE: case Opcode::BUFFER_LOAD_UBYTE_D16: diff --git a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h index 63e92124a..e18b89b6c 100644 --- a/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h +++ b/src/graphics/shader/recompiler/frontend/decode/ShaderDecoder.h @@ -547,6 +547,8 @@ enum class Opcode { BUFFER_LOAD_FORMAT_XYZ, BUFFER_LOAD_FORMAT_XYZW, BUFFER_LOAD_FORMAT_D16_X, + BUFFER_LOAD_FORMAT_D16_XY, + BUFFER_LOAD_FORMAT_D16_XYZ, BUFFER_LOAD_FORMAT_D16_XYZW, BUFFER_STORE_FORMAT_X, BUFFER_STORE_FORMAT_XY, @@ -554,6 +556,8 @@ enum class Opcode { BUFFER_STORE_FORMAT_XYZW, BUFFER_STORE_FORMAT_D16_X, BUFFER_STORE_FORMAT_D16_HI_X, + BUFFER_STORE_FORMAT_D16_XY, + BUFFER_STORE_FORMAT_D16_XYZ, BUFFER_STORE_FORMAT_D16_XYZW, BUFFER_LOAD_UBYTE, BUFFER_LOAD_UBYTE_D16, diff --git a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp index 0b755ef24..b8eba8daa 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Memory.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Memory.cpp @@ -997,6 +997,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) { case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZ: case Decoder::Opcode::BUFFER_LOAD_FORMAT_XYZW: case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_X: + case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY: + case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZ: case Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XYZW: case Decoder::Opcode::TBUFFER_LOAD_FORMAT_X: case Decoder::Opcode::TBUFFER_LOAD_FORMAT_XY: @@ -1017,6 +1019,8 @@ void Translator::EmitMemory(const Decoder::Instruction& inst) { case Decoder::Opcode::BUFFER_STORE_FORMAT_XYZW: case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_X: case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_HI_X: + case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XY: + case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZ: case Decoder::Opcode::BUFFER_STORE_FORMAT_D16_XYZW: case Decoder::Opcode::TBUFFER_STORE_FORMAT_X: case Decoder::Opcode::TBUFFER_STORE_FORMAT_XY: diff --git a/src/graphics/shader/recompiler/ir/Program.cpp b/src/graphics/shader/recompiler/ir/Program.cpp index 4bc82fc6d..92a85b706 100644 --- a/src/graphics/shader/recompiler/ir/Program.cpp +++ b/src/graphics/shader/recompiler/ir/Program.cpp @@ -487,7 +487,7 @@ void ValidateProgram(const Program& program, bool require_ssa) { const auto access = BufferAccessOf(inst.GetOpcode()); const bool packed_formatted = (access == BufferAccess::Read || access == BufferAccess::Write) && memory.formatted && - memory.data_bits == 16u && memory.component_count == 4u && buffer_components == 2u; + memory.data_bits == 16u && (memory.component_count + 1u) / 2u == buffer_components; if (buffer_components > 1u && (!vector_buffer || (memory.data_bits != 32u && !packed_formatted) || memory.data_dwords != buffer_components || memory.component_index != 0u)) { diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 5e3be4d92..40551da77 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -33076,7 +33076,7 @@ std::vector BufferLoadFormatD16XCases() { return cases; } -std::vector BufferLoadFormatD16XyzwCases() { +std::vector BufferLoadFormatD16VectorCases() { using F = Prospero::BufferFormat; using O = ShaderOpcode; struct LoadCase { @@ -33089,6 +33089,7 @@ std::vector BufferLoadFormatD16XyzwCases() { u32 index = 0; bool active = true; bool overlap = false; + u32 opcode = 0x83u; }; constexpr LoadCase inputs[] = { {"BufferLoadFormatD16XyzwFloat32", F::k32_32_32_32Float, @@ -33118,7 +33119,26 @@ std::vector BufferLoadFormatD16XyzwCases() { {"BufferLoadFormatD16XyzwOverlappingAddress", F::k32_32_32_32Float, {0x3f800000u, 0x3f000000u, 0u, 0xc0000000u}, {0x38003c00u, 0xc0000000u}, DstSel(4, 5, 6, 7), 16, 0, true, true}, + {"BufferLoadFormatD16XyFloat32", F::k32_32_32_32Float, + {0x3f800000u, 0x3f000000u, 0u, 0xc0000000u}, + {0x38003c00u, 0xbeef5678u}, DstSel(4, 5, 6, 7), 16, 0, true, false, 0x81u}, + {"BufferLoadFormatD16XyUintSaturates", F::k32_32UInt, + {0x10000u, 7u}, {0x0007ffffu, 0xbeef5678u}, DstSel(4, 5, 6, 7), 8, 0, true, false, 0x81u}, + {"BufferLoadFormatD16XyzFloat32", F::k32_32_32_32Float, + {0x3f800000u, 0x3f000000u, 0xc0000000u, 0x3e800000u}, + {0x38003c00u, 0xbeefc000u}, DstSel(4, 5, 6, 7), 16, 0, true, false, 0x82u}, + {"BufferLoadFormatD16XyzSintSaturates", F::k32_32_32_32SInt, + {0xffff7fffu, 7u, 0x8000u, 0xffff8000u}, + {0x00078000u, 0xbeef7fffu}, DstSel(4, 5, 6, 7), 16, 0, true, false, 0x82u}, + {"BufferLoadFormatD16XyzOobOne", F::k32_32_32_32Float, + {}, {0x3c00u, 0xbeef3c00u}, DstSel(1, 4, 1, 4), 16, 1, true, false, 0x82u}, + {"BufferLoadFormatD16XyzZeroStrideFloatOne", F::k32_32_32_32Float, + {}, {0x3c003c00u, 0xbeef0000u}, DstSel(1, 1, 4, 1), 0, 0, true, false, 0x82u}, + {"BufferLoadFormatD16XyzInactive", F::k32_32_32_32Float, + {}, {0xabcd1234u, 0xbeef5678u}, DstSel(4, 5, 6, 7), 16, 0, false, false, 0x82u}, }; + constexpr O formats[] = {O::BUFFER_LOAD_FORMAT_D16_XY, O::BUFFER_LOAD_FORMAT_D16_XYZ, + O::BUFFER_LOAD_FORMAT_D16_XYZW}; std::vector cases; for (const auto &input : inputs) { TestCase test; @@ -33128,8 +33148,8 @@ std::vector BufferLoadFormatD16XyzwCases() { AppendVMovLiteral(&test.code, 18, 0xcafef00du); AppendVMovU32(&test.code, 12, input.index); if (!input.active) test.code.push_back(EncodeSop1(0x04, 126, InlineU32(0))); - // Captured b21e373811f28e31 pc 0x1914: e20c2000 8001100c. - test.code.push_back(EncodeMubuf0(0x83u, 0, true, false)); + // Captured XYZW b21e373811f28e31 pc 0x1914: e20c2000 8001100c. + test.code.push_back(EncodeMubuf0(input.opcode, 0, true, false)); test.code.push_back(EncodeMubuf1(16, 1, input.overlap ? 16 : 12)); if (!input.active) { test.code.push_back(EncodeSMovB32(126, InlineU32(1))); @@ -33151,7 +33171,7 @@ std::vector BufferLoadFormatD16XyzwCases() { if (input.stride != 0 && input.active) { test.storage_buffer_ranges = {{0, input.stride}, {0, 32}}; } - test.opcodes = {O::V_MOV_B32, O::BUFFER_LOAD_FORMAT_D16_XYZW, + test.opcodes = {O::V_MOV_B32, formats[input.opcode - 0x81u], O::BUFFER_STORE_DWORD, O::S_ENDPGM}; cases.push_back(std::move(test)); } @@ -33195,12 +33215,23 @@ std::vector BufferStoreFormatD16Cases() { {0xff80ff7fu, 0x0080007fu}, {0x7f7f8080u}, 1, 0x87u}, {"BufferStoreFormatD16XyzwUnorm8", F::k8_8_8_8UNorm, {0x3800c000u, 0x40003c00u}, {0xffff8000u}, 1, 0x87u}, + {"BufferStoreFormatD16XyFloat32", F::k32_32Float, + {0x3800c000u}, {0xc0000000u, 0x3f000000u}, 2, 0x85u}, + {"BufferStoreFormatD16XyUint16", F::k16_16UInt, {0xfffe0001u}, {0xfffe0001u}, 1, 0x85u}, + {"BufferStoreFormatD16XyzFloat32", F::k32_32_32Float, + {0xc0003c00u, 0xdead3800u}, {0x3f800000u, 0xc0000000u, 0x3f000000u}, 3, 0x86u}, + {"BufferStoreFormatD16XyzZeroFillsW", F::k16_16_16_16Float, + {0xc0003c00u, 0xdead3800u}, {0xc0003c00u, 0x00003800u}, 2, 0x86u}, + {"BufferStoreFormatD16XyzUint8", F::k8_8_8_8UInt, + {0x00020001u, 0xdead0003u}, {0x00030201u}, 1, 0x86u}, }; + constexpr O formats[] = {O::BUFFER_STORE_FORMAT_D16_X, O::BUFFER_STORE_FORMAT_D16_XY, + O::BUFFER_STORE_FORMAT_D16_XYZ, O::BUFFER_STORE_FORMAT_D16_XYZW}; std::vector cases; for (const auto &input : inputs) for (const u32 wave : {32u, 64u}) for (u32 mask = 0; mask < (input.opcode == 0x27u ? 2u : 1u); ++mask) { - const bool wide = input.opcode == 0x87u; + const bool wide = input.opcode >= 0x85u; const bool high = input.opcode == 0x27u; const bool multi_lane = wide || high; const bool masked = wide || mask != 0; @@ -33253,9 +33284,9 @@ std::vector BufferStoreFormatD16Cases() { test.user_data = MakeStructuredStorageBufferData(stride, multi_lane ? wave : 3u, false, BufferFormat(input.format)); test.has_user_data = true; - test.opcodes = {O::V_MOV_B32, high ? O::BUFFER_STORE_FORMAT_D16_HI_X : - wide ? O::BUFFER_STORE_FORMAT_D16_XYZW : - O::BUFFER_STORE_FORMAT_D16_X, O::S_ENDPGM}; + test.opcodes = {O::V_MOV_B32, + high ? O::BUFFER_STORE_FORMAT_D16_HI_X : formats[input.opcode & 3u], + O::S_ENDPGM}; if (high) test.decoded_counts = {{"BUFFER_STORE_FORMAT_D16_HI_X", 3u}}; cases.push_back(std::move(test)); } @@ -43310,7 +43341,7 @@ std::vector MakeCases() { for (auto &test : BufferLoadFormatD16XCases()) { cases.push_back(std::move(test)); } - for (auto &test : BufferLoadFormatD16XyzwCases()) { + for (auto &test : BufferLoadFormatD16VectorCases()) { cases.push_back(std::move(test)); } for (auto &test : BufferStoreFormatD16Cases()) { @@ -49288,7 +49319,7 @@ int main(int argc, char **argv) { for (const auto &test : BufferLoadFormatD16XCases()) { RunCase(&vulkan, test); } - for (const auto &test : BufferLoadFormatD16XyzwCases()) { + for (const auto &test : BufferLoadFormatD16VectorCases()) { RunCase(&vulkan, test); } for (const auto &test : BufferStoreFormatD16Cases()) { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 4aa2fe1e8..ff6b85241 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -16085,6 +16085,23 @@ void TestCapturedBufferLoadD16XyzwDecode() { "captured D16 XYZW load lost its full destination VGPRs or index"); } +void TestCapturedBufferLoadD16XyDecode() { + namespace Decoder = ShaderRecompiler::Decoder; + const uint32_t code[] = {0xe2042000u, 0x80090600u}; + Decoder::Instruction inst; + Decoder::DecodeInstruction(code, 0, inst); + Check(inst.opcode == Decoder::Opcode::BUFFER_LOAD_FORMAT_D16_XY && + inst.word_count == 2 && inst.data_dwords == 1 && inst.data_bits == 16 && + inst.data_components == 2 && inst.formatted && !inst.typed, + "captured D16 XY load lost its two components in one packed VGPR"); + Check(inst.dst.kind == Decoder::OperandKind::Vgpr && inst.dst.reg == 6 && + inst.dst.sdwa_sel == 6 && + inst.src0.kind == Decoder::OperandKind::Vgpr && inst.src0.reg == 0 && + inst.src1.kind == Decoder::OperandKind::Sgpr && inst.src1.reg == 36 && + inst.idxen && !inst.offen, + "captured D16 XY load lost its destination VGPR or index"); +} + void TestCapturedBufferStoreD16XyzwDecode() { namespace Decoder = ShaderRecompiler::Decoder; const uint32_t code[] = {0xe21c6000u, 0x8002000eu}; @@ -16900,6 +16917,7 @@ int main() { TestCapturedBufferLoadUbyteD16Decode(); TestCapturedBufferStoreShortD16HiDecode(); TestCapturedBufferStoreFormatD16HiXDecode(); + TestCapturedBufferLoadD16XyDecode(); TestCapturedBufferLoadD16XyzwDecode(); TestCapturedBufferStoreD16XyzwDecode(); TestNewShaderRecompilerUnsupportedMemoryDecode();