From 392f39e2f61915c1e11c99e407fce6220e49d174 Mon Sep 17 00:00:00 2001 From: nmzik Date: Thu, 24 Sep 2026 19:07:39 +0400 Subject: [PATCH] shader: support indirect BUFFER_LOAD_DWORDX3 --- src/graphics/shader/recompiler/ir/Program.cpp | 6 ++-- src/graphics/shader/recompiler/ir/ShaderIR.h | 6 ++++ .../recompiler/ir/passes/ResourceTracking.cpp | 5 ++-- tests/ShaderRecompilerComputeTests.cpp | 29 +++++++++++++++---- 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/graphics/shader/recompiler/ir/Program.cpp b/src/graphics/shader/recompiler/ir/Program.cpp index 90c3aee25..1fe47e8f5 100644 --- a/src/graphics/shader/recompiler/ir/Program.cpp +++ b/src/graphics/shader/recompiler/ir/Program.cpp @@ -454,10 +454,8 @@ void ValidateProgram(const Program& program, bool require_ssa) { ValueOpcodeName(inst.GetOpcode()))); } if (memory.kind == ResourceKind::IndirectBuffer && - (memory.formatted || memory.typed || - (inst.GetOpcode() != ValueOpcode::LoadBufferU32x2 && - inst.GetOpcode() != ValueOpcode::LoadBufferU32x4))) { - return Fail("indirect buffer requires a raw DWORD x2/x4 load"); + !memory.SupportsIndirectBufferLoad(inst.GetOpcode())) { + return Fail("indirect buffer requires a raw DWORD x2/x3/x4 load"); } if (buffer_components > 1u && (!vector_buffer || memory.data_bits != 32u || diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index ea024b39a..d52fa99c2 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -69,6 +69,12 @@ struct MemoryInfo { bool offen = false; bool planning_only = false; + [[nodiscard]] bool SupportsIndirectBufferLoad(ValueOpcode opcode) const { + return !formatted && !typed && data_bits == 32u && + (opcode == ValueOpcode::LoadBufferU32x2 || opcode == ValueOpcode::LoadBufferU32x3 || + opcode == ValueOpcode::LoadBufferU32x4); + } + bool operator==(const MemoryInfo& other) const = default; }; diff --git a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp index 1e613f9a6..809b16050 100644 --- a/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp +++ b/src/graphics/shader/recompiler/ir/passes/ResourceTracking.cpp @@ -1206,11 +1206,10 @@ private: if (buffer != BufferAccess::None) { if (!GetHandle(inst.Arg(0), ValueOpcode::GetBufferResource, 4, flags.pc, handle, source)) { - if (memory.kind != ResourceKind::Buffer || memory.formatted || memory.typed || - (op != ValueOpcode::LoadBufferU32x2 && op != ValueOpcode::LoadBufferU32x4)) { + if (memory.kind != ResourceKind::Buffer || !memory.SupportsIndirectBufferLoad(op)) { Fail(flags.pc, "buffer descriptor is not a valid runtime value; GPU-selected access " - "requires a raw DWORD x2/x4 load"); + "requires a raw DWORD x2/x3/x4 load"); } m_program.memory_info[flags.index].kind = ResourceKind::IndirectBuffer; m_info.uses_dma = true; diff --git a/tests/ShaderRecompilerComputeTests.cpp b/tests/ShaderRecompilerComputeTests.cpp index 84367e170..d6e3e1725 100644 --- a/tests/ShaderRecompilerComputeTests.cpp +++ b/tests/ShaderRecompilerComputeTests.cpp @@ -23196,7 +23196,7 @@ TestCase BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail() { return test; } -TestCase BufferLoadsGpuSelectedDescriptors() { +TestCase BufferLoadsGpuSelectedDescriptors(bool xyz) { using O = ShaderOpcode; constexpr uint64_t GuestBase = 0x0000000110000000ull; struct DescriptorCase { @@ -23206,6 +23206,8 @@ TestCase BufferLoadsGpuSelectedDescriptors() { }; const DescriptorCase cases[] = { {24, 2, 0, 0, false, true, {7, 8, 9, 10, 11, 12}}, + // PPSA01417 selects XYZ vertices from a table of stride-16 descriptors. + {16, 2, 0, 0, false, true, {5, 6, 7, 8, 0, 0}}, {12, 2, 0, 0, false, true, {4, 5, 6, 0, 0, 0}}, {12, 2, 1, 0, false, true, {4, 5, 6, 7, 8, 9}}, {12, 1, 2, 0, false, true, {4, 5, 6, 7, 8, 9}}, @@ -23219,7 +23221,8 @@ TestCase BufferLoadsGpuSelectedDescriptors() { {12, 4, 3, 4, true, true, {}}, }; TestCase test; - test.name = "BufferLoadsGpuSelectedDescriptors"; + test.name = xyz ? "BufferLoadDwordx3GpuSelectedDescriptors" + : "BufferLoadsGpuSelectedDescriptors"; test.initial.resize(2048); for (u32 i = 0; i < std::size(cases); ++i) { const auto &input = cases[i]; @@ -23245,10 +23248,10 @@ TestCase BufferLoadsGpuSelectedDescriptors() { test.code.push_back(EncodeSmem1(520, 20)); AppendSMovLiteral(&test.code, 22, cases[selected].soffset); AppendVMovU32(&test.code, 21, 1); - test.code.push_back(EncodeMubuf0(0x0e, 0, true, false)); + test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0e, 0, true, false)); test.code.push_back(EncodeMubuf1(0, 2, 21, 22)); - test.code.push_back(EncodeMubuf0(0x0d, 16, true, false)); - test.code.push_back(EncodeMubuf1(4, 2, 21, 22)); + test.code.push_back(EncodeMubuf0(xyz ? 0x0f : 0x0d, xyz ? 12 : 16, true, false)); + test.code.push_back(EncodeMubuf1(xyz ? 3 : 4, 2, 21, 22)); for (u32 component = 0; component < 6; ++component) { AppendStoreVgpr(&test.code, component, i * 6 + component); const u32 expected = cases[selected].expected[component]; @@ -23259,12 +23262,24 @@ TestCase BufferLoadsGpuSelectedDescriptors() { test.bda_mappings = {{GuestBase, 0}}; test.opcodes = {O::V_MOV_B32, O::S_MOV_B32, O::BUFFER_LOAD_DWORD, O::V_READFIRSTLANE_B32, O::S_MUL_I32, O::S_BUFFER_LOAD_DWORDX4, - O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2, O::BUFFER_STORE_DWORD, O::S_ENDPGM}; + if (xyz) { + test.opcodes.push_back(O::BUFFER_LOAD_DWORDX3); + } else { + test.opcodes.insert(test.opcodes.end(), {O::BUFFER_LOAD_DWORDX4, O::BUFFER_LOAD_DWORDX2}); + } test.required_spirv = {"OpConvertUToPtr", "PhysicalStorageBuffer"}; return test; } +TestCase BufferLoadsGpuSelectedDescriptors() { + return BufferLoadsGpuSelectedDescriptors(false); +} + +TestCase BufferLoadDwordx3GpuSelectedDescriptors() { + return BufferLoadsGpuSelectedDescriptors(true); +} + TestCase BufferStoreDwordx4DropsOnlyOutOfBoundsTail() { using O = ShaderOpcode; @@ -28479,6 +28494,7 @@ std::vector MakeCases() { AddCase(BufferLoadDwordx4SnapshotsOverlappingAddress); AddCase(BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail); AddCase(BufferLoadsGpuSelectedDescriptors); + AddCase(BufferLoadDwordx3GpuSelectedDescriptors); AddCase(BufferStoreDwordx4DropsOnlyOutOfBoundsTail); AddCase(BufferLoadFormatXyzwRejectsPartialRecord); AddCase(BufferStoreFormatXyzwDropsPartialRecord); @@ -33307,6 +33323,7 @@ int main(int argc, char **argv) { if (argc == 2 && std::strcmp(argv[1], "--indirect-buffer-only") == 0) { VulkanHarness vulkan; RunCase(&vulkan, BufferLoadsGpuSelectedDescriptors()); + RunCase(&vulkan, BufferLoadDwordx3GpuSelectedDescriptors()); RunCase(&vulkan, BufferLoadDwordx4SnapshotsOverlappingAddress()); RunCase(&vulkan, BufferLoadDwordx4ZeroesOnlyOutOfBoundsTail()); RunCase(&vulkan, BufferLoadDwordIdxenUsesDescriptorStride());