Make the fragment depth clamp fallback match hardware depth clamp

For GPUs without depth clamp (old Mali), found by comparing with
--disable-gpu-features=depth-clamp on the Mac:

- Write Z / 65536 like the vertex shader, not / 65535.
- Pass the fragment shader Z after the vertex depth rounding, which hardware
  clamp sees too (Puyo Puyo Fever 2's ground at Z = 0).
- Also clamp draws whose Z only touches 0 or 65535.
- Draws that skip the bounds check (skinned, morphed) get the fragment clamp
  and min/max discard wherever the hardware versions would be on, since those
  don't depend on the check either.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Henrik RydgårdandClaude Opus 5.5 committed 2026-10-13 14:44:34 +02:00
1 parent 3193031e7b
commit 2d61d58bbe
5 files changed
+41 -6

No files matched your search

+22 -3
View File
@@ -484,10 +484,14 @@ static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, cons
flags |= ClipInfoFlags::FlatZ | ClipInfoFlags::SoftClipCull;
}
if (needFragmentDepthClamp() && (minProjZ < 0 || maxProjZ > 65535)) {
if (needFragmentDepthClamp()) {
if (gstate_c.Use(GPU_USE_DEPTH_CLAMP)) {
flags |= ClipInfoFlags::DepthClamp;
} else {
if (minProjZ < 0 || maxProjZ > 65535) {
flags |= ClipInfoFlags::DepthClamp;
}
} else if (minProjZ <= 0 || maxProjZ >= 65535) {
// Including draws right on the limit (Puyo Puyo Fever 2's flat Z = 0 ground): hardware clamp keeps
// what rounding puts just outside, the clip without it doesn't.
flags |= ClipInfoFlags::DepthClampFragment;
}
}
@@ -496,6 +500,21 @@ static bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, cons
return true;
}
ClipInfoFlags DrawEngineCommon::UncheckedClipInfoFlags() {
ClipInfoFlags flags{};
if (gstate.isModeThrough() || gstate.isModeClear()) {
return flags;
}
// Hardware depth clamp and clip planes are always on for these, so do the same with the fallbacks.
if (needFragmentDepthClamp() && !gstate_c.Use(GPU_USE_DEPTH_CLAMP)) {
flags |= ClipInfoFlags::Valid | ClipInfoFlags::DepthClampFragment;
}
if (needFragmentMinMaxClipping() && !gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
flags |= ClipInfoFlags::Valid | ClipInfoFlags::MinMaxZDiscard;
}
return flags;
}
bool DrawEngineCommon::TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *flags) {
// 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.
+3
View File
@@ -105,6 +105,9 @@ public:
// Doesn't support indexing.
bool TestBoundingBoxFast(const float *cullMatrix, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, ClipInfoFlags *clipInfoFlags);
bool TestBoundingBoxThrough(GEPrimitiveType prim, const void *vdata, const void *idata, int vertexCount, const VertexDecoder *dec, u32 vertType, int *bytesRead, ClipInfoFlags *flags);
// For a draw too big or complex for TestBoundingBoxFast: the fallbacks it would need on a GPU that lacks
// depth clamp or clip distances, assuming the worst, or nothing (not Valid) if none are needed.
static ClipInfoFlags UncheckedClipInfoFlags();
bool EstimateThroughPrimSafeSize(const void *verts, const void *inds, GEPrimitiveType prim, int vertexCount, const VertexDecoder *dec, u32 vertType, int *safeWidth, int *safeHeight);
void FlushPartialDecode() {
+2 -1
View File
@@ -1281,7 +1281,8 @@ bool GenerateFragmentShader(const FShaderID &id, char *buffer, const ShaderLangu
WRITE(p, " gl_FragDepth = floor(gl_FragCoord.z * 65536.0 + 0.01) / 65535.0;\n");
}
} else if (fsDepthClamp) {
WRITE(p, " gl_FragDepth = clamp(projZ, 0.0, 65535.0) / 65535.0;\n");
// What the vertex shader's Z / 65536 gives with hardware depth clamp.
WRITE(p, " gl_FragDepth = clamp(projZ * (1.0 / 65536.0), 0.0, 1.0);\n");
}
} else if (useDiscardStencilBugWorkaround) {
// Adreno and some Mali drivers apply early frag tests even with discard in the shader,
+8 -2
View File
@@ -501,7 +501,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
// If non-through, the viewport has already been applied here.
WRITE(p, " vec4 outPos = position;\n");
if (fsMinmaxDiscard || fsDepthClamp) {
if (fsMinmaxDiscard) {
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
}
} else {
@@ -563,7 +563,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " float recip = 1.0 / outPos.w;\n");
WRITE(p, " outPos.xyz = (outPos.xyz * u_vpScale.xyz) * recip + u_vpOffset.xyz;\n");
if (fsMinmaxDiscard || fsDepthClamp) {
if (fsMinmaxDiscard) {
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
}
@@ -953,6 +953,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " outPos.z = floor(outPos.z * 0.5) * 2.0;\n");
}
if (fsDepthClamp) {
// After the rounding, so the fragment shader clamps the Z that hardware depth clamp would.
// (Never needed together with fsMinmaxDiscard, which wants it unrounded.)
WRITE(p, " %sv_zw = vec2(outPos.z * outPos.w, outPos.w);\n", compat.vsOutPrefix);
}
// It seems that depth values of 65535.6 should survive. Seen in NBA2K12 for example.
// So even if it seems like 65535 would make more sense, we divide by 65536 here.
WRITE(p, " outPos.z *= outPos.w * (1.0 / 65536.0);\n");
+6
View File
@@ -1006,6 +1006,9 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
// If certain conditions are true, do frustum culling.
bool passCulling = PASSES_CULLING;
if (passCulling && !(flags & ClipInfoFlags::Valid)) {
flags = DrawEngineCommon::UncheckedClipInfoFlags();
}
if (!passCulling) {
// Do software culling.
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags)) {
@@ -1095,6 +1098,9 @@ void GPUCommonHW::Execute_Prim(u32 op, u32 diff) {
bool passCulling = onePassed || PASSES_CULLING;
ClipInfoFlags flags{};
if (passCulling) {
flags = DrawEngineCommon::UncheckedClipInfoFlags();
}
if (!passCulling) {
// Do software culling.
if (drawEngineCommon_->TestBoundingBoxFast(gstate_c.cullMatrix, verts, inds, count, decoder, vertexType, &flags)) {