Prepare TestBoundingBoxFast for optimization

This commit is contained in:
Henrik Rydgård
2026-05-25 12:13:11 +02:00
parent e4c9eb6ab0
commit 4a55fc6724
2 changed files with 69 additions and 50 deletions
+66 -49
View File
@@ -328,9 +328,10 @@ bool DrawEngineCommon::TestBoundingBox(const void *vdata, const void *inds, int
// corners. That way we can cull more draws quite cheaply.
// We could take the min/max during the regular vertex decode, and just skip the draw call if it's trivially culled.
// This would help games like Midnight Club (that one does a lot of out-of-bounds drawing) immensely.
bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType) {
SimpleVertex *corners = (SimpleVertex *)(decoded_ + 65536 * 12);
float *verts = (float *)(decoded_ + 65536 * 18);
template<u32 posFmt>
static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, u8 *decoded) {
SimpleVertex *corners = (SimpleVertex *)(decoded + 65536 * 12);
float *verts = (float *)(decoded + 65536 * 18);
// Although this may lead to drawing that shouldn't happen, the viewport is more complex on VR.
// Let's always say objects are within bounds.
@@ -339,8 +340,8 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi
}
// Simple, most common case.
int stride = dec->VertexSize();
int offset = dec->posoff;
const int stride = dec->VertexSize();
const int offset = dec->posoff;
int vertStride = 3;
// TODO: Possibly do the plane tests directly against the source formats instead of converting.
@@ -423,50 +424,6 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi
break;
}
// Modify the transform matrix to take the viewport into account before culling. This is not necessary
// for most games, but there are games that rely on outside-viewport draws (such as Dante's Inferno)'s post
// processing effects, and we don't want to cull those.
// Potentially we should cache this transform matrix too, but hopefully this is not a bottleneck.
// I guess we could also do this directly when computing worldviewproj...
const float vpXCenter = gstate.getViewportXCenter();
const float vpYCenter = gstate.getViewportYCenter();
const float vpXScale = gstate.getViewportXScale();
const float vpYScale = gstate.getViewportYScale();
const int scissorX2 = gstate.getScissorX2();
const int scissorY2 = gstate.getScissorY2();
// Check for weird scaling that can make graphics extend beyond the viewport.
// NOTE: These checks are not bullet proof.
float mtx[16];
if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) && vpYScale < ((scissorY2 + 1) >> 1)) {
// Note that the PSP does not clip against the viewport.
const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY());
// Region1 (rate) is used as an X1/Y1 here, matching PSP behavior.
Vec2f minOffset = baseOffset + Vec2f(std::max(gstate.getRegionX1(), gstate.getScissorX1()), std::max(gstate.getRegionY1(), gstate.getScissorY1()));
Vec2f maxOffset = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2()));
// Now let's apply the viewport to our scissor/region + offset range.
Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale());
Vec2f minViewport = (minOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
Vec2f maxViewport = (maxOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
Vec2f viewportInvSize = Vec2f(1.0f / (maxViewport.x - minViewport.x), 1.0f / (maxViewport.y - minViewport.y));
Lin::Matrix4x4 applyViewport{};
// Scale to the viewport's size.
applyViewport.xx = 2.0f * viewportInvSize.x;
applyViewport.yy = 2.0f * viewportInvSize.y;
applyViewport.zz = 1.0f;
applyViewport.ww = 1.0f;
// And offset to the viewport's centers.
applyViewport.wx = -(maxViewport.x + minViewport.x) * viewportInvSize.x;
applyViewport.wy = -(maxViewport.y + minViewport.y) * viewportInvSize.y;
// TODO: Optimize.
Matrix4ByMatrix4(mtx, worldViewProj, applyViewport.m);
}
// We only check the 4 sides. Near/far won't likely make a huge difference.
// We test one vertex against 4 planes to get some SIMD. Vertices need to be transformed to world space
// for testing, don't want to re-do that, so we have to use that "pivot" of the data.
@@ -604,6 +561,66 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi
return true;
}
bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType) {
// Modify the transform matrix to take the viewport into account before culling. This is not necessary
// for most games, but there are games that rely on outside-viewport draws (such as Dante's Inferno)'s post
// processing effects, and we don't want to cull those.
// Potentially we should cache this transform matrix too, but hopefully this is not a bottleneck.
// I guess we could also do this directly when computing worldviewproj...
const float vpXCenter = gstate.getViewportXCenter();
const float vpYCenter = gstate.getViewportYCenter();
const float vpXScale = gstate.getViewportXScale();
const float vpYScale = gstate.getViewportYScale();
const int scissorX2 = gstate.getScissorX2();
const int scissorY2 = gstate.getScissorY2();
// Check for weird scaling that can make graphics extend beyond the viewport.
// NOTE: These checks are not bullet proof.
float mtx[16];
if (vpXCenter != 2048.0f || vpYCenter != 2048.0f || vpXScale < ((scissorX2 + 1) >> 1) && vpYScale < ((scissorY2 + 1) >> 1)) {
// Note that the PSP does not clip against the viewport.
const Vec2f baseOffset = Vec2f(gstate.getOffsetX(), gstate.getOffsetY());
// Region1 (rate) is used as an X1/Y1 here, matching PSP behavior.
Vec2f minOffset = baseOffset + Vec2f(std::max(gstate.getRegionX1(), gstate.getScissorX1()), std::max(gstate.getRegionY1(), gstate.getScissorY1()));
Vec2f maxOffset = baseOffset + Vec2f(std::min(gstate.getRegionX2(), gstate.getScissorX2()), std::min(gstate.getRegionY2(), gstate.getScissorY2()));
// Now let's apply the viewport to our scissor/region + offset range.
Vec2f inverseViewportScale = Vec2f(1.0f / gstate.getViewportXScale(), 1.0f / gstate.getViewportYScale());
Vec2f minViewport = (minOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
Vec2f maxViewport = (maxOffset - Vec2f(gstate.getViewportXCenter(), gstate.getViewportYCenter())) * inverseViewportScale;
Vec2f viewportInvSize = Vec2f(1.0f / (maxViewport.x - minViewport.x), 1.0f / (maxViewport.y - minViewport.y));
Lin::Matrix4x4 applyViewport{};
// Scale to the viewport's size.
applyViewport.xx = 2.0f * viewportInvSize.x;
applyViewport.yy = 2.0f * viewportInvSize.y;
applyViewport.zz = 1.0f;
applyViewport.ww = 1.0f;
// And offset to the viewport's centers.
applyViewport.wx = -(maxViewport.x + minViewport.x) * viewportInvSize.x;
applyViewport.wy = -(maxViewport.y + minViewport.y) * viewportInvSize.y;
// TODO: Optimize.
Matrix4ByMatrix4(mtx, worldViewProj, applyViewport.m);
worldViewProj = mtx;
}
switch (vertType & GE_VTYPE_POS_MASK) {
case GE_VTYPE_POS_8BIT:
return ::TestBoundingBoxFast<GE_VTYPE_POS_8BIT>(worldViewProj, vdata, vertexCount, dec, vertType, decoded_);
case GE_VTYPE_POS_16BIT:
return ::TestBoundingBoxFast<GE_VTYPE_POS_16BIT>(worldViewProj, vdata, vertexCount, dec, vertType, decoded_);
case GE_VTYPE_POS_FLOAT:
return ::TestBoundingBoxFast<GE_VTYPE_POS_FLOAT>(worldViewProj, vdata, vertexCount, dec, vertType, decoded_);
default:
// Shouldn't end up here with the checks outside this function.
_dbg_assert_(false);
return true;
}
}
// 2D bounding box test against scissor. No indexing yet.
// Only supports non-indexed draws with float positions.
bool DrawEngineCommon::TestBoundingBoxThrough(const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead) {
+3 -1
View File
@@ -1232,7 +1232,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " float projZ = (projPos.z - u_depthRange.z) * u_depthRange.w;\n");
if (!bugs.Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL)) {
// Vertex range culling doesn't happen when Z clips, note sign of w is important.
// Vertex range culling always happens, even when Z clips - but since we let the host GPU clip
// here afterwards, and never actually perform the clipping, we have to disable range culling here if we
// detect clipping, otherwise we're checking unclipped coordinates.
WRITE(p, " if (u_cullRangeMin.w <= 0.0 || projZ * outPos.w > -outPos.w) {\n");
const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y";
const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y";