Files
ppsspp/Core/Compatibility.h
T
Henrik RydgårdandClaude Opus 5.5 ef7ca8b85f 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>
2026-10-21 14:51:06 +02:00

193 lines
6.2 KiB
C++

// Copyright (c) 2015- PPSSPP Project.
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, version 2.0 or later versions.
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License 2.0 for more details.
// A copy of the GPL 2.0 should have been included with the program.
// If not, see http://www.gnu.org/licenses/
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#pragma once
#include <string>
#include <vector>
#include <cstdint>
#include <set>
// compat.ini's ID for every PS1 game: they're all run by the firmware's same PS1 emulator. In the
// usual letters-and-digits form, so it parses like any other ID.
constexpr const char *POPS_COMPAT_ID = "POPS00000";
// Compatibility flags are controlled by assets/compat.ini.
// Alternatively, if PSP/System/compat.ini exists, it is merged on top, to enable editing
// the file on Android for tests.
//
// This file is not meant to be user-editable, although is kept as a separate ini
// file instead of compiled into the code for debugging purposes.
//
// The uses cases are strict:
// * Enable fixes for things we can't reasonably emulate without completely ruining
// performance for other games, such as the screen copies in Dangan Ronpa
// * Disabling accuracy features like 16-bit depth rounding, when we can't seem to
// implement them at all in a 100% compatible way
// * Emergency game-specific compatibility fixes before releases, such as the GTA
// music problem where every attempted fix has reduced compatibility with other games
// * Enable "unsafe" performance optimizations that some games can tolerate and
// others cannot. We do not currently have any of those.
//
// This functionality should NOT be used for any of the following:
// * Cheats
// * Fun hacks, like enlarged heads or whatever
// * Fixing general compatibility issues. First try to find a general solution. Try hard.
//
// We already have the Action Replay-based cheat system for such use cases.
// TODO: Turn into bitfield for smaller mem footprint.
// A framebuffer at a fixed address and size, from compat.ini as "address,width,height" (address in hex).
struct CompatFramebuffer {
uint32_t address;
int width;
int height;
};
struct CompatFlags {
bool VertexDepthRounding;
bool PixelDepthRounding;
bool ClearToRAM;
bool Force04154000Download;
bool DrawSyncEatCycles;
bool DrawSyncInstant;
bool FakeMipmapChange;
bool RequireBufferedRendering;
bool RequireBlockTransfer;
bool RequireDefaultCPUClock;
bool MGS2AcidHack;
bool SonicRivalsHack;
bool TiledDepalHack;
bool BlockTransferAllowCreateFB;
bool IntraVRAMBlockTransferAllowCreateFB;
bool YugiohSaveFix;
bool ForceUMDDelay;
bool ForceMax60FPS;
bool GoWFramerateHack60;
bool FramerateHack30;
bool HideISOFiles;
bool ForceSoftwareRenderer;
bool DarkStalkersPresentHack;
bool ReportSmallMemstick;
bool MemstickFixedFree;
bool DateLimited;
bool DisableFirstFrameReadback;
bool MpegAvcWarmUp;
bool BlueToAlpha;
bool CenteredLines;
bool ZZT3SelectHack;
bool AllowLargeFBTextureOffsets;
bool AtracLoopHack;
bool DeswizzleDepth;
bool SplitFramebufferMargin;
bool ForceLowerResolutionForEffectsOn;
bool ForceLowerResolutionForEffectsOff;
bool AllowDownloadCLUT;
bool NearestFilteringOnFramebufferCreate;
bool EnglishOrJapaneseOnly;
bool OldAdrenoPixelDepthRoundingGL;
bool ForceCircleButtonConfirm;
bool DisallowFramebufferAtOffset;
bool RockmanDash2SoundFix;
bool ReadbackDepth;
bool BlockTransferDepth;
bool DaxterRotatedAnalogStick;
bool ForceMaxDepthResolution;
bool LoadCLUTFromCurrentFrameOnly;
bool ForceUMDReadSpeed;
bool KernelGetSystemTimeLowEatMoreCycles;
bool TacticsOgreEliminateDebugReadback;
bool FramebufferAllowLargeVerticalOffset;
bool DisableMemcpySlicing;
bool ForceEnableGPUReadback;
bool UseFFMPEGFindStreamInfo;
bool SoftwareRasterDepth;
bool DisableHLESceFont;
bool ForceHLEPsmf;
bool SaveStatesNotRecommended;
bool MsgDialogAutoStatus;
bool NullPageValid;
bool DetectDestBlendSquared;
bool BoostExactFramebufferMatch;
bool PersistentFramebuffers;
bool FileCreatedTimeHack;
bool FastEmulatedGPU;
bool CorrectCullAfterClip;
float SpriteBorderFix;
bool TextureCLUTInShader;
bool DisableRangeCulling;
bool BlockTransferReadbackSmall;
bool DepthCLUTIntoHalves;
bool AccurateSplines;
bool ReadbackShadow;
bool ColorPickingDrawSyncReadback;
bool SeparateLogicOpDraws;
bool SnapHalfPixelSprites;
CompatFramebuffer FixedFramebuffer;
uint32_t NativeResolutionFramebuffer;
};
struct VRCompat {
bool ForceMono;
bool ForceFlatScreen;
bool IdentityViewHack;
int MirroringVariant;
bool ProjectionHack;
bool Skyplane;
float UnitsPerMeter;
};
class IniFile;
class Compatibility {
public:
Compatibility() {
Clear();
}
// Flags enforced read-only through const. Only way to change them is to load assets/compat.ini.
const CompatFlags &flags() const { return flags_; }
const VRCompat &vrCompat() const { return vrCompat_; }
void Load(const std::string &gameID);
// No flags at all.
void Clear();
const std::string &GetActiveFlagsString() const {
return activeList_;
}
const std::vector<std::string> &filesLoaded() const { return filesLoaded_; }
private:
void CheckSettings(IniFile &iniFile, const std::string &gameID);
void CheckVRSettings(IniFile &iniFile, const std::string &gameID);
void CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag);
void CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, float *value);
void CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, int *value);
void CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, CompatFramebuffer *value);
// A VRAM address, in hex.
void CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, uint32_t *address);
CompatFlags flags_{};
VRCompat vrCompat_{};
std::set<std::string> ignored_;
std::string activeList_;
std::vector<std::string> filesLoaded_;
};