From f13ee6ec38fae7d9b31ef547d648a74496987258 Mon Sep 17 00:00:00 2001 From: "Herman S." <429230+has207@users.noreply.github.com> Date: Tue, 21 Oct 2025 09:00:37 +0900 Subject: [PATCH] [Vulkan] Add support for user clip planes --- src/xenia/gpu/spirv_shader_translator.cc | 126 +++++++++++++++++- src/xenia/gpu/spirv_shader_translator.h | 14 ++ .../gpu/vulkan/vulkan_command_processor.cc | 51 +++++++ .../gpu/vulkan/vulkan_command_processor.h | 3 + src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc | 21 ++- src/xenia/gpu/vulkan/vulkan_pipeline_cache.h | 3 +- 6 files changed, 208 insertions(+), 10 deletions(-) diff --git a/src/xenia/gpu/spirv_shader_translator.cc b/src/xenia/gpu/spirv_shader_translator.cc index bdbda1fd5..2001b13dc 100644 --- a/src/xenia/gpu/spirv_shader_translator.cc +++ b/src/xenia/gpu/spirv_shader_translator.cc @@ -241,6 +241,10 @@ void SpirvShaderTranslator::StartTranslation() { type_uint4_, builder_->makeUintConstant(4), sizeof(uint32_t) * 4); builder_->addDecoration(type_uint4_array_4, spv::DecorationArrayStride, sizeof(uint32_t) * 4); + spv::Id type_float4_array_6 = builder_->makeArrayType( + type_float4_, builder_->makeUintConstant(6), sizeof(float) * 4); + builder_->addDecoration(type_float4_array_6, spv::DecorationArrayStride, + sizeof(float) * 4); const SystemConstant system_constants[] = { {"flags", offsetof(SystemConstants, flags), type_uint_}, {"vertex_index_load_address", @@ -1261,6 +1265,51 @@ void SpirvShaderTranslator::StartVertexOrTessEvalShaderBeforeMain() { std::vector struct_per_vertex_members; struct_per_vertex_members.reserve(kOutputPerVertexMemberCount); struct_per_vertex_members.push_back(type_float4_); + + // Only allocate ClipDistance/CullDistance arrays when user clip planes are + // actually enabled (count > 0). + uint32_t user_clip_plane_count = + shader_modification.vertex.user_clip_plane_count; + output_per_vertex_clip_distance_member_index_ = 0; + output_per_vertex_cull_distance_member_index_ = 0; + constexpr uint32_t kMaxUserClipPlanes = 6; + if (user_clip_plane_count > 0) { + // Create separate uniform buffer for clip planes. + spv::Id type_float4_array_6 = builder_->makeArrayType( + type_float4_, builder_->makeUintConstant(6), sizeof(float) * 4); + builder_->addDecoration(type_float4_array_6, spv::DecorationArrayStride, + sizeof(float) * 4); + std::vector clip_plane_struct_members; + clip_plane_struct_members.push_back(type_float4_array_6); + spv::Id type_clip_plane_constants = builder_->makeStructType( + clip_plane_struct_members, "XeClipPlaneConstants"); + builder_->addMemberDecoration(type_clip_plane_constants, 0, + spv::DecorationOffset, 0); + builder_->addDecoration(type_clip_plane_constants, spv::DecorationBlock); + uniform_clip_plane_constants_ = builder_->createVariable( + spv::NoPrecision, spv::StorageClassUniform, type_clip_plane_constants, + "xe_uniform_clip_planes"); + builder_->addDecoration(uniform_clip_plane_constants_, + spv::DecorationDescriptorSet, + int(kDescriptorSetConstants)); + builder_->addDecoration(uniform_clip_plane_constants_, + spv::DecorationBinding, + int(kConstantBufferClipPlanes)); + if (features_.spirv_version >= spv::Spv_1_4) { + main_interface_.push_back(uniform_clip_plane_constants_); + } + + output_per_vertex_clip_distance_member_index_ = + static_cast(struct_per_vertex_members.size()); + struct_per_vertex_members.push_back(builder_->makeArrayType( + type_float_, builder_->makeUintConstant(user_clip_plane_count), 0)); + + output_per_vertex_cull_distance_member_index_ = + static_cast(struct_per_vertex_members.size()); + struct_per_vertex_members.push_back(builder_->makeArrayType( + type_float_, builder_->makeUintConstant(user_clip_plane_count), 0)); + } + spv::Id type_struct_per_vertex = builder_->makeStructType(struct_per_vertex_members, "gl_PerVertex"); builder_->addMemberName(type_struct_per_vertex, @@ -1268,6 +1317,24 @@ void SpirvShaderTranslator::StartVertexOrTessEvalShaderBeforeMain() { builder_->addMemberDecoration( type_struct_per_vertex, kOutputPerVertexMemberPosition, spv::DecorationBuiltIn, static_cast(spv::BuiltIn::Position)); + + // Decorate clip/cull arrays only if allocated. + if (user_clip_plane_count > 0) { + builder_->addMemberName(type_struct_per_vertex, + output_per_vertex_clip_distance_member_index_, + "gl_ClipDistance"); + builder_->addMemberDecoration( + type_struct_per_vertex, output_per_vertex_clip_distance_member_index_, + spv::DecorationBuiltIn, static_cast(spv::BuiltIn::ClipDistance)); + + builder_->addMemberName(type_struct_per_vertex, + output_per_vertex_cull_distance_member_index_, + "gl_CullDistance"); + builder_->addMemberDecoration( + type_struct_per_vertex, output_per_vertex_cull_distance_member_index_, + spv::DecorationBuiltIn, static_cast(spv::BuiltIn::CullDistance)); + } + builder_->addDecoration(type_struct_per_vertex, spv::DecorationBlock); output_per_vertex_ = builder_->createVariable( spv::NoPrecision, spv::StorageClassOutput, type_struct_per_vertex, ""); @@ -1519,6 +1586,8 @@ void SpirvShaderTranslator::StartVertexOrTessEvalShaderInMain() { } void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderInMain() { + Modification shader_modification = GetSpirvShaderModification(); + id_vector_temp_.clear(); id_vector_temp_.push_back( builder_->makeIntConstant(kOutputPerVertexMemberPosition)); @@ -1600,6 +1669,61 @@ void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderInMain() { } } + // Compute user clip/cull distances BEFORE NDC transform. + // Clip planes must operate on the original clip-space position. + uint32_t user_clip_plane_count = + shader_modification.vertex.user_clip_plane_count; + if (user_clip_plane_count > 0) { + // Reconstruct the original clip-space position (x, y, z, w) for dot + // product. Use the untransformed position_xyz and corrected position_w. + spv::Id clip_space_position; + { + std::unique_ptr composite_construct_op = + std::make_unique( + builder_->getUniqueId(), type_float4_, spv::OpCompositeConstruct); + composite_construct_op->addIdOperand(position_xyz); + composite_construct_op->addIdOperand(position_w); + clip_space_position = composite_construct_op->getResultId(); + builder_->getBuildPoint()->addInstruction( + std::move(composite_construct_op)); + } + + // Determine which member index to use based on whether we're using + // ClipDistance or CullDistance. + bool user_clip_plane_cull = shader_modification.vertex.user_clip_plane_cull; + unsigned int clip_cull_distance_member_index = + user_clip_plane_cull ? output_per_vertex_cull_distance_member_index_ + : output_per_vertex_clip_distance_member_index_; + + // Compute distance to each enabled user clip plane via dot product. + for (uint32_t i = 0; i < user_clip_plane_count; ++i) { + // Load user clip plane from separate clip plane constants buffer. + id_vector_temp_.clear(); + id_vector_temp_.push_back( + builder_->makeIntConstant(0)); // Struct member 0 + id_vector_temp_.push_back( + builder_->makeIntConstant(int(i))); // Array index + spv::Id clip_plane = builder_->createLoad( + builder_->createAccessChain(spv::StorageClassUniform, + uniform_clip_plane_constants_, + id_vector_temp_), + spv::NoPrecision); + + // Compute dot product: distance = dot(clip_space_position, clip_plane). + spv::Id distance = builder_->createBinOp(spv::OpDot, type_float_, + clip_space_position, clip_plane); + + // Store to gl_ClipDistance[i] or gl_CullDistance[i]. + id_vector_temp_.clear(); + id_vector_temp_.push_back( + builder_->makeIntConstant(clip_cull_distance_member_index)); + id_vector_temp_.push_back(builder_->makeIntConstant(int(i))); + spv::Id distance_ptr = builder_->createAccessChain( + spv::StorageClassOutput, output_per_vertex_, id_vector_temp_); + builder_->createStore(distance, distance_ptr); + } + } + // Apply the NDC scale and offset for guest to host viewport transformation. id_vector_temp_.clear(); id_vector_temp_.push_back(builder_->makeIntConstant(kSystemConstantNdcScale)); @@ -1641,8 +1765,6 @@ void SpirvShaderTranslator::CompleteVertexOrTessEvalShaderInMain() { builder_->createStore(point_size, output_point_size_); } - Modification shader_modification = GetSpirvShaderModification(); - // Expand the point sprite. if (shader_modification.vertex.host_vertex_shader_type == Shader::HostVertexShaderType::kPointListAsTriangleStrip) { diff --git a/src/xenia/gpu/spirv_shader_translator.h b/src/xenia/gpu/spirv_shader_translator.h index d61885c91..f24a9459b 100644 --- a/src/xenia/gpu/spirv_shader_translator.h +++ b/src/xenia/gpu/spirv_shader_translator.h @@ -60,6 +60,11 @@ class SpirvShaderTranslator : public ShaderTranslator { // Pipeline stage and input configuration. Shader::HostVertexShaderType host_vertex_shader_type : Shader::kHostVertexShaderTypeBitCount; + // User clip plane count, number of clip planes enabled (0-6). + uint32_t user_clip_plane_count : 3; + // If user_clip_plane_count is non-zero, whether they should be cull + // distances instead of clip distances. + uint32_t user_clip_plane_cull : 1; } vertex; struct PixelShaderModification { // uint32_t 0. @@ -258,12 +263,18 @@ class SpirvShaderTranslator : public ShaderTranslator { float edram_blend_constant[4]; }; + // Separate constant buffer for user clip planes + struct ClipPlaneConstants { + float user_clip_planes[6][4]; + }; + enum ConstantBuffer : uint32_t { kConstantBufferSystem, kConstantBufferFloatVertex, kConstantBufferFloatPixel, kConstantBufferBoolLoop, kConstantBufferFetch, + kConstantBufferClipPlanes, kConstantBufferCount, }; @@ -846,6 +857,7 @@ class SpirvShaderTranslator : public ShaderTranslator { kSystemConstantEdramBlendConstant, }; spv::Id uniform_system_constants_; + spv::Id uniform_clip_plane_constants_; spv::Id uniform_float_constants_; spv::Id uniform_bool_loop_constants_; spv::Id uniform_fetch_constants_; @@ -895,6 +907,8 @@ class SpirvShaderTranslator : public ShaderTranslator { kOutputPerVertexMemberCount, }; spv::Id output_per_vertex_; + unsigned int output_per_vertex_clip_distance_member_index_ = 0; + unsigned int output_per_vertex_cull_distance_member_index_ = 0; // With fragment shader interlock, variables in the main function. // Otherwise, framebuffer color attachment outputs. diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.cc b/src/xenia/gpu/vulkan/vulkan_command_processor.cc index fa1f39d00..6be391fa0 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.cc +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.cc @@ -4064,6 +4064,34 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i]; } + // User clip planes (for vertex shaders) + auto pa_cl_clip_cntl = regs.Get(); + if (!pa_cl_clip_cntl.clip_disable && pa_cl_clip_cntl.ucp_ena) { + float* user_clip_plane_write_ptr = + clip_plane_constants_.user_clip_planes[0]; + uint32_t user_clip_planes_remaining = pa_cl_clip_cntl.ucp_ena; + uint32_t user_clip_plane_index; + while (xe::bit_scan_forward(user_clip_planes_remaining, + &user_clip_plane_index)) { + user_clip_planes_remaining = + xe::clear_lowest_bit(user_clip_planes_remaining); + // Validate plane index is within bounds (0-5). + assert(user_clip_plane_index < 6); + if (user_clip_plane_index >= 6) { + continue; + } + const void* user_clip_plane_regs = + ®s[XE_GPU_REG_PA_CL_UCP_0_X + user_clip_plane_index * 4]; + if (std::memcmp(user_clip_plane_write_ptr, user_clip_plane_regs, + 4 * sizeof(float))) { + dirty = true; + std::memcpy(user_clip_plane_write_ptr, user_clip_plane_regs, + 4 * sizeof(float)); + } + user_clip_plane_write_ptr += 4; + } + } + // Point size. if (vgt_draw_initiator.prim_type == xenos::PrimitiveType::kPointList) { auto pa_su_point_minmax = regs.Get(); @@ -4446,6 +4474,29 @@ bool VulkanCommandProcessor::UpdateBindings(const VulkanShader* vertex_shader, current_constant_buffers_up_to_date_ |= UINT32_C(1) << SpirvShaderTranslator::kConstantBufferSystem; } + // Clip plane constants. + auto pa_cl_clip_cntl = regs.Get(); + bool clip_planes_enabled = + !pa_cl_clip_cntl.clip_disable && pa_cl_clip_cntl.ucp_ena; + if (clip_planes_enabled) { + if (!(current_constant_buffers_up_to_date_ & + (UINT32_C(1) + << SpirvShaderTranslator::kConstantBufferClipPlanes))) { + VkDescriptorBufferInfo& buffer_info = current_constant_buffer_infos_ + [SpirvShaderTranslator::kConstantBufferClipPlanes]; + uint8_t* mapping = uniform_buffer_pool_->Request( + frame_current_, sizeof(SpirvShaderTranslator::ClipPlaneConstants), + uniform_buffer_alignment, buffer_info.buffer, buffer_info.offset); + if (!mapping) { + return false; + } + buffer_info.range = sizeof(SpirvShaderTranslator::ClipPlaneConstants); + std::memcpy(mapping, &clip_plane_constants_, + sizeof(SpirvShaderTranslator::ClipPlaneConstants)); + current_constant_buffers_up_to_date_ |= + UINT32_C(1) << SpirvShaderTranslator::kConstantBufferClipPlanes; + } + } // Vertex shader float constants. if (!(current_constant_buffers_up_to_date_ & (UINT32_C(1) << SpirvShaderTranslator::kConstantBufferFloatVertex))) { diff --git a/src/xenia/gpu/vulkan/vulkan_command_processor.h b/src/xenia/gpu/vulkan/vulkan_command_processor.h index 43594bda5..77ac2f63a 100644 --- a/src/xenia/gpu/vulkan/vulkan_command_processor.h +++ b/src/xenia/gpu/vulkan/vulkan_command_processor.h @@ -753,6 +753,9 @@ class VulkanCommandProcessor final : public CommandProcessor { // System shader constants. SpirvShaderTranslator::SystemConstants system_constants_; + // Clip plane constants. + SpirvShaderTranslator::ClipPlaneConstants clip_plane_constants_; + // Temporary storage for memexport stream constants used in the draw. std::vector memexport_ranges_; diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index 9e55acc35..066e7e40a 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -175,6 +175,14 @@ VulkanPipelineCache::GetCurrentVertexShaderModification( modification.vertex.interpolator_mask = interpolator_mask; + // User clip planes. + auto pa_cl_clip_cntl = regs.Get(); + uint32_t user_clip_planes = + pa_cl_clip_cntl.clip_disable ? 0 : pa_cl_clip_cntl.ucp_ena; + modification.vertex.user_clip_plane_count = xe::bit_count(user_clip_planes); + modification.vertex.user_clip_plane_cull = + uint32_t(user_clip_planes && pa_cl_clip_cntl.ucp_cull_only_ena); + if (host_vertex_shader_type == Shader::HostVertexShaderType::kPointListAsTriangleStrip) { modification.vertex.output_point_parameters = uint32_t(ps_param_gen_used); @@ -805,15 +813,14 @@ bool VulkanPipelineCache::GetGeometryShaderKey( // real counts here. key.interpolator_count = xe::bit_count(vertex_shader_modification.vertex.interpolator_mask); - key.user_clip_plane_count = - /* vertex_shader_modification.vertex.user_clip_plane_count */ 0; - key.user_clip_plane_cull = - /* vertex_shader_modification.vertex.user_clip_plane_cull */ 0; key.has_vertex_kill_and = /* vertex_shader_modification.vertex.vertex_kill_and */ 0; key.has_point_size = vertex_shader_modification.vertex.output_point_parameters; key.has_point_coordinates = pixel_shader_modification.pixel.param_gen_point; + // Single bit to indicate if clip planes are enabled. + key.has_user_clip_planes = + uint32_t(vertex_shader_modification.vertex.user_clip_plane_count > 0); key_out = key; return true; } @@ -858,10 +865,12 @@ VkShaderModule VulkanPipelineCache::GetGeometryShader(GeometryShaderKey key) { assert_unhandled_case(key.type); } + // When enabled, use max size to reduce variants from different counts. + constexpr uint32_t kMaxUserClipPlanes = 6; uint32_t clip_distance_count = - key.user_clip_plane_cull ? 0 : key.user_clip_plane_count; + key.has_user_clip_planes ? kMaxUserClipPlanes : 0; uint32_t cull_distance_count = - (key.user_clip_plane_cull ? key.user_clip_plane_count : 0) + + (key.has_user_clip_planes ? kMaxUserClipPlanes : 0) + key.has_vertex_kill_and; SpirvBuilder builder(spv::Spv_1_5, diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h index 529e34885..4e92c29f6 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h @@ -224,8 +224,7 @@ class VulkanPipelineCache { struct { PipelineGeometryShader type : 2; uint32_t interpolator_count : 5; - uint32_t user_clip_plane_count : 3; - uint32_t user_clip_plane_cull : 1; + uint32_t has_user_clip_planes : 1; uint32_t has_vertex_kill_and : 1; uint32_t has_point_size : 1; uint32_t has_point_coordinates : 1;