GS: Add secondary vertex buffer for copy/modifying vertices.

Currently only used in HW renderer to fix vertices for provoking-first-vertex APIs.
This commit is contained in:
TJnotJT 2025-07-08 12:11:49 -04:00
parent 14c7f959f5
commit 08ede10204
3 changed files with 51 additions and 70 deletions

View File

@ -125,6 +125,8 @@ GSState::~GSState()
{
if (m_vertex.buff)
_aligned_free(m_vertex.buff);
if (m_vertex.buff_copy)
_aligned_free(m_vertex.buff_copy);
if (m_index.buff)
_aligned_free(m_index.buff);
if (m_draw_vertex.buff)
@ -2859,56 +2861,53 @@ void GSState::UpdateVertexKick()
void GSState::GrowVertexBuffer()
{
const u32 maxcount = std::max<u32>(m_vertex.maxcount * 3 / 2, 10000);
const u32 old_vertex_size = sizeof(GSVertex) * m_vertex.tail;
const u32 new_vertex_size = sizeof(GSVertex) * maxcount;
const u32 old_index_size = sizeof(u16) * m_index.tail;
const u32 new_index_size = sizeof(u16) * maxcount * 6; // Worst case index list is a list of points with vs expansion, 6 indices per point
GSVertex* vertex = static_cast<GSVertex*>(_aligned_malloc(sizeof(GSVertex) * maxcount, 32));
GSVertex* draw_vertex = static_cast<GSVertex*>(_aligned_malloc(sizeof(GSVertex) * maxcount, 32));
// Worst case index list is a list of points with vs expansion, 6 indices per point
u16* index = static_cast<u16*>(_aligned_malloc(sizeof(u16) * maxcount * 6, 32));
u16* draw_index = static_cast<u16*>(_aligned_malloc(sizeof(u16) * maxcount * 6, 32));
if (!vertex || !index)
// Structure describing buffers to reallocate
struct AllocDesc
{
const u32 vert_byte_count = sizeof(GSVertex) * maxcount;
const u32 idx_byte_count = sizeof(u16) * maxcount * 3;
void** pbuff;
u32 old_size;
u32 new_size;
};
std::vector<AllocDesc> alloc_desc = {
{reinterpret_cast<void**>(&m_vertex.buff), old_vertex_size, new_vertex_size},
// discard contents of buff_copy by setting old_size = 0
{reinterpret_cast<void**>(&m_vertex.buff_copy), 0, new_vertex_size},
{reinterpret_cast<void**>(&m_draw_vertex.buff), old_vertex_size, new_vertex_size},
{reinterpret_cast<void**>(&m_index.buff), old_index_size, new_index_size},
{reinterpret_cast<void**>(&m_draw_index.buff), old_index_size, new_index_size}
};
Console.Error("GS: failed to allocate %zu bytes for vertices and %zu for indices.",
vert_byte_count, idx_byte_count);
pxFailRel("Memory allocation failed");
// For logging
u32 total_size = 0;
for (const auto& desc : alloc_desc)
total_size += desc.new_size;
// Reallocate each of the needed buffers
for (const auto [pbuff, old_size, new_size] : alloc_desc)
{
void* new_buff = _aligned_malloc(new_size, 32);
if (!new_buff)
{
Console.Error("GS: failed to allocate %zu bytes for vertices and indices.", total_size);
pxFailRel("Memory allocation failed");
}
if (*pbuff)
{
if (old_size)
{
std::memcpy(new_buff, *pbuff, old_size);
}
_aligned_free(*pbuff);
}
*pbuff = new_buff;
}
if (m_vertex.buff)
{
std::memcpy(vertex, m_vertex.buff, sizeof(GSVertex) * m_vertex.tail);
_aligned_free(m_vertex.buff);
}
if (m_index.buff)
{
std::memcpy(index, m_index.buff, sizeof(u16) * m_index.tail);
_aligned_free(m_index.buff);
}
if (m_draw_vertex.buff)
{
std::memcpy(draw_vertex, m_draw_vertex.buff, sizeof(GSVertex) * m_vertex.tail);
_aligned_free(m_draw_vertex.buff);
}
if (m_draw_index.buff)
{
std::memcpy(draw_index, m_draw_index.buff, sizeof(u16) * m_index.tail);
_aligned_free(m_draw_index.buff);
}
m_draw_vertex.buff = draw_vertex;
m_draw_index.buff = draw_index;
m_vertex.buff = vertex;
m_vertex.maxcount = maxcount - 3; // -3 to have some space at the end of the buffer before DrawingKick can grow it
m_index.buff = index;
}
bool GSState::TrianglesAreQuads(bool shuffle_check)

View File

@ -136,6 +136,7 @@ protected:
struct
{
GSVertex* buff;
GSVertex* buff_copy; // same size buffer to copy/modify the original buffer
u32 head, tail, next, maxcount; // head: first vertex, tail: last vertex + 1, next: last indexed + 1
u32 xy_tail;
GSVector4i xy[4];

View File

@ -4834,34 +4834,15 @@ void GSRendererHW::HandleProvokingVertexFirst()
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 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--)
{
// De-index in place
for (int i = static_cast<int>(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<u16>(i);
}
}
else
{
// Reallocate the vertex buffer
m_vertex.maxcount = std::max(m_vertex.maxcount, m_index.tail);
GSVertex* vert_buff = static_cast<GSVertex*>(_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<u16>(i);
}
std::swap(vert_buff, m_vertex.buff);
_aligned_free(vert_buff);
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