Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 55 additions & 33 deletions pcsx2/GS/Renderers/HW/GSRendererHW.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4976,46 +4976,60 @@ bool GSRendererHW::VerifyIndices()
// 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
// which is "provoking last vertex").
void GSRendererHW::HandleProvokingVertexFirst()
void GSRendererHW::HandleProvokingVertexFirst(bool swap_indices)
{
// Early exit conditions:
if (g_gs_device->Features().provoking_vertex_last || // device supports provoking last vertex
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;
pxAssertRel(!g_gs_device->Features().provoking_vertex_last && !m_conf.vs.iip,
"Should not call HandleProvokingVertexFirst() when API uses provoking vertex last or interpolating colors.");

const int n = GSUtil::GetClassVertexCount(m_vt.m_primclass);

// 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)
if (swap_indices)
{
// 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
{
if (m_vertex.buff[m_index.buff[i]].RGBAQ.U32[0] != m_vertex.buff[m_index.buff[i + n - 1]].RGBAQ.U32[0])
// Slow path: de-index and swap the vertex colors. Used in cases where the drawing order matters (lines).
GL_PUSH("HW: Rewriting vertices for provoking vertex first.");
if (m_index.tail > UINT16_MAX)
{
first_eq_last = false;
break;
GL_INS("HW: Too many vertices to rewrite safely, exiting.");
DevCon.Warning("HW: Too many vertices to rewrite safely for provoking vertex first.");
return;
}
}
if (first_eq_last)
return;

// De-index the vertices using the copy buffer
while (m_vertex.maxcount < m_index.tail)
GrowVertexBuffer();
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_index.buff[i] = static_cast<u16>(i);
}
std::swap(m_vertex.buff, m_vertex.buff_copy);
m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail;
// 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)
{
if (m_vertex.buff[m_index.buff[i]].RGBAQ.U32[0] != m_vertex.buff[m_index.buff[i + n - 1]].RGBAQ.U32[0])
{
first_eq_last = false;
break;
}
}
if (first_eq_last)
return;

// Put correct color in the first vertex
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 + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
// De-index the vertices using the copy buffer
while (m_vertex.maxcount < m_index.tail)
GrowVertexBuffer();
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_index.buff[i] = static_cast<u16>(i);
}
std::swap(m_vertex.buff, m_vertex.buff_copy);
m_vertex.head = m_vertex.next = m_vertex.tail = m_index.tail;

// Put correct color in the first vertex
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 + n - 1].RGBAQ.U32[0] = 0xff; // Make last vertex red for debugging if used improperly
}
}
}

Expand Down Expand Up @@ -5089,6 +5103,13 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
ExpandLineIndices();
}
}

if (!features.provoking_vertex_last && !m_conf.vs.iip)
{
// 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;

Expand Down Expand Up @@ -5147,6 +5168,9 @@ void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert
GSVector4::store<true>(&v[i].ST, v_st);
}
}

if (!features.provoking_vertex_last && !m_conf.vs.iip)
HandleProvokingVertexFirst(true); // Drawing order does not matter for triangles so just swap first/last indices.
}
break;

Expand Down Expand Up @@ -8461,8 +8485,6 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta
m_conf.drawlist_bbox = &m_drawlist_bbox;
}

HandleProvokingVertexFirst();

SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0);

StartDepthAsRTFeedback(); // Depends on the drawarea and alpha test having been determined.
Expand Down
2 changes: 1 addition & 1 deletion pcsx2/GS/Renderers/HW/GSRendererHW.h
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,7 @@ class GSRendererHW : public GSRenderer
void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm);

void ResetStates();
void HandleProvokingVertexFirst();
void HandleProvokingVertexFirst(bool swap_indices);
void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup);
void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex);
u32 EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr);
Expand Down
Loading