Skip to content

Commit 4996771

Browse files
committed
GS: Use triangles-are-quads detection in primitive overlap detection.
1 parent b9f771c commit 4996771

File tree

2 files changed

+131
-106
lines changed

2 files changed

+131
-106
lines changed

pcsx2/GS/GSState.cpp

Lines changed: 122 additions & 104 deletions
Original file line numberDiff line numberDiff line change
@@ -2102,6 +2102,7 @@ void GSState::FlushPrim()
21022102
// Skip draw if Z test is enabled, but set to fail all pixels.
21032103
const bool skip_draw = (m_context->TEST.ZTE && m_context->TEST.ZTST == ZTST_NEVER);
21042104
m_quad_check_valid = false;
2105+
m_quad_check_valid_shuffle = false;
21052106
m_drawlist.clear();
21062107
m_drawlist_bbox.clear();
21072108

@@ -3306,135 +3307,149 @@ void GSState::GrowVertexBuffer()
33063307
m_vertex.maxcount = maxcount - 3; // -3 to have some space at the end of the buffer before DrawingKick can grow it
33073308
}
33083309

3309-
bool GSState::TrianglesAreQuads(bool shuffle_check)
3310+
template<bool shuffle_check>
3311+
bool GSState::TrianglesAreQuadsImpl()
33103312
{
3311-
// If this is a quad, there should only be two distinct values for both X and Y, which
3312-
// also happen to be the minimum/maximum bounds of the primitive.
3313-
if (!shuffle_check && m_quad_check_valid)
3314-
return m_are_quads;
3313+
// are_quads: triangles form axis-aligned quads and they line up end-to-end.
3314+
// are_quads_indiv: every 2 triangles forms an axis-aligned quad (any overlap between quads).
3315+
// When doing shuffle check, are_quads_indiv is not considered. In a shuffle check we want the bboxes
3316+
// to line up end-to-end when we shift the coordinates by 8 pixels horizontally.
3317+
// Special case: when only 2 triangles, the quad need not be axis aligned.
3318+
3319+
bool& quad_check_valid = shuffle_check ? m_quad_check_valid_shuffle : m_quad_check_valid;
3320+
bool& are_quads = shuffle_check ? m_are_quads_shuffle : m_are_quads;
3321+
bool& are_quads_indiv = m_are_quads_indiv;
33153322

3316-
const GSVertex* const v = m_vertex.buff;
3317-
m_are_quads = false;
3318-
m_quad_check_valid = !shuffle_check;
3323+
// Check if the result is cached.
3324+
if (quad_check_valid)
3325+
return are_quads;
33193326

3320-
for (u32 idx = 0; idx < m_index.tail; idx += 6)
3327+
quad_check_valid = true;
3328+
are_quads = true;
3329+
if constexpr (!shuffle_check)
3330+
are_quads_indiv = true;
3331+
3332+
if (m_index.tail % 6 != 0)
33213333
{
3322-
const u16* const i = m_index.buff + idx;
3334+
are_quads = false;
3335+
if constexpr (!shuffle_check)
3336+
are_quads_indiv = false;
3337+
return false;
3338+
}
3339+
3340+
constexpr GSVector4i offset = shuffle_check ? GSVector4i::cxpr(8 << 4, 0, 8 << 4, 0) : GSVector4i::cxpr(0);
33233341

3324-
// Make sure the next set of triangles matches an edge of the previous triangle.
3325-
if (idx > 0)
3342+
const GSVertex* RESTRICT v = m_vertex.buff;
3343+
const u16* RESTRICT index = m_index.buff;
3344+
const size_t count = m_index.tail;
3345+
3346+
if (m_index.tail == 6)
3347+
{
3348+
// Non-axis aligned check when only two triangles
3349+
are_quads = GSUtil::AreTrianglesQuadNonAA(v, &index[0], &index[3]);
3350+
if constexpr (!shuffle_check)
3351+
are_quads_indiv = are_quads;
3352+
}
3353+
else
3354+
{
3355+
GSVector4i prev_bbox;
3356+
3357+
for (u32 i = 0; i < count; i += 6)
33263358
{
3327-
const u16* const prev_tri= m_index.buff + (idx - 3);
3328-
GIFRegXYZ new_verts[3] = {v[i[0]].XYZ, v[i[1]].XYZ, v[i[2]].XYZ};
3359+
const u16* RESTRICT idx0 = &index[i + 0];
3360+
const u16* RESTRICT idx1 = &index[i + 3];
3361+
GSUtil::TriangleOrdering tri0;
3362+
GSUtil::TriangleOrdering tri1;
3363+
3364+
GSVector4i bbox;
33293365

3330-
if (shuffle_check)
3366+
// If the first 2 pairs of triangles form axis-aligned quads, assume the rest do also.
3367+
if (i < 12)
33313368
{
3332-
new_verts[0].X -= 8 << 4;
3333-
new_verts[1].X -= 8 << 4;
3334-
new_verts[2].X -= 8 << 4;
3335-
}
3336-
u32 match_vert_count = 0;
33373369

3338-
if (!(new_verts[0] != m_vertex.buff[prev_tri[0]].XYZ && new_verts[0] != m_vertex.buff[prev_tri[1]].XYZ && new_verts[0] != m_vertex.buff[prev_tri[2]].XYZ))
3339-
match_vert_count++;
3340-
if (!(new_verts[1] != m_vertex.buff[prev_tri[0]].XYZ && new_verts[1] != m_vertex.buff[prev_tri[1]].XYZ && new_verts[1] != m_vertex.buff[prev_tri[2]].XYZ))
3341-
match_vert_count++;
3342-
if (!(new_verts[2] != m_vertex.buff[prev_tri[0]].XYZ && new_verts[2] != m_vertex.buff[prev_tri[1]].XYZ && new_verts[2] != m_vertex.buff[prev_tri[2]].XYZ))
3343-
match_vert_count++;
3370+
if (!GSUtil::AreTrianglesQuad<0, 0>(v, idx0, idx1, &tri0, &tri1))
3371+
{
3372+
are_quads = false;
3373+
if constexpr (!shuffle_check)
3374+
are_quads_indiv = false;
3375+
break;
3376+
}
33443377

3345-
if (match_vert_count != 2)
3346-
return false;
3347-
}
3348-
// Degenerate triangles should've been culled already, so we can check indices.
3349-
// This doesn't really make much sense when it's a triangle strip as it will always have 1 extra vert, so check for distinct values for them.
3350-
if (PRIM->PRIM != GS_TRIANGLESTRIP)
3351-
{
3352-
u32 extra_verts = 0;
3353-
for (u32 j = 3; j < 6; j++)
3378+
// tri.b is right angle corner
3379+
GSVector4i corner0 = GSVector4i(v[idx0[tri0.b]].m[1]).upl16().xyxy();
3380+
GSVector4i corner1 = GSVector4i(v[idx1[tri1.b]].m[1]).upl16().xyxy();
3381+
bbox = corner0.runion(corner1);
3382+
}
3383+
else if (are_quads)
33543384
{
3355-
const u16 tri2_idx = i[j];
3356-
if (tri2_idx != i[0] && tri2_idx != i[1] && tri2_idx != i[2])
3357-
extra_verts++;
3385+
// Two quads are already encountered so just get the bbox here.
3386+
bbox = GSVector4i(v[idx0[0]].m[1]).upl16().xyxy();
3387+
bbox = bbox.runion(GSVector4i(v[idx0[1]].m[1]).upl16().xyxy());
3388+
bbox = bbox.runion(GSVector4i(v[idx0[2]].m[1]).upl16().xyxy());
33583389
}
3359-
if (extra_verts == 1)
3360-
continue;
3361-
}
3362-
else if (m_index.tail == 6)
3363-
{
3364-
bool shared_vert_found = false;
3365-
for (int i = 0; i < 3; i++)
3390+
else
33663391
{
3367-
for (int j = 3; j < 6; j++)
3368-
if (m_vertex.buff[m_index.buff[i]].XYZ.X == m_vertex.buff[m_index.buff[j]].XYZ.X &&
3369-
m_vertex.buff[m_index.buff[i]].XYZ.Y == m_vertex.buff[m_index.buff[j]].XYZ.Y)
3370-
{
3371-
shared_vert_found = true;
3372-
break;
3373-
}
3392+
// Early exit if are_quads already failed and the first 2 pairs of triangles are quads.
3393+
// Means that are_quads_indiv will be true.
3394+
break;
33743395
}
3375-
3376-
// At least one vert should be shared across otherwise it's 2 separate triangles (false positive from Tales of Destiny).
3377-
if (!shared_vert_found)
3378-
return false;
3379-
3380-
const int first_X = m_vertex.buff[m_index.buff[0]].XYZ.X;
3381-
const int first_Y = m_vertex.buff[m_index.buff[0]].XYZ.Y;
3382-
const int second_X = m_vertex.buff[m_index.buff[1]].XYZ.X;
3383-
const int second_Y = m_vertex.buff[m_index.buff[1]].XYZ.Y;
3384-
const int third_X = m_vertex.buff[m_index.buff[2]].XYZ.X;
3385-
const int third_Y = m_vertex.buff[m_index.buff[2]].XYZ.Y;
3386-
const int new_X = m_vertex.buff[m_index.buff[5]].XYZ.X;
3387-
const int new_Y = m_vertex.buff[m_index.buff[5]].XYZ.Y;
3388-
3389-
const int middle_Y = (second_Y >= third_Y) ? (third_Y + ((second_Y - third_Y) / 2)) : (second_Y + ((third_Y - second_Y) / 2));
3390-
const int middle_X = (second_X >= third_X) ? (third_X + ((second_X - third_X) / 2)) : (second_X + ((third_X - second_X) / 2));
3391-
const bool first_lt_X = first_X <= middle_X;
3392-
const bool first_lt_Y = first_Y <= middle_Y;
3393-
const bool new_lt_X = new_X <= middle_X;
3394-
const bool new_lt_Y = new_Y <= middle_Y;
3395-
3396-
// Check if verts are on the same side. Not totally accurate, but should be good enough.
3397-
if (first_lt_X == new_lt_X && new_lt_Y == first_lt_Y)
3398-
return false;
3399-
3400-
m_prim_overlap = PRIM_OVERLAP_NO;
3401-
break;
3402-
}
34033396

3404-
// As a fallback, they might've used different vertices with a tri list, not strip.
3405-
// Note that this won't work unless the quad is axis-aligned.
3406-
u16 distinct_x_values[2] = {v[i[0]].XYZ.X};
3407-
u16 distinct_y_values[2] = {v[i[0]].XYZ.Y};
3408-
u32 num_distinct_x_values = 1, num_distinct_y_values = 1;
3409-
for (u32 j = 1; j < 6; j++)
3410-
{
3411-
const GSVertex& jv = v[i[j]];
3412-
if (jv.XYZ.X != distinct_x_values[0] && jv.XYZ.X != distinct_x_values[1])
3397+
if (are_quads && i > 0)
34133398
{
3414-
if (num_distinct_x_values > 1)
3415-
return false;
3399+
GSVector4i bbox_offset = bbox - offset;
34163400

3417-
distinct_x_values[num_distinct_x_values++] = jv.XYZ.X;
3401+
// Consider two quads exactly on top of each other to be non-overlapping.
3402+
// This is technically incorrect but it does not seems to affect anything
3403+
// negatively and allows mem-clears with redundant quads to be detected.
3404+
if (GSVector4::cast(bbox_offset == prev_bbox).mask() != 0xF)
3405+
{
3406+
// Check that the two bboxes have exactly 1 edge in common.
3407+
int m = GSVector4::cast(bbox_offset == prev_bbox).mask();
3408+
bool valign = (m & 0b0101) == 0b0101; // X-range identical.
3409+
bool halign = (m & 0b1010) == 0b1010; // Y-range identical.
3410+
int vadj = GSVector4::cast(bbox_offset.ywyw() == prev_bbox.wywy()).mask() & 3;
3411+
int hadj = GSVector4::cast(bbox_offset.xzxz() == prev_bbox.zxzx()).mask() & 3;
3412+
3413+
bool adjacent =
3414+
(halign && (hadj == 0b01 || hadj == 0b10)) || // Quads share vertical edge.
3415+
(valign && (vadj == 0b01 || vadj == 0b10)); // Quads share horizontal edge.
3416+
3417+
if (!adjacent)
3418+
{
3419+
are_quads = false;
3420+
}
3421+
}
34183422
}
34193423

3420-
if (jv.XYZ.Y != distinct_y_values[0] && jv.XYZ.Y != distinct_y_values[1])
3421-
{
3422-
if (num_distinct_y_values > 1)
3423-
return false;
3424+
if (!are_quads && (shuffle_check || !are_quads_indiv))
3425+
break;
34243426

3425-
distinct_y_values[num_distinct_y_values++] = jv.XYZ.Y;
3426-
}
3427+
prev_bbox = bbox;
34273428
}
34283429
}
34293430

