diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 1ae81a7026..9d9bc2098f 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -170,6 +170,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ReadbackShadow", &flags_.ReadbackShadow); CheckSetting(iniFile, gameID, "ColorPickingDrawSyncReadback", &flags_.ColorPickingDrawSyncReadback); CheckSetting(iniFile, gameID, "SeparateLogicOpDraws", &flags_.SeparateLogicOpDraws); + CheckSetting(iniFile, gameID, "SnapHalfPixelSprites", &flags_.SnapHalfPixelSprites); CheckSetting(iniFile, gameID, "FixedFramebuffer", &flags_.FixedFramebuffer); CheckSetting(iniFile, gameID, "NativeResolutionFramebuffer", &flags_.NativeResolutionFramebuffer); } diff --git a/Core/Compatibility.h b/Core/Compatibility.h index dcacbe3fea..a700a736aa 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -136,6 +136,7 @@ struct CompatFlags { bool ReadbackShadow; bool ColorPickingDrawSyncReadback; bool SeparateLogicOpDraws; + bool SnapHalfPixelSprites; CompatFramebuffer FixedFramebuffer; uint32_t NativeResolutionFramebuffer; }; diff --git a/GPU/Common/SoftwareTransformCommon.cpp b/GPU/Common/SoftwareTransformCommon.cpp index b16ffbb6dc..745c03c250 100644 --- a/GPU/Common/SoftwareTransformCommon.cpp +++ b/GPU/Common/SoftwareTransformCommon.cpp @@ -1009,6 +1009,20 @@ static SoftwareTransformAction ProjectClipAndExpand(SoftwareTransformParams &par return SW_DRAW_INDEXED; } +// A sprite with its corners on pixel centers (x.5), as the PS1 emulator draws them. Above 1x it covers +// half a pixel more on the right and bottom and reads its neighbours' texels (seams between tiles). +// Moved back half a pixel, with its texture coordinates, it draws the same at 1x. +static bool IsHalfPixelSprite(const TransformedVertex &tl, const TransformedVertex &br) { + auto onCenter = [](float f) { + const float frac = f - floorf(f); + return frac > 0.49f && frac < 0.51f; + }; + if (!onCenter(tl.x) || !onCenter(tl.y) || !onCenter(br.x) || !onCenter(br.y)) { + return false; + } + return br.x > tl.x && br.y > tl.y; +} + static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSize, u16 *&inds, int indsSize, const TransformedVertex *transformed, TransformedVertex *transformedExpanded, int *drawIndexCount, bool throughmode, bool *pixelMappedExactly) { // Before we start, do a sanity check - does the output fit? // The expanded indices are written after the input ones, at inds + vertexCount. @@ -1160,6 +1174,26 @@ static bool ExpandRectangles(int vertexCount, int &numDecodedVerts, int vertsSiz trans[3].v = (transVtxBR.v - spriteBorderFixB) * vScale; trans[3].z = z; + if (PSP_CoreParameter().compat.flags().SnapHalfPixelSprites && gstate.isTextureMapEnabled()) { + // In texels for through mode (scaled below), else normalized. + const float texW = throughmode ? 1.0f : (float)gstate.getTextureWidth(0); + const float texH = throughmode ? 1.0f : (float)gstate.getTextureHeight(0); + if (IsHalfPixelSprite(transVtxTL, transVtxBR)) { + // Half a pixel in texture coordinates. Plus a nudge in: a scaled sprite can sample right on a + // texel edge, where interpolation here can fall short of the texel the GE reads. + const float du = 0.5f * (transVtxBR.u - transVtxTL.u) / (transVtxBR.x - transVtxTL.x) * (throughmode ? uScale : 1.0f); + const float dv = 0.5f * (transVtxBR.v - transVtxTL.v) / (transVtxBR.y - transVtxTL.y) * (throughmode ? vScale : 1.0f); + const float nudgeU = (1.0f / 64.0f) / texW * (throughmode ? uScale : 1.0f); + const float nudgeV = (1.0f / 64.0f) / texH * (throughmode ? vScale : 1.0f); + for (int c = 0; c < 4; c++) { + trans[c].x -= 0.5f; + trans[c].y -= 0.5f; + trans[c].u += nudgeU - du; + trans[c].v += nudgeV - dv; + } + } + } + // 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); diff --git a/assets/compat.ini b/assets/compat.ini index 684a82682a..67f4143157 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -2270,3 +2270,9 @@ POPS00000 = 0x04100000 # draw's rectangles one by one), so each sees the ones before in the framebuffer copy. Costs a copy per draw. # The PS1 emulator puts each 24-bit video frame together by ORing passes into the same pixels. POPS00000 = true + +[SnapHalfPixelSprites] +# Textured sprites with their corners on pixel centers (x.5) and their texture coordinates on texel centers +# are moved back half a pixel, which draws the same at 1x. Above 1x they otherwise cover half a pixel more +# and read their neighbours' texels: seams between tiles. The PS1 emulator draws its sprites like that. +POPS00000 = true