Apply a small depth bias to simulate rounding of Z. Fix min/max clip plane math

This commit is contained in:
Henrik Rydgård
2026-05-25 21:30:59 +02:00
parent 08f4c8fae8
commit f55adcc3a1
4 changed files with 37 additions and 32 deletions
+6
View File
@@ -36,6 +36,8 @@ const char * const hlsl_preamble_fs =
"#define fract frac\n"
"#define mod(x, y) fmod(x, y)\n"
"#define inversesqrt rsqrt\n"
"#define floatBitsToUint asuint\n"
"#define uintBitsToFloat asfloat\n"
"\n";
static const char * const hlsl_d3d11_preamble_fs =
@@ -65,6 +67,8 @@ static const char * const hlsl_preamble_gs =
"#define mediump\n"
"#define highp\n"
"#define inversesqrt rsqrt\n"
"#define floatBitsToUint asuint\n"
"#define uintBitsToFloat asfloat\n"
"\n";
static const char * const vulkan_glsl_preamble_gs =
@@ -92,6 +96,8 @@ static const char * const hlsl_preamble_vs =
"#define fract frac\n"
"#define mod(x, y) fmod(x, y)\n"
"#define inversesqrt rsqrt\n"
"#define floatBitsToUint asuint\n"
"#define uintBitsToFloat asfloat\n"
"\n";
static const char * const semanticNames[] = {
+2 -1
View File
@@ -368,7 +368,8 @@ void SoftwareTransform::Transform(const float projMtx[16], Lin::Vec3 vpScale, Li
Vec3ByMatrix44(xyzw, v, projMtx);
// Here we also need to apply the viewport.
Lin::Vec3 xyz = vpOffset + vpScale.scaledBy(Lin::Vec3(xyzw[0] / xyzw[3], xyzw[1] / xyzw[3], xyzw[2] / xyzw[3]));
float recip = 1.0f / xyzw[3];
Lin::Vec3 xyz = vpOffset + vpScale.scaledBy(Lin::Vec3(xyzw[0] * recip, xyzw[1] * recip, xyzw[2] * recip));
transformed[index].x = xyz.x;
transformed[index].y = xyz.y;
transformed[index].z = xyz.z;
+17 -31
View File
@@ -355,9 +355,10 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
zClipPlaneSuffix = ".x";
minZClipPlaneSuffix = ".y";
maxZClipPlaneSuffix = ".z";
bool clipRange = gstate_c.Use(GPU_USE_CLIP_DISTANCE);
WRITE(p, " float3 gl_ClipDistance : SV_ClipDistance;\n");
WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n");
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
WRITE(p, " float3 gl_ClipDistance : SV_ClipDistance;\n");
WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n");
}
}
WRITE(p, "};\n");
} else {
@@ -734,16 +735,9 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (isModeThrough) {
WRITE(p, " vec4 outPos = position;\n");
WRITE(p, " outPos.z *= 65536.0;\n"); // TODO: This multiplication should be moved to the vertex decoders for through mode.
WRITE(p, " outPos.w = 1.0;\n");
} else {
// The viewport is used in this case, so need to compensate for that.
if (gstate_c.Use(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, " vec4 outPos = depthRoundZVP(position);\n");
} else {
WRITE(p, " vec4 outPos = position;\n");
}
// Here we need to apply the viewport and also range culling, just like with the hardware transform below.
// The viewport has already been applied here.
WRITE(p, " vec4 outPos = position;\n");
}
} else {
// Step 1: World Transform / Skinning
@@ -822,20 +816,11 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
// Final view and projection transforms.
if (gstate_c.Use(GPU_ROUND_DEPTH_TO_16BIT)) {
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
WRITE(p, " vec4 outPos = depthRoundZVP(mul(u_proj_lens, viewPos));\n");
WRITE(p, " vec4 orgPos = depthRoundZVP(mul(u_proj, viewPos));\n");
} else {
WRITE(p, " vec4 outPos = depthRoundZVP(mul(u_proj, viewPos));\n");
}
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
WRITE(p, " vec4 outPos = mul(u_proj_lens, viewPos);\n");
WRITE(p, " vec4 orgPos = mul(u_proj, viewPos);\n");
} else {
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
WRITE(p, " vec4 outPos = mul(u_proj_lens, viewPos);\n");
WRITE(p, " vec4 orgPos = mul(u_proj, viewPos);\n");
} else {
WRITE(p, " vec4 outPos = mul(u_proj, viewPos);\n");
}
WRITE(p, " vec4 outPos = mul(u_proj, viewPos);\n");
}
// We're in clip space, so here we check for clipping. We check if the actual hardware will clip.
@@ -856,7 +841,6 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
// In software transform mode, this is performed in on the CPU.
WRITE(p, " outPos.xyz = (outPos.xyz / outPos.w) * u_vpScale.xyz + u_vpOffset.xyz;\n");
// TODO: Declare variables for dots for shade mapping if needed.
const char *srcCol = "color0";
@@ -1227,11 +1211,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " outPos.xy -= u_rasterOffset.xy;\n");
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
// We use clipping to implement min/max Z.
// 1.0 effectively disables the clip plane.
// TODO: Should we move this to homogenous coordinates after the multiplication by w?
WRITE(p, " gl_ClipDistance%s = u_minZmaxZ.x > 0.0 ? outPos.z - u_minZmaxZ.x : 1.0;\n", minZClipPlaneSuffix);
WRITE(p, " gl_ClipDistance%s = u_minZmaxZ.y < 65535.0 ? u_minZmaxZ.y - outPos.z : 1.0;\n", maxZClipPlaneSuffix);
// We use clipping, where available, to implement min/max Z.
// 1.0 is used to disable the clip plane (should we generate more shaders instead? how costly are they?)
// Note: outPos.z need to be multiplied by outPos.w to undo the division, which shouldn't be in effect here.
// We should probably store the undivided outPos in a variable.
// We apply a small offset here, it's needed to break some ties, like in the map in Test Drive Unlimited.
WRITE(p, " gl_ClipDistance%s = u_minZmaxZ.x > 0.0 ? (outPos.z + 0.5 - u_minZmaxZ.x) * outPos.w : 1.0;\n", minZClipPlaneSuffix);
WRITE(p, " gl_ClipDistance%s = u_minZmaxZ.y < 65535.0 ? (u_minZmaxZ.y - (outPos.z + 0.5)) * outPos.w : 1.0;\n", maxZClipPlaneSuffix);
}
}
+12
View File
@@ -68,6 +68,18 @@ inline unsigned int toFloat24(float f) {
return i >> 8;
}
// TODO: Not completely sure about the exact mechanics here, or where we need to use this.
// Useful to experiment with.
inline float roundToFloat24(float f) {
unsigned int i;
memcpy(&i, &f, 4);
i &= 0xFFFFFF00;
i += 0x80;
float retval;
memcpy(&retval, &i, 4);
return retval;
}
// TODO: Possibly use macros to disable expensive parts of stats tracking in release builds.
struct GPUStatsPerFrame {
// Per frame statistics