diff --git a/bin/resources/shaders/dx11/tfx.fx b/bin/resources/shaders/dx11/tfx.fx index ff7747436c..6a3e948fa5 100644 --- a/bin/resources/shaders/dx11/tfx.fx +++ b/bin/resources/shaders/dx11/tfx.fx @@ -1301,9 +1301,7 @@ VS_OUTPUT vs_main_expand(uint vid : SV_VertexID) uint vid_base = vid >> 2; bool is_bottom = vid & 2; bool is_right = vid & 1; - // All lines will be a pair of vertices next to each other - // Since DirectX uses provoking vertex first, the bottom point will be the lower of the two - uint vid_other = is_bottom ? vid_base + 1 : vid_base - 1; + uint vid_other = is_bottom ? vid_base - 1 : vid_base + 1; VS_OUTPUT vtx = vs_main(load_vertex(vid_base)); VS_OUTPUT other = vs_main(load_vertex(vid_other)); diff --git a/bin/resources/shaders/vulkan/tfx.glsl b/bin/resources/shaders/vulkan/tfx.glsl index 311782a4bb..aba5993ca4 100644 --- a/bin/resources/shaders/vulkan/tfx.glsl +++ b/bin/resources/shaders/vulkan/tfx.glsl @@ -174,11 +174,7 @@ void main() bool is_bottom = (vid & 2u) != 0u; bool is_right = (vid & 1u) != 0u; -#ifdef VS_PROVOKING_VERTEX_LAST uint vid_other = is_bottom ? vid_base - 1 : vid_base + 1; -#else - uint vid_other = is_bottom ? vid_base + 1 : vid_base - 1; -#endif vtx = load_vertex(vid_base); ProcessedVertex other = load_vertex(vid_other); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index df00c69f1d..1fd7c59e89 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -4791,21 +4791,10 @@ bool GSRendererHW::VerifyIndices() return false; // Expect each line to be a pair next to each other // VS expand relies on this! - if (g_gs_device->Features().provoking_vertex_last) + for (u32 i = 0; i < m_index.tail; i += 2) { - for (u32 i = 0; i < m_index.tail; i += 2) - { - if (m_index.buff[i] + 1 != m_index.buff[i + 1]) - return false; - } - } - else - { - for (u32 i = 0; i < m_index.tail; i += 2) - { - if (m_index.buff[i] != m_index.buff[i + 1] + 1) - return false; - } + if (m_index.buff[i] + 1 != m_index.buff[i + 1]) + return false; } break; case GS_TRIANGLE_CLASS: @@ -4818,6 +4807,71 @@ bool GSRendererHW::VerifyIndices() return true; } +// 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() +{ + // 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; + + 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 (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; + + // De-index the vertices either in place or by reallocating the vertex buffer. + if (m_vertex.next <= m_index.tail && m_index.tail <= m_vertex.maxcount) + { + // De-index in place + for (int i = static_cast(m_index.tail) - 1; i >= 0; i--) + { + // FIXME: This might not hold with a large triangle fan with gaps (since gaps are not + // yet removed)! + pxAssert(m_index.buff[i] <= i); // At any point, there can never be more vertices than indices + m_vertex.buff[i] = m_vertex.buff[m_index.buff[i]]; + m_index.buff[i] = static_cast(i); + } + } + else + { + // Reallocate the vertex buffer + m_vertex.maxcount = std::max(m_vertex.maxcount, m_index.tail); + GSVertex* vert_buff = static_cast(_aligned_malloc(sizeof(GSVertex) * m_vertex.maxcount, 32)); + + // De-index and copy the vertices + for (u32 i = 0; i < m_index.tail; i++) + { + vert_buff[i] = m_vertex.buff[m_index.buff[i]]; + m_index.buff[i] = static_cast(i); + } + std::swap(vert_buff, m_vertex.buff); + _aligned_free(vert_buff); + } + 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 + } +} + void GSRendererHW::SetupIA(float target_scale, float sx, float sy, bool req_vert_backup) { GL_PUSH("HW: IA"); @@ -7899,6 +7953,8 @@ __ri void GSRendererHW::DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Ta m_conf.drawarea = m_channel_shuffle ? scissor : scissor.rintersect(ComputeBoundingBox(rtsize, rtscale)); m_conf.scissor = (DATE && !DATE_BARRIER) ? m_conf.drawarea : scissor; + HandleProvokingVertexFirst(); + SetupIA(rtscale, sx, sy, m_channel_shuffle_width != 0); if (ate_second_pass) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index aea9cb7ef1..ce34d3edfc 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -92,6 +92,7 @@ private: void DrawPrims(GSTextureCache::Target* rt, GSTextureCache::Target* ds, GSTextureCache::Source* tex, const TextureMinMaxResult& tmm); void ResetStates(); + void HandleProvokingVertexFirst(); void SetupIA(float target_scale, float sx, float sy, bool req_vert_backup); void EmulateTextureShuffleAndFbmask(GSTextureCache::Target* rt, GSTextureCache::Source* tex); bool EmulateChannelShuffle(GSTextureCache::Target* src, bool test_only, GSTextureCache::Target* rt = nullptr); diff --git a/pcsx2/GS/Renderers/Metal/tfx.metal b/pcsx2/GS/Renderers/Metal/tfx.metal index 3e364e3558..bdbdafb88f 100644 --- a/pcsx2/GS/Renderers/Metal/tfx.metal +++ b/pcsx2/GS/Renderers/Metal/tfx.metal @@ -233,9 +233,7 @@ vertex MainVSOut vs_main_expand( uint vid_base = vid >> 2; bool is_bottom = vid & 2; bool is_right = vid & 1; - // All lines will be a pair of vertices next to each other - // Since Metal uses provoking vertex first, the bottom point will be the lower of the two - uint vid_other = is_bottom ? vid_base + 1 : vid_base - 1; + uint vid_other = is_bottom ? vid_base - 1 : vid_base + 1; MainVSOut point = vs_main_run(load_vertex(vertices[vid_base]), cb); MainVSOut other = vs_main_run(load_vertex(vertices[vid_other]), cb); diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h index 606941d76b..479925478b 100644 --- a/pcsx2/ShaderCacheVersion.h +++ b/pcsx2/ShaderCacheVersion.h @@ -3,4 +3,4 @@ /// Version number for GS and other shaders. Increment whenever any of the contents of the /// shaders change, to invalidate the cache. -static constexpr u32 SHADER_CACHE_VERSION = 68; +static constexpr u32 SHADER_CACHE_VERSION = 69;