diff --git a/GPU/Common/DrawEngineCommon.cpp b/GPU/Common/DrawEngineCommon.cpp index 8dce57d06f..02b5e20bc2 100644 --- a/GPU/Common/DrawEngineCommon.cpp +++ b/GPU/Common/DrawEngineCommon.cpp @@ -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 diff --git a/GPU/Common/ShaderId.cpp b/GPU/Common/ShaderId.cpp index 0ecb1b7359..b237bbc8a9 100644 --- a/GPU/Common/ShaderId.cpp +++ b/GPU/Common/ShaderId.cpp @@ -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)) { diff --git a/GPU/Common/ShaderId.h b/GPU/Common/ShaderId.h index 9c3b295e8b..35f0747558 100644 --- a/GPU/Common/ShaderId.h +++ b/GPU/Common/ShaderId.h @@ -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 diff --git a/GPU/Common/VertexShaderGenerator.cpp b/GPU/Common/VertexShaderGenerator.cpp index 6e85386566..05d86cafad 100644 --- a/GPU/Common/VertexShaderGenerator.cpp +++ b/GPU/Common/VertexShaderGenerator.cpp @@ -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); } } diff --git a/GPU/Debugger/GECommandTable.cpp b/GPU/Debugger/GECommandTable.cpp index 97c34f1810..d2443b1169 100644 --- a/GPU/Debugger/GECommandTable.cpp +++ b/GPU/Debugger/GECommandTable.cpp @@ -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" } }, diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index 41df4774d2..431911575e 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -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. diff --git a/GPU/GeDisasm.cpp b/GPU/GeDisasm.cpp index 5c41ffebd3..ad7a672d68 100644 --- a/GPU/GeDisasm.cpp +++ b/GPU/GeDisasm.cpp @@ -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; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index f56c254dac..d1d683b182 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -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 }, diff --git a/GPU/ge_constants.h b/GPU/ge_constants.h index f4a366f802..4b8460de9b 100644 --- a/GPU/ge_constants.h +++ b/GPU/ge_constants.h @@ -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, diff --git a/UI/ImDebugger/ImGe.cpp b/UI/ImDebugger/ImGe.cpp index 794f5f81ac..c28804f5c2 100644 --- a/UI/ImDebugger/ImGe.cpp +++ b/UI/ImDebugger/ImGe.cpp @@ -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}, diff --git a/Windows/GEDebugger/TabState.cpp b/Windows/GEDebugger/TabState.cpp index a90caa66a6..96eb204665 100644 --- a/Windows/GEDebugger/TabState.cpp +++ b/Windows/GEDebugger/TabState.cpp @@ -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,