gpu: implement PA_CL_UCP user clip planes through draw push constants

This commit is contained in:
Mr. Big Britches
2026-10-20 07:00:55 +02:00
committed by nmzik
parent 18e6747dbf
commit feed075a9f
11 changed files with 109 additions and 45 deletions
@@ -349,6 +349,38 @@ uint32_t ExportVector(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo
return vector;
}
uint32_t DistancePointer(EmitterState& state, IR::StageOutputKind kind, uint32_t index) {
if (state.program.stage == ShaderType::Mesh) {
return MeshOutputPointer(state, kind, index);
}
const auto variable = kind == IR::StageOutputKind::ClipDistance ? state.clip_distance_variable
: state.cull_distance_variable;
const auto pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpAccessChain,
TypePointer(state, spv::StorageClassOutput, TypeF32(state)), pointer,
variable, ConstantU32(state, index));
return pointer;
}
// The clipper keeps the half space where dot(plane, position) >= 0 for each UCP_ENA plane.
void EmitUserClipDistances(EmitterState& state, uint32_t position) {
uint32_t index = 0;
for (auto mask = state.user_clip_planes; mask != 0; mask &= mask - 1u) {
const auto dword = IR::PushData::UserClipPlaneDword + 4u * std::countr_zero(mask);
uint32_t components[4] {};
for (uint32_t i = 0; i < 4; i++) {
components[i] = Unary(state, spv::OpBitcast, TypeF32(state),
EmitPushDataLoad(state, ConstantU32(state, dword + i)));
}
const auto plane = state.builder.AllocateId();
state.builder.AddFunction(spv::OpCompositeConstruct, TypeF32Vector(state, 4), plane,
components[0], components[1], components[2], components[3]);
state.builder.AddFunction(
spv::OpStore, DistancePointer(state, IR::StageOutputKind::ClipDistance, index++),
Binary(state, spv::OpDot, TypeF32(state), plane, position));
}
}
void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::ExportInfo& exp) {
auto& state = ctx.state;
for (uint32_t component = 0; component < 4; component++) {
@@ -389,23 +421,13 @@ void EmitAuxPositionExport(ValueEmitContext& ctx, uint32_t data, const IR::Expor
state.builder.AddFunction(spv::OpStore, state.point_size_variable, f32);
continue;
}
auto StoreDistance = [&](IR::StageOutputKind kind, uint32_t variable, uint32_t index) {
if (index == UINT32_MAX) {
return;
auto StoreDistance = [&](IR::StageOutputKind kind, uint32_t index) {
if (index != UINT32_MAX) {
state.builder.AddFunction(spv::OpStore, DistancePointer(state, kind, index), f32);
}
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(IR::StageOutputKind::ClipDistance, state.clip_distance_variable, output.clip_distance);
StoreDistance(IR::StageOutputKind::CullDistance, state.cull_distance_variable, output.cull_distance);
StoreDistance(IR::StageOutputKind::ClipDistance, output.clip_distance);
StoreDistance(IR::StageOutputKind::CullDistance, output.cull_distance);
}
}
@@ -596,6 +618,9 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
state.input_info.vertex->clip_space.enabled) {
value = ConvertPositionToClipSpace(state, value);
}
if (exp.kind == IR::ExportTargetKind::Position && state.user_clip_planes != 0) {
EmitUserClipDistances(state, value);
}
if (state.program.stage == ShaderType::Mesh) {
const auto kind = exp.kind == IR::ExportTargetKind::Position
? IR::StageOutputKind::Position
@@ -608,7 +633,6 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
const auto equal = state.builder.AllocateId();
const auto invalid = state.builder.AllocateId();
const auto distance = state.builder.AllocateId();
const auto distance_pointer = state.builder.AllocateId();
state.builder.AddFunction(spv::OpFOrdEqual, TypeBoolVector(state, 4), equal,
value, zero);
state.builder.AddFunction(spv::OpAll, TypeBool(state), invalid, equal);
@@ -617,11 +641,10 @@ void EmitSetAttribute(ValueEmitContext& ctx, const IR::Inst& inst) {
state.builder.AddFunction(spv::OpSelect, TypeF32(state), distance, invalid,
ConstantF32Value(state, -1.0f),
ConstantF32Value(state, 0.0f));
state.builder.AddFunction(
spv::OpAccessChain, TypePointer(state, spv::StorageClassOutput, TypeF32(state)),
distance_pointer, state.clip_distance_variable,
ConstantU32(state, state.invalid_position_clip_distance));
state.builder.AddFunction(spv::OpStore, distance_pointer, distance);
state.builder.AddFunction(spv::OpStore,
DistancePointer(state, IR::StageOutputKind::ClipDistance,
state.invalid_position_clip_distance),
distance);
static std::atomic_bool logged = false;
if (!logged.exchange(true, std::memory_order_relaxed)) {
Log::WriteToConsoleAndLog(
@@ -148,6 +148,7 @@ struct EmitterState {
uint32_t point_size_variable = 0;
uint32_t clip_distance_variable = 0;
uint32_t invalid_position_clip_distance = UINT32_MAX;
uint32_t user_clip_planes = 0;
uint32_t cull_distance_variable = 0;
uint32_t layer_variable = 0;
uint32_t viewport_index_variable = 0;
@@ -244,7 +244,7 @@ void DefineDescriptors(EmitterState& state) {
counts[static_cast<size_t>(Kind::ShaderData)] =
bindings.ShaderDataDwords() != 0 && !bindings.UsesPushData();
if (state.program.bindings.UsesPushData() || state.program.stage == ShaderType::Mesh ||
state.object_primitive_id_variable != 0) {
state.object_primitive_id_variable != 0 || state.user_clip_planes != 0) {
const auto type = PushConstantBlockType(state);
state.push_constant_variable = state.builder.DefineGlobalVariable(
TypePointer(state, spv::StorageClassPushConstant, type), spv::StorageClassPushConstant);
@@ -600,14 +600,24 @@ void DefineOutputs(EmitterState& state) {
cull_distance_count = std::max(cull_distance_count, output.index + 1);
}
}
const bool position = std::ranges::any_of(state.outputs, [](const OutputBinding& output) {
return output.kind == IR::StageOutputKind::Position;
});
if (position && state.input_info.vertex->user_clip_planes != 0) {
// PipelineCache rejects UCP_ENA with shader clip or cull distances.
EXIT_IF(clip_distance_count + cull_distance_count != 0);
state.user_clip_planes = state.input_info.vertex->user_clip_planes;
for (auto mask = state.user_clip_planes; mask != 0; mask &= mask - 1u) {
state.outputs.push_back(
{{IR::StageOutputKind::ClipDistance, clip_distance_count++, 0, "gl_ClipDistance"}});
}
}
if (state.program.stage == ShaderType::Mesh) {
DefineMeshOutputs(state, clip_distance_count, cull_distance_count);
return;
}
if (state.program.stage == ShaderType::Vertex && clip_distance_count + cull_distance_count < 8u &&
std::ranges::any_of(state.outputs, [](const OutputBinding& output) {
return output.kind == IR::StageOutputKind::Position;
})) {
if (state.program.stage == ShaderType::Vertex &&
clip_distance_count + cull_distance_count < 8u && position) {
// Reserve one plane for the enabled PA_CL_CLIP_CNTL clipping-error cull.
state.invalid_position_clip_distance = clip_distance_count++;
state.outputs.push_back({{IR::StageOutputKind::ClipDistance,
+8 -1
View File
@@ -356,7 +356,14 @@ struct PushData {
static constexpr uint32_t NoStart = UINT32_MAX;
std::array<uint32_t, DwordCount> dwords {};
[[nodiscard]] static constexpr uint32_t GraphicsDrawDwords(bool mesh, bool object_id) {
// User clip planes 0..N follow the mesh draw parameters or object ID, four dwords each.
static constexpr uint32_t UserClipPlaneDword = MeshDrawDwordCount;
[[nodiscard]] static constexpr uint32_t GraphicsDrawDwords(bool mesh, bool object_id,
uint32_t user_clip_planes = 0) {
if (user_clip_planes != 0) {
return UserClipPlaneDword + 4u * std::bit_width(user_clip_planes);
}
return mesh ? MeshDrawDwordCount : static_cast<uint32_t>(object_id);
}