[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 <reality@xenios.jp>
This commit is contained in:
goldislead
2026-08-08 09:43:00 -07:00
committed by Radosław Gliński
parent 77597d62ae
commit 3eab2b8b39
2 changed files with 75 additions and 11 deletions

View File

@@ -296,17 +296,18 @@ bool PrimitiveProcessor::Process(ProcessingResult& result_out) {
regs.Get<reg::VGT_HOS_CNTL>().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<uint16_t*>(
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<uint16_t*>(
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;

View File

@@ -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 <typename Index, typename IndexTransform>
static void TriangleFanToList(Index* dest, const Index* source,
uint32_t source_index_count,