GS/HW: Fixes/improvements to early scissor/bbox test.

This commit is contained in:
TJnotJT
2026-05-09 13:48:42 +02:00
committed by lightningterror
parent fc90973509
commit 4fa2b8e45d
2 changed files with 32 additions and 25 deletions
+2 -2
View File
@@ -91,8 +91,8 @@ void GSDrawingContext::UpdateScissor()
scissor.in = rscissor + GSVector4i::cxpr(0, 0, 1, 1);
// Fixed-point scissor min/max, used for rejecting primitives which are entirely outside.
// Bottom-right end point should be exclusive for rectangle intersection test to work correctly.
scissor.cull = rscissor.sll32<4>() + GSVector4i::cxpr(0, 0, 1, 1);
// Add half a pixel around the edges for upscaling and lines/points.
scissor.cull = rscissor.sll32<4>() + GSVector4i(-8, -8, 8, 8);
// Offset applied to vertices for culling.
scissor.xyof = GSVector4i::loadl(&XYOFFSET.U64).xyxy();
+30 -23
View File
@@ -5930,39 +5930,46 @@ __forceinline void GSState::VertexKick(u32 skip)
bbox = v0.runion(v1).runion(v2);
}
if (m_nativeres && (primclass == GS_TRIANGLE_CLASS || primclass == GS_SPRITE_CLASS))
if constexpr (primclass == GS_TRIANGLE_CLASS || primclass == GS_SPRITE_CLASS)
{
// For triangles and sprites at native res take the region strictly within the bounds
// omitting the bottom/right.
bbox = (bbox + GSVector4i(0xF, 0xF, -1, -1)) & GSVector4i(~0xF);
if (m_nativeres)
{
// For triangles and sprites at native res take the interior pixel centers.
const GSVector4i interior = (bbox + GSVector4i(0xF, 0xF, -1, -1)) & GSVector4i(~0xF);
bbox = interior + GSVector4i(0, 0, 1, 1); // +1 to bottom/right so empty test works correctly.
}
else
{
// For upscaling, remove bottom/right subtexels.
bbox -= ((bbox & GSVector4i(0xF)) == GSVector4i(0)) & GSVector4i(0, 0, 1, 1);
}
// For AA1 triangles and lines, expand the bounds by 1 pixel on all sides.
// Note: redundant check for the AA1 flag to avoid calling a function if not needed.
if ((primclass == GS_TRIANGLE_CLASS || primclass == GS_LINE_CLASS) && PRIM->AA1 && IsCoverageAlphaSupported())
if (PRIM->AA1 && IsCoverageAlphaSupported())
{
bbox += GSVector4i(-0x10, -0x10, 0x10, 0x10);
}
// Make the bottom-right endpoints exclusive for rectangle intersection to work correctly.
bbox += GSVector4i::cxpr(0, 0, 1, 1);
// Do scissor test and empty bbox test.
GSVector4i test = GSVector4i(bbox.rintersects(m_context->scissor.cull)) == GSVector4i::cxpr(0);
// Test for degenerate triangle.
if (primclass == GS_TRIANGLE_CLASS)
{
test |= v0.eq64(v1) | v1.eq64(v2) | v0.eq64(v2);
}
#ifndef ARCH_ARM64
// We only care about the xy passing the skip test.
skip |= test.mask();
#else
// mask() is slow on ARM, so just pull the bits out instead, thankfully we only care about the first 4 bytes.
skip |= (static_cast<u64>(test.extract64<0>()) & UINT64_C(0x8080808080808080)) != 0;
#endif
// Do scissor test.
const GSVector4i bbox_ex = bbox + GSVector4i(0, 0, 1, 1); // Exclusive coords for the scissor test.
const GSVector4i& scissor = m_context->scissor.cull;
u32 test = static_cast<u32>(!bbox_ex.rintersects(scissor));
// Test for empty bbox.
if constexpr (primclass == GS_TRIANGLE_CLASS || primclass == GS_SPRITE_CLASS)
{
test |= static_cast<u32>(bbox.rempty());
}
// Test for degenerate triangle.
if constexpr (primclass == GS_TRIANGLE_CLASS)
{
test |= static_cast<u32>(v0.eq(v1)) | static_cast<u32>(v1.eq(v2)) | static_cast<u32>(v0.eq(v2));
}
skip |= test;
}
if (skip != 0)