3430-
m_are_quads = true;
3431-
return true;
3431+
return are_quads;
3432+
}
3433+
3434+
bool GSState::TrianglesAreQuads(bool shuffle_check)
3435+
{
3436+
return shuffle_check ? TrianglesAreQuadsImpl<true>() : TrianglesAreQuadsImpl<false>();
34323437
}
34333438

3434-
template<u32 primclass>
3439+
bool GSState::TrianglesAreQuadsIndiv()
3440+
{
3441+
TrianglesAreQuadsImpl<false>();
3442+
3443+
return m_are_quads_indiv;
3444+
}
3445+
3446+
template<u32 primclass, bool quads>
34353447
GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlist, bool save_bbox, float bbox_scale)
34363448
{
3449+
static_assert(primclass == GS_TRIANGLE_CLASS || !quads); // quads only valid for triangles.
3450+
34373451
constexpr int n = GSUtil::GetClassVertexCount(primclass);
3452+
constexpr int quad_factor = quads ? 2 : 1; // Skip every other triangle when they are quads.
34383453

34393454
// We should should only have to compute the drawlist/bboxes once per draw.
34403455
pxAssert(!save_drawlist || m_drawlist.empty());
@@ -3484,7 +3499,7 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlistImpl(bool save_drawlis
34843499

34853500
all = all.runion(prim);
34863501

3487-
j += n;
3502+
j += quad_factor * n;
34883503
}
34893504

34903505
if (save_drawlist)
@@ -3521,11 +3536,14 @@ GSState::PRIM_OVERLAP GSState::GetPrimitiveOverlapDrawlist(bool save_drawlist, b
35213536
case GS_LINE_CLASS:
35223537
return GetPrimitiveOverlapDrawlistImpl<GS_LINE_CLASS>(save_drawlist, save_bbox, bbox_scale);
35233538
case GS_TRIANGLE_CLASS:
3524-
return GetPrimitiveOverlapDrawlistImpl<GS_TRIANGLE_CLASS>(save_drawlist, save_bbox, bbox_scale);
3539+
if (m_quad_check_valid && m_are_quads_indiv)
3540+
return GetPrimitiveOverlapDrawlistImpl<GS_TRIANGLE_CLASS, true>(save_drawlist, save_bbox, bbox_scale);
3541+
else
3542+
return GetPrimitiveOverlapDrawlistImpl<GS_TRIANGLE_CLASS, false>(save_drawlist, save_bbox, bbox_scale);
35253543
case GS_SPRITE_CLASS:
35263544
return GetPrimitiveOverlapDrawlistImpl<GS_SPRITE_CLASS>(save_drawlist, save_bbox, bbox_scale);
35273545
default:
3528-
pxFail("Invalid prim class."); // Impossible.
3546+
pxFail("Invalid primclass."); // Impossible.
35293547
return PRIM_OVERLAP_UNKNOW;
35303548
}
35313549
}

