From 3eab2b8b39442e32537610c955fbb8db0c2a6561 Mon Sep 17 00:00:00 2001 From: goldislead <69987043+goldislead@users.noreply.github.com> Date: Sat, 8 Aug 2026 09:43:00 -0700 Subject: [PATCH] [GPU] Handle tessellated triangle strip and fan draws Strips and fans normalize to triangle list host draws instead of being rejected. The conversion buffer is built at runtime when the backend did not bake one at init. Co-authored-by: Herman S. <429230+has207@users.noreply.github.com> Co-authored-by: Reality --- src/xenia/gpu/primitive_processor.cc | 80 ++++++++++++++++++++++++---- src/xenia/gpu/primitive_processor.h | 6 +++ 2 files changed, 75 insertions(+), 11 deletions(-) diff --git a/src/xenia/gpu/primitive_processor.cc b/src/xenia/gpu/primitive_processor.cc index 7364c36d8..129b5a16a 100644 --- a/src/xenia/gpu/primitive_processor.cc +++ b/src/xenia/gpu/primitive_processor.cc @@ -296,17 +296,18 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { regs.Get().tess_mode; Shader::HostVertexShaderType host_vertex_shader_type; if (tessellation_enabled) { - // Currently only supporting tessellation in known cases for safety, and not - // yet converting patch strips / fans to patch lists until games using them - // are found for easier debugging when it actually happens. - // TODO(Triang3l): Conversion of patch strips / fans if found. + // Currently only supporting tessellation in known cases for safety. host_vertex_shader_type = Shader::HostVertexShaderType(-1); switch (guest_primitive_type) { case xenos::PrimitiveType::kTriangleList: + case xenos::PrimitiveType::kTriangleFan: + case xenos::PrimitiveType::kTriangleStrip: // Also supported by triangle strips and fans according to: // https://www.khronos.org/registry/OpenGL/extensions/AMD/AMD_vertex_shader_tessellator.txt - // Would need to convert those to triangle lists, but haven't seen any - // games using tessellated strips / fans so far. + // Convert strips and fans to triangle lists for tessellation. + if (guest_primitive_type != xenos::PrimitiveType::kTriangleList) { + host_primitive_type = xenos::PrimitiveType::kTriangleList; + } switch (tessellation_mode) { case xenos::TessellationMode::kDiscrete: // - 415607E1 - nets above barrels in the beginning of the first @@ -534,11 +535,34 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { xenos::PrimitiveType::kTriangleList); cacheable.host_draw_vertex_count = GetTriangleFanListIndexCount(cacheable.host_draw_vertex_count); - cacheable.index_buffer_type = - ProcessedIndexBufferType::kHostBuiltinForAuto; - assert_true(builtin_ib_offset_triangle_fans_to_lists_ != SIZE_MAX); - cacheable.host_index_buffer_handle = - builtin_ib_offset_triangle_fans_to_lists_; + if (builtin_ib_offset_triangle_fans_to_lists_ != SIZE_MAX) { + cacheable.index_buffer_type = + ProcessedIndexBufferType::kHostBuiltinForAuto; + cacheable.host_index_buffer_handle = + builtin_ib_offset_triangle_fans_to_lists_; + } else if (cacheable.host_draw_vertex_count) { + // The backend didn't request the builtin fan to list conversion + // at initialization, so there's no prebuilt index buffer. This is + // only reached via the tessellation path above, which forces + // host_primitive_type to kTriangleList, so build the conversion at + // runtime. + cacheable.index_buffer_type = + ProcessedIndexBufferType::kHostConverted; + auto host_indices = reinterpret_cast( + RequestHostConvertedIndexBufferForCurrentFrame( + xenos::IndexFormat::kInt16, + cacheable.host_draw_vertex_count, false, 0, + cacheable.host_index_buffer_handle)); + if (!host_indices) { + return false; + } + uint16_t* host_indices_write = host_indices; + for (uint32_t i = 2; i < guest_draw_vertex_count; ++i) { + *(host_indices_write++) = uint16_t(i - 1); + *(host_indices_write++) = uint16_t(i); + *(host_indices_write++) = 0; + } + } break; case xenos::PrimitiveType::kLineLoop: // Plus 1 element (if there's anything to draw) in the strip, still @@ -563,6 +587,40 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { cacheable.host_index_buffer_handle = builtin_ib_offset_quad_lists_to_triangle_lists_; break; + case xenos::PrimitiveType::kTriangleStrip: + assert_true(host_primitive_type == + xenos::PrimitiveType::kTriangleList); + cacheable.host_draw_vertex_count = + GetTriangleStripListIndexCount(cacheable.host_draw_vertex_count); + if (cacheable.host_draw_vertex_count) { + // There's no prebuilt strip to list index buffer. This is only + // reached via the tessellation path above, which forces + // host_primitive_type to kTriangleList, so build the conversion at + // runtime. + cacheable.index_buffer_type = + ProcessedIndexBufferType::kHostConverted; + auto host_indices = reinterpret_cast( + RequestHostConvertedIndexBufferForCurrentFrame( + xenos::IndexFormat::kInt16, + cacheable.host_draw_vertex_count, false, 0, + cacheable.host_index_buffer_handle)); + if (!host_indices) { + return false; + } + uint16_t* host_indices_write = host_indices; + for (uint32_t i = 2; i < guest_draw_vertex_count; ++i) { + if ((i & 1) == 0) { + *(host_indices_write++) = uint16_t(i - 2); + *(host_indices_write++) = uint16_t(i - 1); + *(host_indices_write++) = uint16_t(i); + } else { + *(host_indices_write++) = uint16_t(i - 1); + *(host_indices_write++) = uint16_t(i - 2); + *(host_indices_write++) = uint16_t(i); + } + } + } + break; default: assert_always(); return false; diff --git a/src/xenia/gpu/primitive_processor.h b/src/xenia/gpu/primitive_processor.h index d1150e638..b64451016 100644 --- a/src/xenia/gpu/primitive_processor.h +++ b/src/xenia/gpu/primitive_processor.h @@ -548,6 +548,12 @@ class PrimitiveProcessor { uint32_t fan_index_count) { return fan_index_count > 2 ? (fan_index_count - 2) * 3 : 0; } + // Triangle strip to triangle list conversion. + // A strip with N vertices produces (N-2) triangles, each needing 3 indices. + static constexpr uint32_t GetTriangleStripListIndexCount( + uint32_t strip_index_count) { + return strip_index_count > 2 ? (strip_index_count - 2) * 3 : 0; + } template static void TriangleFanToList(Index* dest, const Index* source, uint32_t source_index_count,