diff --git a/src/graphics/host_gpu/renderer/renderDraw.cpp b/src/graphics/host_gpu/renderer/renderDraw.cpp index 195044ded..3b2c89d64 100644 --- a/src/graphics/host_gpu/renderer/renderDraw.cpp +++ b/src/graphics/host_gpu/renderer/renderDraw.cpp @@ -713,6 +713,7 @@ struct DrawIndexBufferSource { const void* host_data = nullptr; uint64_t size = 0; vk::IndexType type = vk::IndexType::eUint16; + uint32_t guest_element_size = 0; }; struct PreparedIndexBuffer { @@ -1129,15 +1130,15 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff uint32_t mesh_groups = 0; if (mesh_active) { const auto& mesh = state.vs_input_info.mesh; - if (emit.indexed || ucfg.GetPrimType() != Prospero::PrimitiveType::kTriStrip || - primitive_restart_enable || mesh.primitives_per_group == 0) { + if (primitive_restart_enable || mesh.primitives_per_group == 0) { EXIT("unsupported mesh draw: primitive=%u indexed=%u restart=%u\n", static_cast(ucfg.GetPrimType()), emit.indexed, primitive_restart_enable); } - if (draw.index_count < 3 || draw.instance_count == 0) { + const auto primitives = mesh.InputPrimitiveCount(draw.index_count); + if (primitives == 0 || draw.instance_count == 0) { return; } - mesh_groups = (draw.index_count - 3u) / mesh.primitives_per_group + 1u; + mesh_groups = (primitives - 1u) / mesh.primitives_per_group + 1u; const auto& limits = m_context.GetGraphics().mesh_shader_properties; if (mesh_groups > limits.maxMeshWorkGroupCount[0] || draw.instance_count > limits.maxMeshWorkGroupCount[1] || @@ -1148,6 +1149,13 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff } } + if (mesh_active && emit.indexed) { + // Register the original guest indices for shader reads; PrepareGraphicsBindings + // synchronizes registered BDA ranges before any draw commands are committed. + (void)m_context.GetBufferCache().FindBuffer( + index_source.address, static_cast(draw.index_count) * + index_source.guest_element_size); + } LogDrawPhase(draw.name, "PrepareBindings"); auto bindings = PrepareGraphicsBindings(state.vs_input_info.stage, state.ps_input_info.stage, state.ps_active); @@ -1195,7 +1203,12 @@ void RenderExecutor::ExecutePreparedDraw(uint64_t submit_id, CommandBuffer& buff CommitBindings(buffer, vk::PipelineBindPoint::eGraphics, pipeline, std::span {descriptor_stages.data(), descriptor_stage_count}); if (mesh_active) { - const uint32_t draw_data[] {draw.index_count, emit.first_vertex, emit.first_instance}; + const uint32_t draw_data[] { + draw.index_count, + emit.indexed ? static_cast(emit.vertex_offset) : emit.first_vertex, + emit.first_instance, index_source.guest_element_size, + static_cast(index_source.address), + static_cast(index_source.address >> 32u)}; static_assert(std::size(draw_data) == ShaderRecompiler::IR::PushData::MeshDrawDwordCount); vk_buffer.pushConstants(pipeline.pipeline_layout, vk::ShaderStageFlagBits::eMeshEXT | @@ -1342,6 +1355,7 @@ void RenderExecutor::DrawIndex(uint64_t submit_id, CommandBuffer& buffer, index_source.size = expanded_indices.empty() ? index_size : expanded_indices.size() * sizeof(uint16_t); index_source.type = index_type; + index_source.guest_element_size = static_cast(index_size / args.index_count); DrawRenderState state {}; if (!PrepareDrawRenderState(submit_id, buffer, draw, args.render_target_slice_offset, true, diff --git a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp index b25376de1..15b44dac4 100644 --- a/src/graphics/shader/recompiler/frontend/translate/Translate.cpp +++ b/src/graphics/shader/recompiler/frontend/translate/Translate.cpp @@ -1079,11 +1079,17 @@ IR::Program TranslateProgram(const Decoder::Program& decoded, const CFG::Graph& return entry_ir.ISub(lhs, minimum(lhs, rhs)); }; const auto local = builtin(IR::StageInputKind::LocalInvocationIndex); - const auto chunk = entry_ir.IMul(builtin(IR::StageInputKind::WorkgroupId, 0), - u32(mesh.primitives_per_group)); + const auto primitive_chunk = entry_ir.IMul(builtin(IR::StageInputKind::WorkgroupId, 0), + u32(mesh.primitives_per_group)); + const auto step = u32(mesh.InputPrimitiveStep()); + const auto chunk = entry_ir.IMul(primitive_chunk, step); const auto vertices = minimum(subtract_saturate(draw(0), chunk), u32(mesh.vertices_per_group)); - const auto primitives = subtract_saturate(vertices, u32(2)); + const auto primitives = entry_ir.Select( + entry_ir.ULessThan(vertices, u32(3)), u32(0), + entry_ir.IAdd(IR::U32(entry_ir.Emit(IR::ValueOpcode::UDiv32, + {subtract_saturate(vertices, u32(3)), step})), + u32(1))); const auto wave = entry_ir.ShiftRightLogical(local, u32(6)); const auto wave_base = entry_ir.BitwiseAnd(local, u32(~63u)); const auto vertex_count = minimum(subtract_saturate(vertices, wave_base), u32(64)); @@ -1095,18 +1101,41 @@ 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))); - // Triangle-strip adjacency addresses the ES records in LDS. Alternating the - // first two indices preserves winding across subgroup boundaries. - const auto parity = entry_ir.BitwiseAnd(entry_ir.IAdd(chunk, local), u32(1)); - const auto first = entry_ir.IAdd(local, parity); - const auto second = entry_ir.ISub(entry_ir.IAdd(local, u32(1)), parity); + // GS adjacency addresses local ES records in LDS. Strip winding alternates + // with the global primitive number, including across subgroup boundaries. + const auto parity = mesh.InputPrimitiveStep() == 1u + ? entry_ir.BitwiseAnd(entry_ir.IAdd(primitive_chunk, local), u32(1)) + : u32(0); + const auto vertex = entry_ir.IMul(local, step); + const auto first = entry_ir.IAdd(vertex, parity); + const auto second = entry_ir.ISub(entry_ir.IAdd(vertex, u32(1)), 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(entry_ir.IAdd(local, u32(2)), u32(2))); + entry_ir.ShiftLeftLogical(entry_ir.IAdd(vertex, u32(2)), 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); + const auto byte_offset = entry_ir.IAdd(entry_ir.BitwiseAnd(index_low, u32(3)), + entry_ir.IMul(input_vertex, index_bytes)); + const auto index_resource = entry_ir.Emit( + IR::ValueOpcode::GetAddressResource, + {entry_ir.BitwiseAnd(index_low, u32(~3u)), draw(5)}); + const auto memory_index = static_cast(result.memory_info.size()); + result.memory_info.push_back({.kind = IR::ResourceKind::Global}); + const auto packed_index = entry_ir.Emit( + IR::ValueOpcode::LoadAddressU32, + {index_resource, entry_ir.BitwiseAnd(byte_offset, u32(~3u)), u32(0), + entry_ir.LogicalAnd(indexed, entry_ir.ULessThan(local, vertices))}, + IR::MemoryFlags {.index = memory_index}); + const auto index = IR::U32(entry_ir.Emit( + IR::ValueOpcode::BitFieldUExtract, + {packed_index, entry_ir.IMul(entry_ir.BitwiseAnd(byte_offset, u32(3)), u32(8)), + entry_ir.IMul(index_bytes, u32(8))})); entry_ir.SetVectorReg(static_cast(5), - entry_ir.IAdd(draw(1), entry_ir.IAdd(chunk, local))); + entry_ir.IAdd(draw(1), entry_ir.Select(indexed, index, input_vertex))); entry_ir.SetVectorReg( static_cast(8), entry_ir.IAdd(draw(2), builtin(IR::StageInputKind::WorkgroupId, 1))); diff --git a/src/graphics/shader/recompiler/ir/ShaderIR.h b/src/graphics/shader/recompiler/ir/ShaderIR.h index 969d2206b..60d90bacb 100644 --- a/src/graphics/shader/recompiler/ir/ShaderIR.h +++ b/src/graphics/shader/recompiler/ir/ShaderIR.h @@ -273,7 +273,7 @@ static_assert(static_cast(DescriptorBindingKind::Count) == 43u); struct PushData { static constexpr uint32_t DwordCount = 32; - static constexpr uint32_t MeshDrawDwordCount = 3; + static constexpr uint32_t MeshDrawDwordCount = 6; static constexpr uint32_t NoStart = UINT32_MAX; std::array dwords {}; diff --git a/src/graphics/shader/shader.cpp b/src/graphics/shader/shader.cpp index 8779772be..91382e35b 100644 --- a/src/graphics/shader/shader.cpp +++ b/src/graphics/shader/shader.cpp @@ -878,7 +878,8 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& EXIT_NOT_IMPLEMENTED(regs.gs_regs.rsrc1.gs_vgpr_component_count != 3u || regs.gs_regs.rsrc2.es_vgpr_component_count != 3u); const auto& group = user_config.GetGeControl(); - if (user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip || + if ((user_config.GetPrimType() != Prospero::PrimitiveType::kTriStrip && + user_config.GetPrimType() != Prospero::PrimitiveType::kTriList) || sh.m_vgtGsOutPrimType != 2u || sh.m_vgtGsMaxVertOut < 3u || group.vertex_group_size < 3u || mesh.max_vertices == 0u) { EXIT("unsupported GS assembly: input=%u output=%u vertices=%u GE=%u/%u max_output=%u\n", @@ -887,10 +888,10 @@ ShaderParams PrepareProgram(const HW::VertexShaderInfo& regs, const HW::Context& } mesh.max_primitives = group.primitive_group_size * (sh.m_vgtGsMaxVertOut - 2u); mesh.primitives_per_group = std::min({static_cast(group.primitive_group_size), - static_cast(group.vertex_group_size) - 2u, + mesh.InputPrimitiveCount(group.vertex_group_size), mesh.max_vertices / sh.m_vgtGsMaxVertOut}); EXIT_IF(mesh.primitives_per_group == 0u); - mesh.vertices_per_group = mesh.primitives_per_group + 2u; + mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group); mesh.threads_num[0] = ((mesh.max_vertices + mesh.wave_size - 1u) / mesh.wave_size) * mesh.wave_size; mesh.threads_num[1] = mesh.threads_num[2] = 1u; diff --git a/src/graphics/shader/shader.h b/src/graphics/shader/shader.h index 6a72d33de..78cd98c21 100644 --- a/src/graphics/shader/shader.h +++ b/src/graphics/shader/shader.h @@ -78,6 +78,16 @@ struct ShaderMeshInputInfo: ShaderWorkgroupInputInfo { uint32_t max_vertices = 0; uint32_t max_primitives = 0; uint32_t provoking_vertex = 0; + + [[nodiscard]] constexpr uint32_t InputPrimitiveStep() const { + return input_primitive == static_cast(Prospero::PrimitiveType::kTriList) ? 3u : 1u; + } + [[nodiscard]] constexpr uint32_t InputPrimitiveCount(uint32_t vertices) const { + return vertices < 3u ? 0u : (vertices - 3u) / InputPrimitiveStep() + 1u; + } + [[nodiscard]] constexpr uint32_t InputVertexCount(uint32_t primitives) const { + return primitives == 0u ? 0u : (primitives - 1u) * InputPrimitiveStep() + 3u; + } }; struct ShaderVertexInputInfo { diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index ce0cb7a88..f05eee287 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -9054,6 +9054,95 @@ void TestFusedShaderHandoffPreservesRegisters() { Check(allocations == 1u, "fused shader omitted the back shader allocation"); } +void TestMeshInputAssembly() { + using namespace ShaderRecompiler; + using namespace ShaderRecompiler::IR; + struct Case { + Prospero::PrimitiveType topology; + uint32_t capacity, count, group, lane, width, address_low, base_vertex; + uint32_t wave_info, first, second, third, byte_offset, vertex_id; + bool fetch; + }; + const Case cases[] = { + {Prospero::PrimitiveType::kTriList, 14, 177, 14, 2, 2, 0x1002, UINT32_MAX, + 0x40000309, 6, 7, 8, 340, 0xabcc, true}, + {Prospero::PrimitiveType::kTriList, 14, 177, 14, 9, 2, 0x1002, 0, + 0x40000309, 27, 28, 29, 356, 0, false}, + {Prospero::PrimitiveType::kTriList, 8, 180, 0, 64, 2, 0x1002, 0, + 0x41000000, 192, 193, 194, 128, 0, false}, + {Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 1, 0x1000, 5, + 0x40000206, 3, 4, 5, 4, 0xb0, true}, + {Prospero::PrimitiveType::kTriList, 8, 180, 1, 1, 4, 0x1000, 5, + 0x40000206, 3, 4, 5, 28, 0xabcd0128, true}, + {Prospero::PrimitiveType::kTriStrip, 5, 8, 1, 1, 0, 0, 11, + 0x40000305, 1, 2, 3, 0, 15, false}, + {Prospero::PrimitiveType::kTriStrip, 5, 8, 0, 1, 0, 0, 11, + 0x40000305, 2, 1, 3, 0, 12, false}, + }; + for (const auto &test : cases) { + ShaderVertexInputInfo input{}; + auto &mesh = input.mesh; + mesh.input_primitive = static_cast(test.topology); + mesh.primitives_per_group = mesh.InputPrimitiveCount(test.capacity); + mesh.vertices_per_group = mesh.InputVertexCount(mesh.primitives_per_group); + mesh.threads_num[0] = 256; + mesh.threads_num[1] = mesh.threads_num[2] = 1; + Decoder::Program decoded; + CFG::Graph graph; + CFG::BasicBlock block; + block.id = 0; + block.terminator.kind = CFG::TerminatorKind::Return; + graph.blocks.push_back(std::move(block)); + graph.entry_block = 0; + Frontend::TranslateOptions options{}; + options.stage = ShaderType::Mesh; + options.wave_size = 64; + options.user_data_count = 0; + options.vertex = &input; + auto program = Frontend::TranslateProgram(decoded, graph, options); + const uint32_t draw[] = {test.count, test.base_vertex, 7, test.width, + test.address_low, 0x12}; + Inst *load = nullptr; + for (auto &inst : *program.blocks.front()) { + if (inst.GetOpcode() == ValueOpcode::MeshDrawParameter) { + inst.ReplaceUsesWith(Value(draw[inst.Arg(0).U32()])); + } else if (inst.GetOpcode() == ValueOpcode::GetBuiltin) { + const auto kind = static_cast(inst.Arg(0).U32()); + const uint32_t value = kind == StageInputKind::LocalInvocationIndex + ? test.lane + : inst.Arg(1).U32() == 0 ? test.group : 2; + inst.ReplaceUsesWith(Value(value)); + } else if (inst.GetOpcode() == ValueOpcode::LoadAddressU32) { + Check(load == nullptr, "mesh index fetch emitted duplicate loads"); + load = &inst; + } + } + ConstantPropagationPass(program.blocks); + Check(load != nullptr && load->Arg(1).Resolve().U32() == test.byte_offset && + load->Arg(3).Resolve().U1() == test.fetch, + "mesh index fetch address or active-lane predicate is wrong"); + const auto *resource = load->Arg(0).ResolveInstruction(); + Check(resource != nullptr && resource->Arg(0).Resolve().U32() == (test.address_low & ~3u) && + resource->Arg(1).Resolve().U32() == 0x12 && + program.memory_info[load->Flags().index].kind == ResourceKind::Global, + "mesh index fetch lost its aligned guest address resource"); + load->ReplaceUsesWith(Value(test.fetch ? 0xabcd0123u : 0u)); + ConstantPropagationPass(program.blocks); + std::array vgprs{}; + uint32_t sgpr3 = 0; + for (const auto &inst : *program.blocks.front()) { + if (inst.GetOpcode() == ValueOpcode::SetVectorRegister) { + vgprs[RegIndex(inst.Arg(0).VectorRegister())] = inst.Arg(1).Resolve().U32(); + } else if (inst.GetOpcode() == ValueOpcode::SetScalarRegister) { + sgpr3 = inst.Arg(1).Resolve().U32(); + } + } + Check(sgpr3 == test.wave_info && vgprs[0] == ((test.first << 2) | (test.second << 18)) && + vgprs[1] == test.third * 4 && vgprs[5] == test.vertex_id && vgprs[8] == 9, + "mesh prolog changed triangle assembly, wave counts, vertex ID, or instance ID"); + } +} + void TestNewShaderRecompilerSetpcJumpTable() { const uint32_t shader[] = { EncodeSop2(0x07, 0, 0, 129), // s_min_u32 s0, s0, 1 @@ -12317,6 +12406,7 @@ int main() { TestNewShaderRecompilerBranchConditionForms(); TestNewShaderRecompilerSetpcBranch(); TestFusedShaderHandoffPreservesRegisters(); + TestMeshInputAssembly(); TestNewShaderRecompilerSetpcJumpTable(); TestNewShaderRecompilerPrunesUnreachableSetpcMetadata(); TestNewShaderRecompilerSetpcDwordJumpTable();