Check if the viewport transform matches the clip space. If so we can skip the near clip plane.

This commit is contained in:
Henrik Rydgård
2026-05-30 10:15:26 +02:00
parent 189dde8115
commit 2a5c2fa477
4 changed files with 32 additions and 12 deletions
+1 -1
View File
@@ -184,7 +184,7 @@ inline bool AnyZeroSignBit(Vec4S32 value) {
return _mm_movemask_ps(_mm_castsi128_ps(value.v)) != 0xF;
}
// These are for evaluating compare masks.
// These are for evaluating compare masks. On some archs it might just check the upper bit.
inline bool AllCompareBitsSet(Vec4S32 value) {
return _mm_movemask_ps(_mm_castsi128_ps(value.v)) == 0xF;
}
+20 -11
View File
@@ -30,6 +30,7 @@
#include "Core/Config.h"
#include "GPU/GPUCommon.h"
#include "GPU/Common/DrawEngineCommon.h"
#include "GPU/GPUStateSIMDUtil.h"
#include "GPU/Common/SplineCommon.h"
#include "GPU/Common/DepthRaster.h"
#include "GPU/Common/ShaderId.h"
@@ -368,12 +369,12 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
objPos = Vec4F32::Load((const float *)data);
break;
}
Vec4F32 clipPos = objPos.AsVec3ByMatrix44(worldViewProjMat) & vertexMask;
Vec4F32 clipPos = objPos.AsVec3ByMatrix44(worldViewProjMat) & vertexMask; // Not sure we should do the vertex mask thing.
Vec4F32 posW = clipPos.ShuffleWWWW();
Vec4F32 posXY = clipPos.ShuffleXXYY();
Vec4F32 planeDistXY = posXY * planesXY + posW;
insideMaskXY |= planeDistXY.CompareGe(Vec4F32::Zero());
Vec4F32 posZ = clipPos.ShuffleZZZZ();
Vec4F32 posZ = clipPos.ShuffleZZZZ(); // This means that we compute the Z sides twice. Oh well.
Vec4F32 planeDistZ = posZ * planesXY + posW;
anyOutsideMaskZ |= planeDistZ.CompareLt(Vec4F32::Zero());
insideMaskZ |= planeDistZ.CompareGe(Vec4F32::Zero());
@@ -386,17 +387,25 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
}
}
if (AllCompareBitsSet(insideMaskXY) && AllCompareBitsSet(insideMaskZ)) {
depths->valid = true;
depths->hitClipSpaceZW = AnyCompareBitsSet(anyOutsideMaskZ);
// Before checking later, we apply a viewport to the projZ, so we can later compare against minZ/maxZ or the outer bounds.
// But to save operations we don't do it here.
depths->minProjZ = minProjZ + vpZOffset;
depths->maxProjZ = maxProjZ + vpZOffset;
return true;
} else {
if (!AllCompareBitsSet(insideMaskXY) || !AllCompareBitsSet(insideMaskZ)) {
// All vertices were outside one side of the clipping cube. We can skip the draw entirely.
return false;
}
depths->valid = true;
depths->hitClipSpaceZW = AnyCompareBitsSet(anyOutsideMaskZ);
// If the W=-Z plane was intersected, here we can go through the vertices again, and check for X/Y bounds for range culling.
// However! We need to find a valid way to do so by "backprojecting" the range culling into clip space, which may be a little tricky.
//
// If nothing is outside the box, the "inversion" cases (vertices hit the boundary after clipping like Flatout, Sengoku Cannon)
// cannot happen, and soft clipping is only needed if the viewport is smaller than the valid Z range.
// Before checking later, we apply a viewport to the projZ, so we can later compare against minZ/maxZ or the outer bounds.
// But to save operations we don't do it here.
depths->minProjZ = minProjZ + vpZOffset;
depths->maxProjZ = maxProjZ + vpZOffset;
return true;
}
bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, BoundingDepths *depths) {
+10
View File
@@ -2252,6 +2252,16 @@ void GPUCommon::UpdateMatrixProducts() {
// No funny business, just use worldviewproj for culling.
memcpy(gstate_c.cullMatrix, gstate_c.worldviewproj, sizeof(float) * 16);
}
// Now, check the Z range. If the viewport matches the limits of Z, we can avoid the need to do near clipping in many cases
// since the host hardware will take care of it automatically.
const float absZScale = fabsf(gstate.getViewportZScale());
const float zCenter = gstate.getViewportZCenter();
const float zMin = gstate.getDepthRangeMin();
const float frontPlane = zCenter - absZScale;
if (frontPlane == zMin || frontPlane == zMin + 1.0f) {
gstate_c.viewportNearPlaneMatchesOutput = true;
}
}
// It's just a bit operation, cheaper to clean all three together.
gstate_c.Clean(DIRTY_WORLD_VIEW_PROJ_MATRIX | DIRTY_VIEW_PROJ_MATRIX | DIRTY_CULL_MATRIX);
+1
View File
@@ -668,6 +668,7 @@ public:
float viewproj[16];
float worldviewproj[16];
float cullMatrix[16];
bool viewportNearPlaneMatchesOutput;
KnownVertexBounds vertBounds;