GS/HW: Allow swapping indices to handle provoking vertex first APIs.

Avoids doing an extra copy of the whole vertex queue when its not needed.

Approach suggested by TellowKrinkle.
This commit is contained in:
TJnotJT 2025-12-08 13:50:11 -05:00
parent e9977f2a2c
commit f9e53c76fd
2 changed files with 49 additions and 34 deletions

View File

@ -4922,46 +4922,53 @@ bool GSRendererHW::VerifyIndices()
// Fix the colors in vertices in case the API only supports "provoking first vertex" // Fix the colors in vertices in case the API only supports "provoking first vertex"
// (i.e., when using flat shading the color comes from the first vertex, unlike PS2 // (i.e., when using flat shading the color comes from the first vertex, unlike PS2
// which is "provoking last vertex"). // which is "provoking last vertex").
void GSRendererHW::HandleProvokingVertexFirst() void GSRendererHW::HandleProvokingVertexFirst(bool swap_indices)
{ {
// Early exit conditions: pxAssertRel(!g_gs_device->Features().provoking_vertex_last && !m_conf.vs.iip,
if (g_gs_device->Features().provoking_vertex_last || // device supports provoking last vertex "Should not call HandleProvokingVertexFirst() when API uses provoking vertex last or interpolating colors.");
m_conf.vs.iip || // we are doing Gouraud shading
m_vt.m_primclass == GS_POINT_CLASS || // drawing points (one vertex per primitive; color is unambiguous)
m_vt.m_primclass == GS_SPRITE_CLASS) // drawing sprites (handled by the sprites -> triangles expand shader)
return;
const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass); const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass);
// If all first/last vertices have the same color there is nothing to do. if (swap_indices)
bool first_eq_last = true;
for (u32 i = 0; i < m_index.tail; i += n)
{ {
if (m_vertex.buff[m_index.buff[i]].RGBAQ.U32[0] != m_vertex.buff[m_index.buff[i + n - 1]].RGBAQ.U32[0]) // Fast path: just swap the indices. Used in cases where drawing order does not matter (triangles, expanded lines).
for (u32 i = 0; i < m_index.tail; i += n)
std::swap(m_index.buff[i], m_index.buff[i + n - 1]);
}
else
{
// Slow path: de-index and swap the vertex colors. Used in cases where the drawing order matters (lines).
// If all first/last vertices have the same color there is nothing to do.
bool first_eq_last = true;
for (u32 i = 0; i < m_index.tail; i += n)
{ {
first_eq_last = false; if (m_vertex.buff[m_index.buff[i]].RGBAQ.U32[0] != m_vertex.buff[m_index.buff[i + n - 1]].RGBAQ.U32[0])
break; {
first_eq_last = false;
break;
}
} }
} if (first_eq_last)
if (first_eq_last) return;
return;
// De-index the vertices using the copy buffer // De-index the vertices using the copy buffer
while (m_vertex.maxcount < m_index.tail) while (m_vertex.maxcount < m_index.tail)
GrowVertexBuffer(); GrowVertexBuffer();
for (int i = static_cast<int>(m_index.tail) - 1; i >= 0; i--) for (int i = static_cast<int>(m_index.tail) - 1; i >= 0; i--)
{ {
m_vertex.buff_copy[i] = m_vertex.buff[m_index.buff[i]]; m_vertex.buff_copy[i] = m_vertex.buff[m_index.buff[i]];
m_index.buff[i] = static_cast<u16>(i); m_index.buff[i] = static_cast<u16>(i);
} }
std::swap(m_vertex.buff, m_vertex.buff_copy); std::swap(m_vertex.buff, m_vertex.buff_copy);
m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail; m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail;
// Put correct color in the first vertex // Put correct color in the first vertex
for (u32 i = 0; i < m_index.tail; i += n) for (u32 i = 0; i < m_index.tail; i += n)
{ {
m_vertex.buff[i].RGBAQ.U32[0] = m_vertex.buff[i + n - 1].RGBAQ.U32[0]; m_vertex.buff[i].RGBAQ.U32[0] = m_vertex.buff[i + n - 1].RGBAQ.U32[0];
m_vertex.buff[i + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly m_vertex.buff[i + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
}
} }
} }
@ -5035,6 +5042,13 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
ExpandLineIndices(); ExpandLineIndices();
} }
} }
if (!features.provoking_vertex_last && !m_conf.vs.iip) // Flat shaded colors and API uses provoking vertex first
{
// Expanded lines are converted to triangles where drawing order does not matter so just swap first/last indices.
const bool swap_indices = m_conf.line_expand || (m_conf.vs.expand != GSHWDrawConfig::VSExpand::None);
HandleProvokingVertexFirst(swap_indices);
}
} }
break; break;
@ -5093,6 +5107,9 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
GSVector4::store<true>(&v[i].ST, v_st); GSVector4::store<true>(&v[i].ST, v_st);
} }
} }
if (!features.provoking_vertex_last && !m_conf.vs.iip) // Flat shaded colors and API uses provoking vertex first
HandleProvokingVertexFirst(true); // Drawing order does not matter for triangles so just swap first/last indices.
} }
break; break;
@ -8031,8 +8048,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.drawarea = m_channel_shuffle ? scissor : scissor.rintersect(ComputeBoundingBox(rtsize, rtscale)); m_conf.drawarea = m_channel_shuffle ? scissor : scissor.rintersect(ComputeBoundingBox(rtsize, rtscale));
m_conf.scissor = (DATE && !DATE_BARRIER) ? m_conf.drawarea : scissor; m_conf.scissor = (DATE && !DATE_BARRIER) ? m_conf.drawarea : scissor;
HandleProvokingVertexFirst();
SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0); SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0);
if (ate_second_pass) if (ate_second_pass)

View File

@ -93,7 +93,7 @@ private:
void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm); void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm);
void ResetStates(); void ResetStates();
void HandleProvokingVertexFirst(); void HandleProvokingVertexFirst(bool swap_indices);
void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup); void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup);
void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex); void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex);
bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr); bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr);