During culling, evaluate minZ,maxZ,minW,maxW, and use them to cull some things. Also, assorted vshader work.

This commit is contained in:
Henrik Rydgård
2026-05-23 20:53:45 +02:00
parent 5808afef10
commit f0d715feaa
11 changed files with 73 additions and 51 deletions
+30 -5
View File
@@ -353,6 +353,8 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
Mat4F32 worldViewProjMat(worldViewProj);
alignas(16) static const float planesXYData[4] = { 1, -1, 1, -1 };
Vec4F32 planesXY = Vec4F32::LoadAligned(planesXYData);
Vec4F32 minClipPos = Vec4F32::Splat(INFINITY);
Vec4F32 maxClipPos = Vec4F32::Splat(-INFINITY);
Vec4S32 insideMask = Vec4S32::Zero();
const s8 *data = (const s8 *)vdata + offset;
for (int i = 0; i < vertexCount; i++, data += stride) {
@@ -368,14 +370,35 @@ static bool TestBoundingBoxFast(const float *worldViewProj, const void *vdata, i
objPos = Vec4F32::Load((const float *)data);
break;
}
Vec4F32 clippos = objPos.AsVec3ByMatrix44(worldViewProjMat);
Vec4F32 posXY = clippos.ShuffleXXYY();
Vec4F32 posW = clippos.ShuffleWWWW();
Vec4F32 clipPos = objPos.AsVec3ByMatrix44(worldViewProjMat);
minClipPos = minClipPos.Min(clipPos);
maxClipPos = maxClipPos.Max(clipPos);
Vec4F32 posXY = clipPos.ShuffleXXYY();
Vec4F32 posW = clipPos.ShuffleWWWW();
Vec4F32 planeDist = posXY * planesXY + posW;
insideMask |= planeDist.CompareGe(Vec4F32::Zero());
}
// At least one vertex is inside one of the planes.
return AllCompareBitsSet(insideMask);
const float maxZ = maxClipPos[2];
const float maxW = maxClipPos[3];
const float minZ = minClipPos[2];
const float minW = minClipPos[3];
if (AllCompareBitsSet(insideMask)) {
// At least one vertex is inside each one of the planes.
// Process min/max. Z is in minProj[2]/maxProj[2], W is in minProj[3]/maxProj[3].
// Here we can perform the culling that happens if all vertices are closer than the near plane, or farther than the far plane.
// Technically this should happen per-primitive, which we try to do with cull planes or software vertex processing if available, but this is still valid
// for groups of primitives, avoiding detailed checking, and is a good fallback for games that rely on the min/max culling
// behavior (like LocoRoco2's Tropuca level). The question is, do the inequalities really work like that properly? It seem
// s like they should.
if (maxZ < -maxW || minZ > maxW) {
passesCull = false;
}
return passesCull;
} else {
return false;
}
}
bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const void *vdata, int vertexCount, const VertexDecoder *dec, u32 vertType) {
@@ -383,6 +406,8 @@ bool DrawEngineCommon::TestBoundingBoxFast(const float *worldViewProj, const voi
// Let's always say objects are within bounds.
if (gstate_c.Use(GPU_USE_VIRTUAL_REALITY)) {
return true;
} else if (vertexCount == 0) {
return false;
}
// Modify the transform matrix to take the viewport into account before culling. This is not necessary
+3
View File
@@ -17,6 +17,8 @@ std::string VertexShaderDesc(const VShaderID &id) {
std::stringstream desc;
desc << StringFromFormat("%08x:%08x ", id.d[1], id.d[0]);
if (id.Bit(VS_BIT_IS_THROUGH)) desc << "THR ";
if (id.Bit(VS_BIT_CLIP_ENABLE)) desc << "Clip ";
if (id.Bit(VS_BIT_MINMAX_DISCARD)) desc << "ZClip ";
if (id.Bit(VS_BIT_USE_HW_TRANSFORM)) desc << "HWX ";
if (id.Bit(VS_BIT_HAS_COLOR)) desc << "C ";
if (id.Bit(VS_BIT_HAS_TEXCOORD)) desc << "T ";
@@ -91,6 +93,7 @@ void ComputeVertexShaderID(VShaderID *id_out, u32 vertType, bool useHWTransform,
id.SetBit(VS_BIT_LMODE, lmode);
id.SetBit(VS_BIT_IS_THROUGH, isModeThrough);
id.SetBit(VS_BIT_HAS_COLOR, vtypeHasColor);
id.SetBit(VS_BIT_CLIP_ENABLE, gstate.isDepthClipEnabled());
id.SetBit(VS_BIT_VERTEX_RANGE_CULLING, vertexRangeCulling);
if (!isModeThrough && gstate_c.Use(GPU_USE_SINGLE_PASS_STEREO)) {
+3 -3
View File
@@ -13,10 +13,10 @@ enum VShaderBit : uint8_t {
VS_BIT_IS_THROUGH = 1,
// bit 2 is free.
VS_BIT_HAS_COLOR = 3,
// bit 4 is free.
VS_BIT_CLIP_ENABLE = 4,
VS_BIT_VERTEX_RANGE_CULLING = 5,
VS_BIT_SIMPLE_STEREO = 6,
// 7 is free.
VS_BIT_MINMAX_DISCARD = 7, // Do min/max in the fragment shader.
VS_BIT_USE_HW_TRANSFORM = 8,
VS_BIT_HAS_NORMAL = 9, // conditioned on hw transform
VS_BIT_NORM_REVERSE = 10,
@@ -83,7 +83,7 @@ enum FShaderBit : uint8_t {
FS_BIT_COLOR_AGAINST_ZERO = 20,
FS_BIT_ENABLE_FOG = 21, // Not used with FS_BIT_UBERSHADER
FS_BIT_DO_TEXTURE_PROJ = 22,
// 1 free bit
FS_BIT_MINMAX_DISCARD = 23,
FS_BIT_STENCIL_TO_ALPHA = 24, // 2 bits
FS_BIT_REPLACE_ALPHA_WITH_STENCIL_TYPE = 26, // 4 bits (ReplaceAlphaType)
FS_BIT_SIMULATE_LOGIC_OP_TYPE = 30, // 2 bits
+29 -28
View File
@@ -175,6 +175,8 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
bool flatBug = bugs.Has(Draw::Bugs::BROKEN_FLAT_IN_SHADER) && g_Config.bVendorBugChecksEnabled;
bool needsZWHack = bugs.Has(Draw::Bugs::EQUAL_WZ_CORRUPTS_DEPTH) && g_Config.bVendorBugChecksEnabled;
bool nanBug = bugs.Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL) && g_Config.bVendorBugChecksEnabled;
bool doFlatShading = id.Bit(VS_BIT_FLATSHADE) && !flatBug;
bool useHWTransform = id.Bit(VS_BIT_USE_HW_TRANSFORM);
@@ -236,10 +238,13 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
bool texCoordInVec3 = false;
bool vertexRangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING) && !isModeThrough;
bool clipClampedDepth = !isModeThrough && gstate_c.Use(GPU_USE_DEPTH_CLAMP) && gstate_c.Use(GPU_USE_CLIP_DISTANCE);
const char *clipClampedDepthSuffix = "[0]";
const char *vertexRangeClipSuffix = clipClampedDepth ? "[1]" : "[0]";
const bool clipEnable = id.Bit(VS_BIT_CLIP_ENABLE) && !isModeThrough;
const bool rangeCulling = id.Bit(VS_BIT_VERTEX_RANGE_CULLING);
// bool clipClampedDepth = !isModeThrough && gstate_c.Use(GPU_USE_DEPTH_CLAMP) && gstate_c.Use(GPU_USE_CLIP_DISTANCE);
const char *zClipPlaneSuffix = "[0]";
const char *minZClipPlaneSuffix = "[1]";
const char *maxZClipPlaneSuffix = "[2]";
if (compat.shaderLanguage == GLSL_VULKAN) {
WRITE(p, "\n");
@@ -347,19 +352,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
WRITE(p, " float v_fogdepth : TEXCOORD1;\n");
{
WRITE(p, " vec4 gl_Position : SV_Position;\n");
bool clipRange = vertexRangeCulling && gstate_c.Use(GPU_USE_CLIP_DISTANCE);
if (clipClampedDepth && clipRange) {
WRITE(p, " float2 gl_ClipDistance : SV_ClipDistance;\n");
clipClampedDepthSuffix = ".x";
vertexRangeClipSuffix = ".y";
} else if (clipClampedDepth || clipRange) {
WRITE(p, " float gl_ClipDistance : SV_ClipDistance;\n");
clipClampedDepthSuffix = "";
vertexRangeClipSuffix = "";
}
if (vertexRangeCulling && gstate_c.Use(GPU_USE_CULL_DISTANCE)) {
WRITE(p, " float2 gl_CullDistance : SV_CullDistance0;\n");
}
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");
}
WRITE(p, "};\n");
} else {
@@ -840,11 +838,19 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
}
}
// We're in clip space, so here we check for virtual clipping. We don't actually clip, but we check if the actual hardware would have clipped.
// We're in clip space, so here we check for clipping. We check if the actual hardware will clip.
// If so, we skip the "range culling" (x and y out-of-bounds checks) since they wouldn't have happened, most likely.
WRITE(p, " if (outPos.z < -outPos.w || outPos.z > outPos.w) {\n");
WRITE(p, " if (outPos.z < -outPos.w) {\n");
WRITE(p, " zClipped = true;\n");
WRITE(p, " }\n");
// Then we actually add the clip plane.
WRITE(p, " gl_ClipDistance%s = outPos.z + outPos.w;\n", zClipPlaneSuffix);
if (gstate_c.Use(GPU_USE_CULL_DISTANCE)) {
// Not quite understanding this one.
// WRITE(p, " gl_CullDistance[0] = outPos.z + outPos.w;\n");
// WRITE(p, " gl_CullDistance[1] = outPos.w - outPos.z;\n");
}
// Perform the perspective projection and viewport transform. (We'll have to undo the division before passing the coordinate along).
// In software transform mode, this is performed in on the CPU.
@@ -1209,7 +1215,7 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
if (!isModeThrough) {
// Cull against X and Y limits (unless the GPU has a certain driver bug).
// It's not clear what the limits should be in through mode though, although I'm sure they exist.
if (gstate_c.Use(GPU_USE_VS_RANGE_CULLING)) {
if (!nanBug && rangeCulling) {
WRITE(p, " if (!zClipped && (outPos.x < 0.0 || outPos.y < 0.0 || outPos.x >= 4096.0 || outPos.y >= 4096.0 || outPos.w < -1.0)) {\n");
// Discard the whole triangle by setting one vertex to NaN.
WRITE(p, " outPos = vec4(u_NaN, u_NaN, u_NaN, u_NaN);\n");
@@ -1220,17 +1226,12 @@ bool GenerateVertexShader(const VShaderID &id, char *buffer, const ShaderLanguag
// Apply raster offset after the range culling.
WRITE(p, " outPos.xy -= u_rasterOffset.xy;\n");
if (gstate_c.Use(GPU_USE_CULL_DISTANCE)) {
// Cull against the Z=-W and Z=W planes.
WRITE(p, " gl_CullDistance[0] = outPos.w + outPos.z; \n");
WRITE(p, " gl_CullDistance[1] = outPos.z - outPos.w; \n");
}
if (gstate_c.Use(GPU_USE_CLIP_DISTANCE)) {
// We use clipping to implement min/max Z.
// 1.0 effectively disables the clip plane.
WRITE(p, " gl_ClipDistance[0] = u_minZmaxZ.x > 0.0 ? outPos.z - u_minZmaxZ.x : 1.0;\n");
WRITE(p, " gl_ClipDistance[1] = u_minZmaxZ.y < 65535.0 ? u_minZmaxZ.y - outPos.z : 1.0;\n");
// 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);
}
}
+2 -2
View File
@@ -59,7 +59,7 @@ static constexpr GECmdInfo geCmdInfo[] = {
{ GE_CMD_LIGHTENABLE1, "light1_on", GECmdFormat::FLAG, "Light 1 enable", CMD_FMT_FLAG, GE_CMD_LIGHTINGENABLE },
{ GE_CMD_LIGHTENABLE2, "light2_on", GECmdFormat::FLAG, "Light 2 enable", CMD_FMT_FLAG, GE_CMD_LIGHTINGENABLE },
{ GE_CMD_LIGHTENABLE3, "light3_on", GECmdFormat::FLAG, "Light 3 enable", CMD_FMT_FLAG, GE_CMD_LIGHTINGENABLE },
{ GE_CMD_DEPTHCLAMPENABLE, "zclamp_on", GECmdFormat::FLAG, "Depth clamp enable", CMD_FMT_FLAG},
{ GE_CMD_DEPTHCLIPENABLE, "zclamp_on", GECmdFormat::FLAG, "Depth clamp enable", CMD_FMT_FLAG},
{ GE_CMD_CULLFACEENABLE, "cull_on", GECmdFormat::FLAG, "Cullface enable", CMD_FMT_FLAG},
{ GE_CMD_TEXTUREMAPENABLE, "tex_on", GECmdFormat::FLAG, "Texture enable", CMD_FMT_FLAG},
{ GE_CMD_FOGENABLE, "fog_on", GECmdFormat::FLAG, "Fog enable", CMD_FMT_FLAG},
@@ -306,7 +306,7 @@ static constexpr GECmdAlias geCmdAliases[] = {
{ GE_CMD_LIGHTENABLE1, { "light1enable" } },
{ GE_CMD_LIGHTENABLE2, { "light2enable" } },
{ GE_CMD_LIGHTENABLE3, { "light3enable" } },
{ GE_CMD_DEPTHCLAMPENABLE, { "zclampenable", "depthclamp_on", "depthclampenable" } },
{ GE_CMD_DEPTHCLIPENABLE, { "zclampenable", "depthclamp_on", "depthclampenable" } },
{ GE_CMD_CULLFACEENABLE, { "cullenable", "cullface_on", "cullfaceenable" } },
{ GE_CMD_TEXTUREMAPENABLE, { "texenable", "texture_on", "textureenable" } },
{ GE_CMD_FOGENABLE, { "fogenable" } },
+1 -8
View File
@@ -200,7 +200,7 @@ const CommonCommandTableEntry commonCommandTable[] = {
{ GE_CMD_VIEWPORTYCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS },
{ GE_CMD_VIEWPORTZSCALE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS },
{ GE_CMD_VIEWPORTZCENTER, FLAG_FLUSHBEFOREONCHANGE, DIRTY_FRAMEBUF | DIRTY_TEXTURE_PARAMS | DIRTY_PROJMATRIX | DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_VIEWPORT_UNIFORMS },
{ GE_CMD_DEPTHCLAMPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VIEWPORTSCISSOR_STATE | DIRTY_RASTER_STATE },
{ GE_CMD_DEPTHCLIPENABLE, FLAG_FLUSHBEFOREONCHANGE, DIRTY_VERTEXSHADER_STATE },
// Z range.
{ GE_CMD_MINZ, FLAG_FLUSHBEFOREONCHANGE, DIRTY_RASTER_STATE | DIRTY_RASTER_OFFSET},
@@ -602,13 +602,6 @@ u32 GPUCommonHW::CheckGPUFeatures() const {
features |= GPU_USE_DEPTH_CLAMP;
}
bool canClipOrCull = draw_->GetDeviceCaps().maxClipDistances >= 3 || draw_->GetDeviceCaps().maxCullDistances >= 1;
bool canDiscardVertex = !draw_->GetBugs().Has(Draw::Bugs::BROKEN_NAN_IN_CONDITIONAL);
if ((canClipOrCull || canDiscardVertex) && !g_Config.bDisableRangeCulling) {
// We'll dynamically use the parts that are supported, to reduce artifacts as much as possible.
features |= GPU_USE_VS_RANGE_CULLING;
}
if (draw_->GetDeviceCaps().framebufferFetchSupported) {
features |= GPU_USE_FRAMEBUFFER_FETCH;
features |= GPU_USE_SHADER_BLENDING; // doesn't matter if we are buffered or not here.
+1 -1
View File
@@ -323,7 +323,7 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer, int bufsize) {
}
break;
case GE_CMD_DEPTHCLAMPENABLE:
case GE_CMD_DEPTHCLIPENABLE:
snprintf(buffer, bufsize, "Depth clamp enable: %i", data);
break;
+1 -1
View File
@@ -228,7 +228,7 @@ const SoftwareCommandTableEntry softgpuCommandTable[] = {
{ GE_CMD_VIEWPORTYCENTER, 0, SoftDirty::TRANSFORM_VIEWPORT },
{ GE_CMD_VIEWPORTZSCALE, 0, SoftDirty::TRANSFORM_VIEWPORT },
{ GE_CMD_VIEWPORTZCENTER, 0, SoftDirty::TRANSFORM_VIEWPORT },
{ GE_CMD_DEPTHCLAMPENABLE, 0, SoftDirty::TRANSFORM_BASIC },
{ GE_CMD_DEPTHCLIPENABLE, 0, SoftDirty::TRANSFORM_BASIC },
// Z clipping.
{ GE_CMD_MINZ, 0, SoftDirty::PIXEL_BASIC | SoftDirty::PIXEL_CACHED },
+1 -1
View File
@@ -45,7 +45,7 @@ enum GECommand : uint8_t {
GE_CMD_LIGHTENABLE1 = 0x19,
GE_CMD_LIGHTENABLE2 = 0x1A,
GE_CMD_LIGHTENABLE3 = 0x1B,
GE_CMD_DEPTHCLAMPENABLE = 0x1C,
GE_CMD_DEPTHCLIPENABLE = 0x1C,
GE_CMD_CULLFACEENABLE = 0x1D,
GE_CMD_TEXTUREMAPENABLE = 0x1E,
GE_CMD_FOGENABLE = 0x1F,
+1 -1
View File
@@ -1407,7 +1407,7 @@ static const StateItem g_rasterState[] = {
{true, GE_CMD_NOP, "Clipping/Clamping"},
{false, GE_CMD_MINZ},
{false, GE_CMD_MAXZ},
{false, GE_CMD_DEPTHCLAMPENABLE},
{false, GE_CMD_DEPTHCLIPENABLE},
{true, GE_CMD_NOP, "Other raster state"},
{false, GE_CMD_MASKRGB},
+1 -1
View File
@@ -66,7 +66,7 @@ const GECommand g_stateFlagsRows[] = {
GE_CMD_LIGHTENABLE1,
GE_CMD_LIGHTENABLE2,
GE_CMD_LIGHTENABLE3,
GE_CMD_DEPTHCLAMPENABLE,
GE_CMD_DEPTHCLIPENABLE,
GE_CMD_CULLFACEENABLE,
GE_CMD_TEXTUREMAPENABLE,
GE_CMD_FOGENABLE,