From 7c9148dd4775702d761e7f557f41c3515126ce41 Mon Sep 17 00:00:00 2001 From: nmzik Date: Wed, 16 Sep 2026 08:42:47 +0400 Subject: [PATCH] Support triangle fans in PS5 geometry shaders Keep the fan center across subgroups and reuse the existing index fetch. --- .../frontend/translate/Translate.cpp | 34 +++++++++++-------- src/graphics/shader/shader.cpp | 1 + src/graphics/shader/shader.h | 7 ++-- tests/shaderCfgTests.cpp | 21 ++++++++++++ 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index 338a593e1..81c401be5 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1102,26 +1102,32 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& entry_ir.BitwiseOr(wave_info, entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical( primitive_count, u32(8)), vertex_count))); - // GS adjacency addresses local ES records in LDS. Strip winding alternates - // with the global primitive number, including across subgroup boundaries. - const auto parity = mesh.input_primitive == - static_cast(Prospero::PrimitiveType::kTriStrip) - ? entry_ir.BitwiseAnd(entry_ir.IAdd(primitive_chunk, local), u32(1)) - : u32(0); + // GS adjacency addresses local ES records in LDS. Fans retain the draw's + // center in every subgroup; strip winding follows the global primitive. const auto vertex = entry_ir.IMul(local, step); - const auto first = entry_ir.IAdd(vertex, parity); - const auto second = mesh.InputPrimitiveSize() >= 2u - ? entry_ir.ISub(entry_ir.IAdd(vertex, u32(1)), parity) - : u32(0); - const auto third = mesh.InputPrimitiveSize() == 3u - ? entry_ir.IAdd(vertex, u32(2)) - : u32(0); + auto first = vertex; + auto second = u32(0); + auto third = u32(0); + if (mesh.InputPrimitiveSize() >= 2u) { + second = entry_ir.IAdd(vertex, u32(1)); + } + if (mesh.InputPrimitiveSize() == 3u) { + third = entry_ir.IAdd(vertex, u32(2)); + } + auto input_vertex = entry_ir.IAdd(chunk, local); + if (mesh.input_primitive == static_cast(Prospero::PrimitiveType::kTriFan)) { + first = u32(0); + input_vertex = entry_ir.Select(entry_ir.IEqual(local, u32(0)), u32(0), input_vertex); + } else if (mesh.input_primitive == static_cast(Prospero::PrimitiveType::kTriStrip)) { + const auto parity = entry_ir.BitwiseAnd(entry_ir.IAdd(primitive_chunk, local), u32(1)); + first = entry_ir.IAdd(first, parity); + second = entry_ir.ISub(second, parity); + } entry_ir.SetVectorReg(static_cast(0), entry_ir.BitwiseOr(entry_ir.ShiftLeftLogical(first, u32(2)), entry_ir.ShiftLeftLogical(second, u32(18)))); entry_ir.SetVectorReg(static_cast(1), entry_ir.ShiftLeftLogical(third, u32(2))); - const auto input_vertex = entry_ir.IAdd(chunk, local); const auto index_bytes = draw(3); const auto indexed = entry_ir.INotEqual(index_bytes, u32(0)); const auto index_low = draw(4); diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 79a04b3d0..9de979230 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -797,6 +797,7 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& const auto& group = user_config.GetGeControl(); if ((user_config.GetPrimType() != Prospero::PrimitiveType::kPointList && user_config.GetPrimType() != Prospero::PrimitiveType::kLineList && + user_config.GetPrimType() != Prospero::PrimitiveType::kTriFan && user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip && user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) || sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u || diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 880b2707e..7ec2e2b95 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -97,8 +97,11 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo { } } [[nodiscard]] constexpr uint32_t InputPrimitiveStep() const { - return input_primitive == static_cast(Prospero::PrimitiveType::kTriStrip) - ? 1u : InputPrimitiveSize(); + switch (static_cast(input_primitive)) { + case Prospero::PrimitiveType::kTriFan: + case Prospero::PrimitiveType::kTriStrip: return 1u; + default: return InputPrimitiveSize(); + } } [[nodiscard]] constexpr uint32_t InputPrimitiveCount(uint32_t vertices) const { const auto size = InputPrimitiveSize(); diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index 096362e07..f374e8e01 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -9606,6 +9606,15 @@ void TestMergedShaderUserDataSnapshot() { params.user_data.begin() + 8), "monolithic NGG shader used stale GS-back state or lost its s8 user data"); } + context.SetMaxOutputPerSubgroup(256); + context.SetGsMaxVertOut(8); + user_config.SetPrimitiveType(Prospero::PrimitiveType::kTriFan); + user_config.SetGeControl({32, 32}); + ShaderVertexInputInfo fan_input{}; + PrepareProgram(regs, context, user_config, fan_input); + Check(fan_input.mesh.primitives_per_group == 30 && fan_input.mesh.vertices_per_group == 32 && + fan_input.mesh.max_vertices == 256 && fan_input.mesh.max_primitives == 192, + "captured triangle-fan GS configuration lost its subgroup assembly limits"); } void TestEmbeddedFetchPreservesSharedScalarLoad() { @@ -9791,6 +9800,18 @@ void TestMeshInputAssembly() { 0x40000305, 1, 2, 3, 0, 15, false}, {Prospero::PrimitiveType::kTriStrip, 5, 8, 0, 1, 0, 0, 11, 0x40000305, 2, 1, 3, 0, 12, false}, + {Prospero::PrimitiveType::kTriFan, 32, 6, 0, 3, 2, 0x1002, 0, + 0x40000406, 0, 4, 5, 8, 0x0123, true}, + {Prospero::PrimitiveType::kTriFan, 5, 8, 1, 0, 2, 0x1002, 0, + 0x40000305, 0, 1, 2, 0, 0xabcd, true}, + {Prospero::PrimitiveType::kTriFan, 5, 8, 1, 1, 0, 0, 11, + 0x40000305, 0, 2, 3, 0, 15, false}, + {Prospero::PrimitiveType::kTriFan, 5, 7, 1, 3, 2, 0x1002, 11, + 0x40000204, 0, 4, 5, 12, 0xabd8, true}, + {Prospero::PrimitiveType::kTriFan, 5, 7, 1, 4, 2, 0x1002, 0, + 0x40000204, 0, 5, 6, 16, 0, false}, + {Prospero::PrimitiveType::kTriFan, 5, 8, 1, 64, 4, 0x1000, 0, + 0x41000000, 0, 65, 66, 268, 0, false}, {Prospero::PrimitiveType::kLineList, 5, 17, 3, 0, 0, 0, 11, 0x40000204, 0, 1, 0, 0, 23, false}, {Prospero::PrimitiveType::kLineList, 6, 17, 2, 4, 2, 0x1002, 11,