diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc index 8db426857..1700584b2 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.cc @@ -9,6 +9,7 @@ #include "xenia/gpu/vulkan/vulkan_pipeline_cache.h" +#include #include #include @@ -43,6 +44,7 @@ bool VulkanPipelineCache::Initialize() { device_pipeline_features_.features = 0; // TODO(Triang3l): Support the portability subset. + device_pipeline_features_.point_polygons = 1; device_pipeline_features_.triangle_fans = 1; shader_translator_ = std::make_unique( @@ -210,15 +212,21 @@ bool VulkanPipelineCache::GetCurrentStateDescription( description_out.Reset(); const RegisterFile& regs = register_file_; + auto pa_su_sc_mode_cntl = regs.Get(); + auto vgt_draw_initiator = regs.Get(); description_out.vertex_shader_hash = vertex_shader->ucode_data_hash(); description_out.pixel_shader_hash = pixel_shader ? pixel_shader->ucode_data_hash() : 0; description_out.render_pass_key = render_pass_key; - auto vgt_draw_initiator = regs.Get(); + xenos::PrimitiveType primitive_type = vgt_draw_initiator.prim_type; PipelinePrimitiveTopology primitive_topology; - switch (vgt_draw_initiator.prim_type) { + // Vulkan explicitly allows primitive restart only for specific primitive + // types, unlike Direct3D where it's valid for non-strips, but has + // implementation-defined behavior. + bool primitive_restart_allowed = false; + switch (primitive_type) { case xenos::PrimitiveType::kPointList: primitive_topology = PipelinePrimitiveTopology::kPointList; break; @@ -227,24 +235,76 @@ bool VulkanPipelineCache::GetCurrentStateDescription( break; case xenos::PrimitiveType::kLineStrip: primitive_topology = PipelinePrimitiveTopology::kLineStrip; + primitive_restart_allowed = true; break; case xenos::PrimitiveType::kTriangleList: + case xenos::PrimitiveType::kRectangleList: primitive_topology = PipelinePrimitiveTopology::kTriangleList; break; case xenos::PrimitiveType::kTriangleFan: - primitive_topology = device_pipeline_features_.triangle_fans - ? PipelinePrimitiveTopology::kTriangleFan - : PipelinePrimitiveTopology::kTriangleList; + if (device_pipeline_features_.triangle_fans) { + primitive_topology = PipelinePrimitiveTopology::kTriangleFan; + primitive_restart_allowed = true; + } else { + primitive_topology = PipelinePrimitiveTopology::kTriangleList; + } break; case xenos::PrimitiveType::kTriangleStrip: primitive_topology = PipelinePrimitiveTopology::kTriangleStrip; + primitive_restart_allowed = true; break; default: // TODO(Triang3l): All primitive types and tessellation. return false; } description_out.primitive_topology = primitive_topology; - // TODO(Triang3l): Primitive restart. + description_out.primitive_restart = + primitive_restart_allowed && pa_su_sc_mode_cntl.multi_prim_ib_ena; + + // TODO(Triang3l): Tessellation. + bool primitive_polygonal = xenos::IsPrimitivePolygonal(false, primitive_type); + if (primitive_polygonal) { + // Vulkan only allows the polygon mode to be set for both faces - pick the + // most special one (more likely to represent the developer's deliberate + // intentions - fill is very generic, wireframe is common in debug, points + // are for pretty unusual things, but closer to debug purposes too - on the + // Xenos, points have the lowest register value and triangles have the + // highest) based on which faces are not culled. + bool cull_front = pa_su_sc_mode_cntl.cull_front; + bool cull_back = pa_su_sc_mode_cntl.cull_back; + description_out.cull_front = cull_front; + description_out.cull_back = cull_back; + xenos::PolygonType polygon_type = xenos::PolygonType::kTriangles; + if (!cull_front) { + polygon_type = + std::min(polygon_type, pa_su_sc_mode_cntl.polymode_front_ptype); + } + if (!cull_back) { + polygon_type = + std::min(polygon_type, pa_su_sc_mode_cntl.polymode_back_ptype); + } + switch (polygon_type) { + case xenos::PolygonType::kPoints: + // When points are not supported, use lines instead, preserving + // debug-like purpose. + description_out.polygon_mode = device_pipeline_features_.point_polygons + ? PipelinePolygonMode::kPoint + : PipelinePolygonMode::kLine; + break; + case xenos::PolygonType::kLines: + description_out.polygon_mode = PipelinePolygonMode::kLine; + break; + case xenos::PolygonType::kTriangles: + description_out.polygon_mode = PipelinePolygonMode::kFill; + break; + default: + assert_unhandled_case(polygon_type); + return false; + } + description_out.front_face_clockwise = pa_su_sc_mode_cntl.face != 0; + } else { + description_out.polygon_mode = PipelinePolygonMode::kFill; + } return true; } @@ -374,6 +434,34 @@ bool VulkanPipelineCache::EnsurePipelineCreated( VkPipelineRasterizationStateCreateInfo rasterization_state = {}; rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; + switch (description.polygon_mode) { + case PipelinePolygonMode::kFill: + rasterization_state.polygonMode = VK_POLYGON_MODE_FILL; + break; + case PipelinePolygonMode::kLine: + rasterization_state.polygonMode = VK_POLYGON_MODE_LINE; + break; + case PipelinePolygonMode::kPoint: + assert_true(device_pipeline_features_.point_polygons); + if (!device_pipeline_features_.point_polygons) { + return false; + } + rasterization_state.polygonMode = VK_POLYGON_MODE_POINT; + break; + default: + assert_unhandled_case(description.polygon_mode); + return false; + } + rasterization_state.cullMode = VK_CULL_MODE_NONE; + if (description.cull_front) { + rasterization_state.cullMode |= VK_CULL_MODE_FRONT_BIT; + } + if (description.cull_back) { + rasterization_state.cullMode |= VK_CULL_MODE_BACK_BIT; + } + rasterization_state.frontFace = description.front_face_clockwise + ? VK_FRONT_FACE_CLOCKWISE + : VK_FRONT_FACE_COUNTER_CLOCKWISE; rasterization_state.lineWidth = 1.0f; VkPipelineMultisampleStateCreateInfo multisample_state = {}; diff --git a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h index 1d2f852e5..b22212552 100644 --- a/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h +++ b/src/xenia/gpu/vulkan/vulkan_pipeline_cache.h @@ -74,6 +74,7 @@ class VulkanPipelineCache { // method) should result in a different storage file being used. union DevicePipelineFeatures { struct { + uint32_t point_polygons : 1; uint32_t triangle_fans : 1; }; uint32_t features = 0; @@ -91,6 +92,12 @@ class VulkanPipelineCache { kPatchList, }; + enum class PipelinePolygonMode : uint32_t { + kFill, + kLine, + kPoint, + }; + XEPACKEDSTRUCT(PipelineDescription, { uint64_t vertex_shader_hash; // 0 if no pixel shader. @@ -98,8 +105,13 @@ class VulkanPipelineCache { VulkanRenderTargetCache::RenderPassKey render_pass_key; // Input assembly. - PipelinePrimitiveTopology primitive_topology : 3; - uint32_t primitive_restart : 1; + PipelinePrimitiveTopology primitive_topology : 3; // 3 + uint32_t primitive_restart : 1; // 4 + // Rasterization. + PipelinePolygonMode polygon_mode : 2; // 6 + uint32_t cull_front : 1; // 7 + uint32_t cull_back : 1; // 8 + uint32_t front_face_clockwise : 1; // 9 // Including all the padding, for a stable hash. PipelineDescription() { Reset(); }