diff --git a/src/xenia/gpu/primitive_processor.cc b/src/xenia/gpu/primitive_processor.cc index a39f5a9cf..1150df1cc 100644 --- a/src/xenia/gpu/primitive_processor.cc +++ b/src/xenia/gpu/primitive_processor.cc @@ -45,6 +45,11 @@ DEFINE_bool( "May also be useful for graphics debugging when the debugger doesn't " "display the geometry generated by geometry shaders properly.", "GPU"); +DEFINE_bool( + force_convert_triangle_strips_to_lists, false, + "Force CPU conversion of triangle strips to triangle lists. This may help " + "diagnose rendering issues related to triangle strip handling.", + "GPU"); DEFINE_bool( ignore_32bit_vertex_index_support, false, "For host graphics API downlevel testing only (useful only for Qualcomm " @@ -115,6 +120,10 @@ bool PrimitiveProcessor::InitializeCommon( convert_quad_lists_to_triangle_lists_ = !quad_lists_supported || cvars::force_convert_quad_lists_to_triangle_lists; + // Triangle strips are supported natively on all backends, but can be forced + // to convert to lists for debugging purposes. + convert_triangle_strips_to_lists_ = + cvars::force_convert_triangle_strips_to_lists; // No override cvars as hosts are not required to support the fallback paths // since they require different vertex shader structure (for the fallback // HostVertexShaderTypes). @@ -170,6 +179,13 @@ bool PrimitiveProcessor::InitializeCommon( } else { builtin_ib_offset_quad_lists_to_triangle_lists_ = SIZE_MAX; } + if (convert_triangle_strips_to_lists_) { + builtin_ib_offset_triangle_strips_to_lists_ = builtin_index_buffer_size; + builtin_index_buffer_size += + sizeof(uint16_t) * GetTriangleStripListIndexCount(UINT16_MAX); + } else { + builtin_ib_offset_triangle_strips_to_lists_ = SIZE_MAX; + } if (builtin_index_buffer_size) { if (!InitializeBuiltinIndexBuffer( builtin_index_buffer_size, @@ -228,6 +244,28 @@ bool PrimitiveProcessor::InitializeCommon( *(triangle_list_ptr++) = quad_first_index + 3; } } + if (builtin_ib_offset_triangle_strips_to_lists_ != SIZE_MAX) { + // Triangle strip to triangle list conversion. + // For each triangle in the strip (starting at vertex 2), emit + // 3 indices. Winding order alternates for each triangle. + uint16_t* triangle_list_ptr = + mapping_16bit + + builtin_ib_offset_triangle_strips_to_lists_ / + sizeof(uint16_t); + for (uint32_t i = 2; i < UINT16_MAX; ++i) { + if ((i & 1) == 0) { + // Even triangle: v0, v1, v2 + *(triangle_list_ptr++) = uint16_t(i - 2); + *(triangle_list_ptr++) = uint16_t(i - 1); + *(triangle_list_ptr++) = uint16_t(i); + } else { + // Odd triangle: v1, v0, v2 (swapped to maintain winding) + *(triangle_list_ptr++) = uint16_t(i - 1); + *(triangle_list_ptr++) = uint16_t(i - 2); + *(triangle_list_ptr++) = uint16_t(i); + } + } + } })) { ShutdownCommon(); return false; @@ -383,9 +421,13 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { case xenos::PrimitiveType::kLineList: case xenos::PrimitiveType::kLineStrip: case xenos::PrimitiveType::kTriangleList: - case xenos::PrimitiveType::kTriangleStrip: // Supported natively on all backends. break; + case xenos::PrimitiveType::kTriangleStrip: + if (convert_triangle_strips_to_lists_) { + host_primitive_type = xenos::PrimitiveType::kTriangleList; + } + break; case xenos::PrimitiveType::kRectangleList: if (expand_rectangle_lists_in_vs_) { host_primitive_type = xenos::PrimitiveType::kTriangleStrip; @@ -563,6 +605,17 @@ 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); + cacheable.index_buffer_type = + ProcessedIndexBufferType::kHostBuiltinForAuto; + assert_true(builtin_ib_offset_triangle_strips_to_lists_ != SIZE_MAX); + cacheable.host_index_buffer_handle = + builtin_ib_offset_triangle_strips_to_lists_; + break; default: assert_always(); return false; @@ -701,6 +754,9 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) { case xenos::PrimitiveType::kQuadList: host_index_count_getter = GetQuadListTriangleListIndexCount; break; + case xenos::PrimitiveType::kTriangleStrip: + host_index_count_getter = GetTriangleStripListIndexCount; + break; default: assert_unhandled_case(guest_primitive_type); return false; diff --git a/src/xenia/gpu/primitive_processor.h b/src/xenia/gpu/primitive_processor.h index d1150e638..b923b4a31 100644 --- a/src/xenia/gpu/primitive_processor.h +++ b/src/xenia/gpu/primitive_processor.h @@ -170,6 +170,9 @@ class PrimitiveProcessor { bool IsConvertingQuadListsToTriangleLists() const { return convert_quad_lists_to_triangle_lists_; } + bool IsConvertingTriangleStripsToLists() const { + return convert_triangle_strips_to_lists_; + } bool IsExpandingPointSpritesInVS() const { return expand_point_sprites_in_vs_; } @@ -623,6 +626,43 @@ class PrimitiveProcessor { } } + // 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 TriangleStripToList(Index* dest, const Index* source, + uint32_t source_index_count, + const IndexTransform& index_transform) { + if (source_index_count <= 2) { + // To match GetTriangleStripListIndexCount. + return; + } + // For triangle strips, the winding order alternates for each triangle. + // Even triangles: v0, v1, v2 + // Odd triangles: v1, v0, v2 (swapped to maintain consistent winding) + Index v0 = index_transform(source[0]); + Index v1 = index_transform(source[1]); + for (uint32_t i = 2; i < source_index_count; ++i) { + Index v2 = index_transform(source[i]); + if ((i & 1) == 0) { + // Even triangle (0, 2, 4, ...): v0, v1, v2 + *(dest++) = v0; + *(dest++) = v1; + *(dest++) = v2; + } else { + // Odd triangle (1, 3, 5, ...): v1, v0, v2 to maintain winding + *(dest++) = v1; + *(dest++) = v0; + *(dest++) = v2; + } + v0 = v1; + v1 = v2; + } + } + // Pre-gathering the ranges allows for usage of the same functions for // conversion with and without reset. In addition, this increases safety in // weird cases - there won't be mismatch between the pre-calculation of the @@ -685,6 +725,14 @@ class PrimitiveProcessor { dest_write_ptr += range_it->host_index_count; } break; + case xenos::PrimitiveType::kTriangleStrip: + for (PrimitiveRangeIterator range_it = ranges_beginning; + range_it != ranges_end; ++range_it) { + TriangleStripToList(dest_write_ptr, source + range_it->guest_offset, + range_it->guest_index_count, index_transform); + dest_write_ptr += range_it->host_index_count; + } + break; default: assert_unhandled_case(source_primitive_type); } @@ -699,6 +747,7 @@ class PrimitiveProcessor { bool convert_triangle_fans_to_lists_ = false; bool convert_line_loops_to_strips_ = false; bool convert_quad_lists_to_triangle_lists_ = false; + bool convert_triangle_strips_to_lists_ = false; bool expand_point_sprites_in_vs_ = false; bool expand_rectangle_lists_in_vs_ = false; @@ -706,6 +755,7 @@ class PrimitiveProcessor { size_t builtin_ib_offset_two_triangle_strips_ = SIZE_MAX; size_t builtin_ib_offset_triangle_fans_to_lists_ = SIZE_MAX; size_t builtin_ib_offset_quad_lists_to_triangle_lists_ = SIZE_MAX; + size_t builtin_ib_offset_triangle_strips_to_lists_ = SIZE_MAX; std::deque single_primitive_ranges_;