shader: emit per-vertex mesh clip and cull distances

Co-authored-by: Senaxx <michelbeld@gmail.com>
Co-authored-by: shadowbeat070 <70843316+shadowbeat070@users.noreply.github.com>
This commit is contained in:
nmzik
2026-10-04 17:25:44 +04:00
parent 98557a83b6
commit 39248a1f87
5 changed files with 82 additions and 38 deletions
@@ -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);
}
}
@@ -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);
@@ -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<uint32_t>(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<uint32_t>(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);
}
});
@@ -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 &&
+17 -2
View File
@@ -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<uint32_t> 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<uint32_t>{0, 3}
: std::vector<uint32_t>{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<uint32_t> 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");
}
}