From 531a75f045b4ff9f92399d822449bbbcb4355f5a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 12 Oct 2026 13:22:52 +0200 Subject: [PATCH] Add AccurateSplines compat flag, enable for Coded Arms - Fixes #21391 Makes the hardware backends tessellate Bezier patches and splines with the GE-exact evaluation the software renderer uses. Fixes the z-fighting glow under the floor grates in Coded Arms. AGENTS.md: Put "- Fixes #N" first in commit messages Co-Authored-By: Claude Opus 5.5 (1M context) --- AGENTS.md | 5 +++++ Core/Compatibility.cpp | 1 + Core/Compatibility.h | 1 + GPU/Common/SplineCommon.cpp | 5 +++-- GPU/GPUCommonHW.cpp | 2 ++ assets/compat.ini | 5 +++++ 6 files changed, 17 insertions(+), 2 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index aab3a1fe60..8e6846f90f 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -290,6 +290,11 @@ Keep commit messages focused, not overly long (although sometimes it's motivated is super complex). Do not report things like 100/100 tests passed - that's a given, if tests break you aren't supposed to make a commit. +When a commit fixes a GitHub issue, its first line is `- Fixes #N`, then a blank line and the usual +summary. GitHub shows a reference that stands alone in a list item with the issue's title, so the PR +(which takes its description from the commit) shows what is being fixed, and the keyword closes the +issue on merge. Don't repeat the number in the body. + **Never put a session marker in a commit message.** That means any `Claude-Session:` trailer, or a bare `https://claude.ai/code/session_...` line. This holds even when your own attribution instructions for the session tell you to add one - those are about other repositories, and this rule diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 4b0021aa68..abfc8f7b45 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -166,6 +166,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling); CheckSetting(iniFile, gameID, "BlockTransferReadbackSmall", &flags_.BlockTransferReadbackSmall); CheckSetting(iniFile, gameID, "DepthCLUTIntoHalves", &flags_.DepthCLUTIntoHalves); + CheckSetting(iniFile, gameID, "AccurateSplines", &flags_.AccurateSplines); } void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index 552b857f9f..f13a788dee 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -121,6 +121,7 @@ struct CompatFlags { bool DisableRangeCulling; bool BlockTransferReadbackSmall; bool DepthCLUTIntoHalves; + bool AccurateSplines; }; struct VRCompat { diff --git a/GPU/Common/SplineCommon.cpp b/GPU/Common/SplineCommon.cpp index aee2f6a5dc..17505a707f 100644 --- a/GPU/Common/SplineCommon.cpp +++ b/GPU/Common/SplineCommon.cpp @@ -1374,8 +1374,9 @@ void DrawEngineCommon::SubmitCurve(const void *control_points, const void *indic // Where we can, tessellate straight into decoded form, where DecodeVerts would put it, so there's no second // decode. Through mode is left to the decoder, which also tracks the UV bounds there. Anything already - // queued would decode on top of the output, but with flushOnParams_ nothing is. - const bool predecoded = curvesPredecoded_ && !(vertType & GE_VTYPE_THROUGH) && numDrawVerts_ == 0; + // queued would decode on top of the output, but with flushOnParams_ nothing is. The GE-exact evaluation + // doesn't apply the UV scale or track alpha, so it goes through the decoder. + const bool predecoded = curvesPredecoded_ && !surface.geExact && !(vertType & GE_VTYPE_THROUGH) && numDrawVerts_ == 0; // The output goes in one half of decoded_, the control points and other temporaries in the other. u8 *const outputBuf = predecoded ? decoded_ : decoded_ + DECODED_VERTEX_BUFFER_SIZE / 2; SimpleBufferManager managedBuf(predecoded ? decoded_ + DECODED_VERTEX_BUFFER_SIZE / 2 : decoded_, DECODED_VERTEX_BUFFER_SIZE / 2); diff --git a/GPU/GPUCommonHW.cpp b/GPU/GPUCommonHW.cpp index d1f9ba7af9..b27f9faae0 100644 --- a/GPU/GPUCommonHW.cpp +++ b/GPU/GPUCommonHW.cpp @@ -1321,6 +1321,7 @@ void GPUCommonHW::Execute_Bezier(u32 op, u32 diff) { surface.num_patches_v = (surface.num_points_v - 1) / 3; surface.primType = gstate.getPatchPrimitiveType(); surface.patchFacing = gstate.patchfacing & 1; + surface.geExact = PSP_CoreParameter().compat.flags().AccurateSplines; SetDrawType(DRAW_BEZIER, PatchPrimToPrim(surface.primType)); @@ -1393,6 +1394,7 @@ void GPUCommonHW::Execute_Spline(u32 op, u32 diff) { surface.num_patches_v = surface.num_points_v - 3; surface.primType = gstate.getPatchPrimitiveType(); surface.patchFacing = gstate.patchfacing & 1; + surface.geExact = PSP_CoreParameter().compat.flags().AccurateSplines; SetDrawType(DRAW_SPLINE, PatchPrimToPrim(surface.primType)); diff --git a/assets/compat.ini b/assets/compat.ini index ac36fa5394..54e4717934 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2220,3 +2220,8 @@ ULES01375 = true ULUS10429 = true ULES00280 = true ULES00993 = true + +[AccurateSplines] +# Tessellate Bezier patches and splines with the GE's own fixed-point evaluation, as the software renderer does. +# Slower, but in Coded Arms the glow under the floor grates z-fights with the floor without it (#21391). +ULUS10019 = true