mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-21 18:29:05 +02:00
GPU: Move sprites on pixel centers back half a pixel above 1x (compat SnapHalfPixelSprites)
The PS1 emulator draws sprites with their corners on pixel centers (x.5) and their texture coordinates on texel centers. At 1x that covers whole pixels, but above 1x it covers half a pixel more on the right and bottom and reads the neighbouring texels, which shows as seams between tiles. With compat.ini's SnapHalfPixelSprites, such sprites and their texture coordinates are moved back half a pixel, which draws the same at 1x. Texture coordinates are also nudged a 64th of a texel in, as a scaled sprite can sample right on a texel edge, where interpolation can fall short of the texel the GE reads (the emulator's video unpacking does). Set for PS1 games (POPS00000). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
1 parent
cf788d72ca
commit
ef7ca8b85f
4 files changed
+42
No files matched your search
@@ -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);
|
||||
}
|
||||
|
||||
@@ -136,6 +136,7 @@ struct CompatFlags {
|
||||
bool ReadbackShadow;
|
||||
bool ColorPickingDrawSyncReadback;
|
||||
bool SeparateLogicOpDraws;
|
||||
bool SnapHalfPixelSprites;
|
||||
CompatFramebuffer FixedFramebuffer;
|
||||
uint32_t NativeResolutionFramebuffer;
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
Reference in new issue
Block a user