From 39248a1f87bbe41e3954f2dc84d9817b93b6c947 Mon Sep 17 00:00:00 2001 From: nmzik Date: Sun, 4 Oct 2026 17:25:44 +0400 Subject: [PATCH] shader: emit per-vertex mesh clip and cull distances Co-authored-by: Senaxx Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com> --- .../backend/spirv/spirvEmitterFlow.cpp | 19 +++-- .../backend/spirv/spirvEmitterInternal.h | 3 +- .../backend/spirv/spirvEmitterMesh.cpp | 77 ++++++++++++------- .../backend/spirv/spirvEmitterModule.cpp | 2 +- tests/shaderCfgTests.cpp | 19 ++++- 5 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp index a7b167e47..09d22a699 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterFlow.cpp @@ -366,18 +366,23 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor state.builder.AddFunction(spv::OpStore, state.point_size_variable, f32); continue; } - auto StoreDistance = [&](uint32_t variable, uint32_t index) { + auto StoreDistance = [&](IR::StageOutputKind kind, uint32_t variable, uint32_t index) { if (index == UINT32_MAX) { return; } - const auto pointer = state.builder.AllocateId(); - state.builder.AddFunction(spv::OpAccessChain, - TypePointer(state, spv::StorageClassOutput, TypeF32(state)), - pointer, variable, ConstantU32(state, index)); + uint32_t pointer; + if (state.program.stage == ShaderType::Mesh) { + pointer = MeshOutputPointer(state, kind, index); + } else { + pointer = state.builder.AllocateId(); + state.builder.AddFunction(spv::OpAccessChain, + TypePointer(state, spv::StorageClassOutput, TypeF32(state)), + pointer, variable, ConstantU32(state, index)); + } state.builder.AddFunction(spv::OpStore, pointer, f32); }; - StoreDistance(state.clip_distance_variable, output.clip_distance); - StoreDistance(state.cull_distance_variable, output.cull_distance); + StoreDistance(IR::StageOutputKind::ClipDistance, state.clip_distance_variable, output.clip_distance); + StoreDistance(IR::StageOutputKind::CullDistance, state.cull_distance_variable, output.cull_distance); } } diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h index 424a0cb44..91c155f20 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterInternal.h @@ -354,7 +354,8 @@ uint32_t DefineInterfaceVariable(EmitterState& state, uint32_t type, spv::Storag void DefineModule(EmitterState& state); void DefineTessellationInterfaces(EmitterState& state); void DefineTessellationExecutionModes(EmitterState& state); -void DefineMeshOutputs(EmitterState& state); +void DefineMeshOutputs(EmitterState& state, uint32_t clip_distance_count, + uint32_t cull_distance_count); void EmitMeshEntryPoint(EmitterState& state); void EmitMeshAllocate(ValueEmitContext& ctx, const IR::Inst& inst); uint32_t MeshOutputPointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index = 0); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMesh.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMesh.cpp index e125f51c8..2c8f210b2 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMesh.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterMesh.cpp @@ -25,43 +25,58 @@ uint32_t MeshLoad(EmitterState& state, uint32_t variable, spv::StorageClass stor } uint32_t MeshOutputType(EmitterState& state, IR::StageOutputKind kind) { - return kind == IR::StageOutputKind::Layer ? TypeU32(state) : TypeF32Vector(state, 4); + switch (kind) { + case IR::StageOutputKind::Layer: return TypeU32(state); + case IR::StageOutputKind::ClipDistance: + case IR::StageOutputKind::CullDistance: return TypeF32(state); + case IR::StageOutputKind::Position: + case IR::StageOutputKind::Parameter: return TypeF32Vector(state, 4); + default: EXIT("unsupported mesh output kind=%u\n", static_cast(kind)); + } } } // namespace -void DefineMeshOutputs(EmitterState& state) { +void DefineMeshOutputs(EmitterState& state, uint32_t clip_distance_count, + uint32_t cull_distance_count) { const auto& mesh = state.input_info.vertex->mesh; for (auto& output: state.outputs) { - if (output.kind != IR::StageOutputKind::Position && - output.kind != IR::StageOutputKind::Parameter && - output.kind != IR::StageOutputKind::Layer) { - EXIT("unsupported mesh output kind=%u\n", static_cast(output.kind)); + const auto type = MeshOutputType(state, output.kind); + const bool clip = output.kind == IR::StageOutputKind::ClipDistance; + const bool cull = output.kind == IR::StageOutputKind::CullDistance; + auto& variable = clip ? state.clip_distance_variable + : cull ? state.cull_distance_variable : output.variable_id; + if (variable == 0) { + const auto element_type = clip || cull + ? state.builder.Type(spv::OpTypeArray, type, + ConstantU32(state, clip ? clip_distance_count + : cull_distance_count)) + : type; + variable = MeshArray(state, spv::StorageClassOutput, element_type, + output.kind == IR::StageOutputKind::Layer ? mesh.max_primitives + : mesh.max_vertices); + state.interface_variables.push_back(variable); + state.builder.AddName(variable, output.debug_name.c_str()); + if (output.kind == IR::StageOutputKind::Parameter) { + state.builder.AddAnnotation(spv::OpDecorate, variable, + spv::DecorationLocation, output.location); + } else { + const auto builtin = clip ? spv::BuiltInClipDistance + : cull ? spv::BuiltInCullDistance + : output.kind == IR::StageOutputKind::Layer ? spv::BuiltInLayer + : spv::BuiltInPosition; + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationBuiltIn, builtin); + } + if (output.kind == IR::StageOutputKind::Layer) { + state.builder.AddAnnotation(spv::OpDecorate, variable, spv::DecorationPerPrimitiveEXT); + } } - const auto type = MeshOutputType(state, output.kind); - output.variable_id = MeshArray( - state, spv::StorageClassOutput, type, - output.kind == IR::StageOutputKind::Layer ? mesh.max_primitives : mesh.max_vertices); + output.variable_id = variable; // Only Layer is read by another invocation, through the primitive's provoking vertex. const bool shared = output.kind == IR::StageOutputKind::Layer; output.mesh_data_variable = MeshArray(state, shared ? spv::StorageClassWorkgroup : spv::StorageClassPrivate, type, shared ? mesh.max_vertices : state.lane_count); - state.interface_variables.push_back(output.variable_id); - state.builder.AddName(output.variable_id, output.debug_name.c_str()); - if (output.kind == IR::StageOutputKind::Parameter) { - state.builder.AddAnnotation(spv::OpDecorate, output.variable_id, - spv::DecorationLocation, output.location); - } else { - state.builder.AddAnnotation(spv::OpDecorate, output.variable_id, spv::DecorationBuiltIn, - output.kind == IR::StageOutputKind::Layer - ? spv::BuiltInLayer - : spv::BuiltInPosition); - } - if (output.kind == IR::StageOutputKind::Layer) { - state.builder.AddAnnotation(spv::OpDecorate, output.variable_id, - spv::DecorationPerPrimitiveEXT); // PerPrimitiveEXT - } } state.mesh_allocation = MeshArray(state, spv::StorageClassWorkgroup, TypeU32(state), 2); state.mesh_primitive_data = @@ -148,8 +163,16 @@ void EmitMeshEntryPoint(EmitterState& state) { const auto value = MeshLoad(state, output.mesh_data_variable, spv::StorageClassPrivate, type, ConstantU32(state, half)); - const auto pointer = - MeshElement(state, output.variable_id, spv::StorageClassOutput, type, index); + uint32_t pointer; + if (output.kind == IR::StageOutputKind::ClipDistance || + output.kind == IR::StageOutputKind::CullDistance) { + pointer = state.builder.AllocateId(); + state.builder.AddFunction( + spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, type), pointer, + output.variable_id, index, ConstantU32(state, output.index)); + } else { + pointer = MeshElement(state, output.variable_id, spv::StorageClassOutput, type, index); + } state.builder.AddFunction(spv::OpStore, pointer, value); } }); diff --git a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp index 287912f84..0b9ce2602 100644 --- a/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp +++ b/src/graphics/shader/recompiler/backend/spirv/spirvEmitterModule.cpp @@ -535,7 +535,7 @@ void DefineOutputs(EmitterState& state) { } } if (state.program.stage == ShaderType::Mesh) { - DefineMeshOutputs(state); + DefineMeshOutputs(state, clip_distance_count, cull_distance_count); return; } if (state.program.stage == ShaderType::Vertex && clip_distance_count + cull_distance_count < 8u && diff --git a/tests/shaderCfgTests.cpp b/tests/shaderCfgTests.cpp index e0429935e..b4aea2fdf 100644 --- a/tests/shaderCfgTests.cpp +++ b/tests/shaderCfgTests.cpp @@ -10204,13 +10204,15 @@ void TestMeshExportStorage() { EncodeExp0(0x21, 0xf, false), EncodeExp1(0, 0, 0, 0), EncodeExp0(0x22, 0xf, false), EncodeExp1(0, 0, 0, 0), EncodeExp0(0x0d, 0x4, false), EncodeExp1(0, 0, 0, 0), // Layer + EncodeExp0(0x0e, 0x9, false), EncodeExp1(0, 0, 0, 0), // Clip distances 0, 3 + EncodeExp0(0x0f, 0x6, false), EncodeExp1(0, 0, 0, 0), // Cull distances 1, 2 EncodeExp0(0x14, 0x1), EncodeExp1(0, 0, 0, 0), // primitive EncodeSopp(0x01), }; std::vector monolithic(std::begin(front), std::begin(front) + 2); monolithic.insert(monolithic.end(), std::begin(back), std::end(back)); ShaderVertexInputInfo input{}; - input.pa_cl_vs_out_cntl = (1u << 21u) | (1u << 18u); + input.pa_cl_vs_out_cntl = (1u << 21u) | (1u << 18u) | 0x00c0f00fu; auto &mesh = input.mesh; mesh.threads_num[0] = 192; mesh.threads_num[1] = mesh.threads_num[2] = 1; @@ -10269,6 +10271,19 @@ void TestMeshExportStorage() { "vsharp %uint_0 %uint_4294967295"), "mesh draw prefix was lost or spilled shader data wrapped into push constants"); const auto &binary = result.spirv; + for (const auto builtin : {3u, 4u}) { + auto expected = builtin == 3u ? std::vector{0, 3} + : std::vector{1, 2}; + if (subgroup_size == 32) { + expected.push_back(expected[0]); + expected.push_back(expected[1]); + std::sort(expected.begin(), expected.end()); + } + Check(SpirvDecorationValueCount(binary, 11u, builtin) == 1 && + SpirvStoredBuiltInElements(binary, builtin) == expected && + SpirvContainsCapability(binary, builtin == 3u ? 32u : 33u), + "mesh distances lost their shared BuiltIn array, plane index or logical lane"); + } std::vector sizes(binary[3]), constants(binary[3]); uint32_t shared_bytes = 0, private_bytes = 0; for (size_t i = 5; i < binary.size(); i += binary[i] >> 16u) { @@ -10301,7 +10316,7 @@ void TestMeshExportStorage() { // when its four vertex exports and primitive exports are shared arrays. Check(shared_bytes == 3840u * 4u + 192u * 4u + 8u && shared_bytes <= 28672u, "mesh staging must retain guest LDS, shared Layer and allocation within the host budget"); - Check(private_bytes == (4u * 16u + 4u) * (64u / subgroup_size), + Check(private_bytes == (4u * 16u + 4u + 4u * 4u) * (64u / subgroup_size), "mesh vertex and primitive exports lost their separate logical-lane storage"); } }