From 5bfc87ffd53e6f44fa04f9eb51c6f4fc7add4109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Sat, 6 Jun 2026 12:31:09 +0200 Subject: [PATCH] Improve the sprite border fix to work with RECTs, add some conditions --- GPU/Common/SoftwareTransformCommon.cpp | 97 +++++++++++++++++--------- GPU/Common/TextureCacheCommon.cpp | 2 +- assets/compat.ini | 7 ++ 3 files changed, 71 insertions(+), 35 deletions(-) diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index 156ec67c73..aea73df6ce 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -78,6 +78,10 @@ static void RotateUV(TransformedVertex v[4]) { } } +static bool ShouldApplySpriteBorderFix(const GPUgstate &gstate) { + return gstate.isAlphaBlendEnabled() && gstate.getBlendFuncA() != GE_SRCBLEND_FIXA; +} + // Clears on the PSP are best done by drawing a series of vertical strips // in clear mode. This tries to detect that. static bool IsReallyAClear(const TransformedVertex *transformed, int numVerts, float x2, float y2) { @@ -595,9 +599,6 @@ static void ClipTrianglesAgainstNearPlane( // Note: This modifies the U/V coordinates of transformed. static void ApplySpriteBorderFixTriangles(TransformedVertex *transformed, const u16 *quad, float uScale, float vScale, float spriteBorderFix) { - const float invUScale = 1.0f / uScale; - const float invVScale = 1.0f / vScale; - // We have two triangles, but the vertex order can really be anything. We just need to find the shared edge, and then check the opposite vertices. // sharedA and sharedB are indices into transformed, picked from the quad array. @@ -677,9 +678,14 @@ static void ApplySpriteBorderFixTriangles(TransformedVertex *transformed, const } } + const float invUScale = 1.0f / uScale; + const float invVScale = 1.0f / vScale; if (validSprite) { // We have a valid sprite! Apply the border fix if needed. if (spriteBorderFix != 0.0f) { + const bool topleft = spriteBorderFix < 0.0f; + spriteBorderFix = fabsf(spriteBorderFix); + //spriteBorderFix *= 10.0f; const float uBorderFix = spriteBorderFix * invUScale; const float vBorderFix = spriteBorderFix * invVScale; @@ -693,24 +699,22 @@ static void ApplySpriteBorderFixTriangles(TransformedVertex *transformed, const if (du != 0.0f && fabsf(dx) != 480.0f) { const float uSign = (du > 0.0f ? 1.0f : -1.0f); const float uAmount = uBorderFix * uSign; - const float xAmount = spriteBorderFix * uSign; - // vSharedA.u += uAmount; - // vCornerB.u += uAmount; + if (topleft) { + vSharedA.u += uAmount; + vCornerB.u += uAmount; + } vCornerA.u -= uAmount; vSharedB.u -= uAmount; - vCornerA.x -= xAmount; - vSharedB.x -= xAmount; } if (dv != 0.0f && fabsf(dy) != 272.0f) { const float vSign = (dv > 0.0f ? 1.0f : -1.0f); const float vAmount = vBorderFix * vSign; - const float yAmount = spriteBorderFix * vSign; - // vSharedA.v += vAmount; - // vCornerA.v += vAmount; + if (topleft) { + vSharedA.v += vAmount; + vCornerA.v += vAmount; + } vCornerB.v -= vAmount; vSharedB.v -= vAmount; - vCornerB.y -= yAmount; - vSharedB.y -= yAmount; } } } @@ -835,15 +839,17 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par } // Apply the sprite border fix, but only if pixel mapping was not detected! - const float spriteBorderFix = PSP_CoreParameter().compat.flags().SpriteBorderFix; // if != 0.0, apply border fix. - if (spriteBorderFix != 0.0f && flat) { - // This assumes that we have a list of two-triangle sprites. If not the position checks will fail anyway. - const u16 *indsIn = (const u16 *)inds; - for (int t = 0; t < vertexCount - 5; t += 6) { - // The previous triangle started three vertices ago. Now, let's do some disgustingly hacky checks, - // to identify the type of sprite (if any) and move the UV coordinates inwards a bit. - const u16 *quad = indsIn + t; - ApplySpriteBorderFixTriangles(transformed, quad, uScale, vScale, spriteBorderFix); + if (flat) { + const float spriteBorderFix = ShouldApplySpriteBorderFix(gstate) ? PSP_CoreParameter().compat.flags().SpriteBorderFix : 0.0f; // if != 0.0, apply border fix. + if (spriteBorderFix != 0.0f) { + // This assumes that we have a list of two-triangle sprites. If not the position checks will fail anyway. + const u16 *indsIn = (const u16 *)inds; + for (int t = 0; t < vertexCount - 5; t += 6) { + // The previous triangle started three vertices ago. Now, let's do some disgustingly hacky checks, + // to identify the type of sprite (if any) and move the UV coordinates inwards a bit. + const u16 *quad = indsIn + t; + ApplySpriteBorderFixTriangles(transformed, quad, uScale, vScale, spriteBorderFix); + } } } @@ -1000,15 +1006,36 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz numDecodedVerts = 4 * (vertexCount / 2); - float uscale = 1.0f; - float vscale = 1.0f; + float uScale = 1.0f; + float vScale = 1.0f; if (throughmode) { - uscale /= gstate_c.curTextureWidth; - vscale /= gstate_c.curTextureHeight; + uScale /= gstate_c.curTextureWidth; + vScale /= gstate_c.curTextureHeight; } bool pixelMapped = g_Config.bSmart2DTexFiltering && !gstate_c.textureIsVideo; + float spriteBorderFixL = 0.0f; + float spriteBorderFixR = 0.0f; + float spriteBorderFixT = 0.0f; + float spriteBorderFixB = 0.0f; + float spriteBorderFix = PSP_CoreParameter().compat.flags().SpriteBorderFix; + if (spriteBorderFix && !ShouldApplySpriteBorderFix(gstate)) { + spriteBorderFix = 0.0f; + } else { + if (spriteBorderFix < 0.0f) { + spriteBorderFixL = (spriteBorderFix / uScale) / gstate_c.curTextureWidth; + spriteBorderFixT = (spriteBorderFix / vScale) / gstate_c.curTextureHeight; + spriteBorderFixR = (spriteBorderFix / uScale) / gstate_c.curTextureWidth; + spriteBorderFixB = (spriteBorderFix / vScale) / gstate_c.curTextureHeight; + } else if (spriteBorderFix > 0.0f) { + spriteBorderFixL = 0.0f; + spriteBorderFixR = (spriteBorderFix / uScale) / gstate_c.curTextureWidth; + spriteBorderFixT = 0.0f; + spriteBorderFixB = (spriteBorderFix / vScale) / gstate_c.curTextureHeight; + } + } + for (int i = 0; i < vertexCount; i += 2) { const TransformedVertex &transVtxTL = transformed[indsIn[i + 0]]; const TransformedVertex &transVtxBR = transformed[indsIn[i + 1]]; @@ -1028,6 +1055,7 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz float z = transVtxBR.z; // Apply Z clamping. It appears clipping/culling does not affect rectangles, see #12058. + // TODO: We might want to make this 65536.999. Since those will pass, and if a game mixes through and non-through... if (z > 65535.0f) { z = 65535.0f; } else if (z < 0.0f) { @@ -1039,33 +1067,34 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz // bottom right trans[0] = transVtxBR; - trans[0].u = transVtxBR.u * uscale; - trans[0].v = transVtxBR.v * vscale; + trans[0].u = (transVtxBR.u + spriteBorderFixR) * uScale; + trans[0].v = (transVtxBR.v + spriteBorderFixB) * vScale; trans[0].z = z; // top right trans[1] = transVtxBR; trans[1].y = transVtxTL.y; - trans[1].u = transVtxBR.u * uscale; - trans[1].v = transVtxTL.v * vscale; + trans[1].u = (transVtxBR.u + spriteBorderFixR) * uScale; + trans[1].v = (transVtxTL.v - spriteBorderFixT) * vScale; trans[1].z = z; // top left trans[2] = transVtxBR; trans[2].x = transVtxTL.x; trans[2].y = transVtxTL.y; - trans[2].u = transVtxTL.u * uscale; - trans[2].v = transVtxTL.v * vscale; + trans[2].u = (transVtxTL.u - spriteBorderFixL) * uScale; + trans[2].v = (transVtxTL.v - spriteBorderFixT) * vScale; trans[2].z = z; // bottom left trans[3] = transVtxBR; trans[3].x = transVtxTL.x; - trans[3].u = transVtxTL.u * uscale; - trans[3].v = transVtxBR.v * vscale; + trans[3].u = (transVtxTL.u - spriteBorderFixL) * uScale; + trans[3].v = (transVtxBR.v + spriteBorderFixB) * vScale; trans[3].z = z; // That's the four corners. Now process UV rotation. + // TODO: Should we apply the sprite border fix before or after rotation? Likely after, right? RotateUV(trans); // Triangle: BR-TR-TL diff --git a/GPU/Common/TextureCacheCommon.cpp b/GPU/Common/TextureCacheCommon.cpp index 0dd630ea41..8bb4139a1a 100644 --- a/GPU/Common/TextureCacheCommon.cpp +++ b/GPU/Common/TextureCacheCommon.cpp @@ -695,7 +695,7 @@ TexCacheEntry *TextureCacheCommon::SetTexture() { gstate_c.curTextureWidth = w; gstate_c.curTextureHeight = h; - gstate_c.SetTextureIsVideo(false); + gstate_c.SetTextureIsVideo((entry->status & TexCacheEntry::STATUS_VIDEO) != 0); gstate_c.SetTextureIs3D((entry->status & TexCacheEntry::STATUS_3D) != 0); gstate_c.SetTextureIsArray(false); // Ordinary 2D textures still aren't used by array view in VK. We probably might as well, though, at this point.. gstate_c.SetTextureIsFramebuffer(false); diff --git a/assets/compat.ini b/assets/compat.ini index 9e7fb33f9e..0f274df21d 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2090,6 +2090,9 @@ ULES00969 = true # NOTE: This is currently tuned only for GTA, but may work with some other games too with filtering # problems. 0.5 is half a texel, which seems to be enough to smooth everything out. +# NOTE: Negative values are interpreted as applying from both top, left, bottom and right, +# while positive values only apply to bottom and right. Different games need different measures. + # Grand Theft Auto: Vice City Stories ULUS10160 = 0.5 ULES00502 = 0.5 @@ -2114,3 +2117,7 @@ ULUX80146 = 0.5 ULUS01826 = 0.5 # Seen in Liberty City (GTA LCS romhack) ULUS11826 = 0.5 + +# Tales of Destiny 2 +# Unfortunately, this affects game backgrounds negatively, although fixes lines in the menus. +ULJS00097 = -0.25