pcsx2/GS/GSState.h

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,10 @@ class GSState : public GSAlignedClass<32>
238238
std::unique_ptr<GSDumpBase> m_dump;
239239
bool m_scissor_invalid = false;
240240
bool m_quad_check_valid = false;
241+
bool m_quad_check_valid_shuffle = false;
241242
bool m_are_quads = false;
243+
bool m_are_quads_indiv = false;
244+
bool m_are_quads_shuffle = false;
242245
bool m_nativeres = false;
243246
bool m_mipmap = false;
244247
bool m_texflush_flag = false;
@@ -447,9 +450,13 @@ class GSState : public GSAlignedClass<32>
447450
void DumpVertices(const std::string& filename);
448451
void DumpTransferList(const std::string& filename);
449452
void DumpTransferImages();
450-
453+
454+
template<bool shuffle_check>
455+
bool TrianglesAreQuadsImpl();
456+
bool TrianglesAreQuads0(bool shuffle_check = false);
451457
bool TrianglesAreQuads(bool shuffle_check = false);
452-
template <u32 primclass>
458+
bool TrianglesAreQuadsIndiv();
459+
template <u32 primclass, bool quads = false>
453460
PRIM_OVERLAP GetPrimitiveOverlapDrawlistImpl(bool save_drawlist = false, bool save_bbox = false, float bbox_scale = 1.0f);
454461
PRIM_OVERLAP GetPrimitiveOverlapDrawlist(bool save_drawlist = false, bool save_bbox = false, float bbox_scale = 1.0f);
455462
PRIM_OVERLAP PrimitiveOverlap(bool save_drawlist = false);

0 commit comments

Comments
 (0)