mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-10-22 02:39:05 +02:00
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>
291 lines
13 KiB
C++
291 lines
13 KiB
C++
// Copyright (c) 2013- 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/.
|
|
|
|
#include <cstring>
|
|
|
|
#include "Common/Log.h"
|
|
#include "Common/Data/Format/IniFile.h"
|
|
#include "Common/Data/Text/I18n.h"
|
|
#include "Common/File/VFS/VFS.h"
|
|
#include "Common/StringUtils.h"
|
|
#include "Common/System/OSD.h"
|
|
#include "Common/System/System.h"
|
|
#include "Core/Compatibility.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/System.h"
|
|
|
|
void Compatibility::Load(const std::string &gameID) {
|
|
Clear();
|
|
|
|
// Allow ignoring compat settings by name (regardless of game ID.)
|
|
std::vector<std::string> ignored;
|
|
SplitString(g_Config.sIgnoreCompatSettings, ',', ignored);
|
|
ignored_ = std::set<std::string>(ignored.begin(), ignored.end());
|
|
// If ALL, don't process any compat flags.
|
|
if (ignored_.find("ALL") != ignored_.end())
|
|
return;
|
|
|
|
{
|
|
IniFile compat;
|
|
// This loads from assets.
|
|
if (compat.LoadFromVFS(g_VFS, "compat.ini")) {
|
|
CheckSettings(compat, gameID);
|
|
filesLoaded_.push_back("assets/compat.ini");
|
|
} else {
|
|
auto e = GetI18NCategory(I18NCat::ERRORS);
|
|
std::string msg = ApplySafeSubstitutions(e->T("File not found: %1"), "compat.ini");
|
|
g_OSD.Show(OSDType::MESSAGE_ERROR, msg, 3.0f);
|
|
}
|
|
}
|
|
|
|
{
|
|
IniFile compat2;
|
|
// This one is user-editable. Need to load it after the system one.
|
|
Path path = GetSysDirectory(DIRECTORY_SYSTEM) / "compat.ini";
|
|
if (compat2.Load(path)) {
|
|
CheckSettings(compat2, gameID);
|
|
filesLoaded_.push_back(path.ToString());
|
|
}
|
|
}
|
|
|
|
const int deviceType = System_GetPropertyInt(SYSPROP_DEVICE_TYPE);
|
|
if ((deviceType == DEVICE_TYPE_VR) || g_Config.bForceVR) {
|
|
{
|
|
IniFile compat;
|
|
// This loads from assets.
|
|
if (compat.LoadFromVFS(g_VFS, "compatvr.ini")) {
|
|
CheckVRSettings(compat, gameID);
|
|
filesLoaded_.push_back("assets/compatvr.ini");
|
|
}
|
|
}
|
|
|
|
{
|
|
IniFile compat2;
|
|
// This one is user-editable. Need to load it after the system one.
|
|
Path path = GetSysDirectory(DIRECTORY_SYSTEM) / "compatvr.ini";
|
|
if (compat2.Load(path)) {
|
|
CheckVRSettings(compat2, gameID);
|
|
filesLoaded_.push_back(path.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void Compatibility::Clear() {
|
|
flags_ = {};
|
|
vrCompat_ = {};
|
|
activeList_.clear();
|
|
filesLoaded_.clear();
|
|
}
|
|
|
|
void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) {
|
|
CheckSetting(iniFile, gameID, "VertexDepthRounding", &flags_.VertexDepthRounding);
|
|
CheckSetting(iniFile, gameID, "PixelDepthRounding", &flags_.PixelDepthRounding);
|
|
CheckSetting(iniFile, gameID, "ClearToRAM", &flags_.ClearToRAM);
|
|
CheckSetting(iniFile, gameID, "Force04154000Download", &flags_.Force04154000Download);
|
|
CheckSetting(iniFile, gameID, "DrawSyncEatCycles", &flags_.DrawSyncEatCycles);
|
|
CheckSetting(iniFile, gameID, "DrawSyncInstant", &flags_.DrawSyncInstant);
|
|
CheckSetting(iniFile, gameID, "FakeMipmapChange", &flags_.FakeMipmapChange);
|
|
CheckSetting(iniFile, gameID, "RequireBufferedRendering", &flags_.RequireBufferedRendering);
|
|
CheckSetting(iniFile, gameID, "RequireBlockTransfer", &flags_.RequireBlockTransfer);
|
|
CheckSetting(iniFile, gameID, "RequireDefaultCPUClock", &flags_.RequireDefaultCPUClock);
|
|
CheckSetting(iniFile, gameID, "MGS2AcidHack", &flags_.MGS2AcidHack);
|
|
CheckSetting(iniFile, gameID, "SonicRivalsHack", &flags_.SonicRivalsHack);
|
|
CheckSetting(iniFile, gameID, "TiledDepalHack", &flags_.TiledDepalHack);
|
|
CheckSetting(iniFile, gameID, "BlockTransferAllowCreateFB", &flags_.BlockTransferAllowCreateFB);
|
|
CheckSetting(iniFile, gameID, "IntraVRAMBlockTransferAllowCreateFB", &flags_.IntraVRAMBlockTransferAllowCreateFB);
|
|
CheckSetting(iniFile, gameID, "YugiohSaveFix", &flags_.YugiohSaveFix);
|
|
CheckSetting(iniFile, gameID, "ForceUMDDelay", &flags_.ForceUMDDelay);
|
|
CheckSetting(iniFile, gameID, "ForceMax60FPS", &flags_.ForceMax60FPS);
|
|
CheckSetting(iniFile, gameID, "GoWFramerateHack60", &flags_.GoWFramerateHack60);
|
|
CheckSetting(iniFile, gameID, "FramerateHack30", &flags_.FramerateHack30);
|
|
CheckSetting(iniFile, gameID, "HideISOFiles", &flags_.HideISOFiles);
|
|
CheckSetting(iniFile, gameID, "ForceSoftwareRenderer", &flags_.ForceSoftwareRenderer);
|
|
CheckSetting(iniFile, gameID, "DarkStalkersPresentHack", &flags_.DarkStalkersPresentHack);
|
|
CheckSetting(iniFile, gameID, "ReportSmallMemstick", &flags_.ReportSmallMemstick);
|
|
CheckSetting(iniFile, gameID, "MemstickFixedFree", &flags_.MemstickFixedFree);
|
|
CheckSetting(iniFile, gameID, "DateLimited", &flags_.DateLimited);
|
|
CheckSetting(iniFile, gameID, "DisableFirstFrameReadback", &flags_.DisableFirstFrameReadback);
|
|
CheckSetting(iniFile, gameID, "MpegAvcWarmUp", &flags_.MpegAvcWarmUp);
|
|
CheckSetting(iniFile, gameID, "BlueToAlpha", &flags_.BlueToAlpha);
|
|
CheckSetting(iniFile, gameID, "CenteredLines", &flags_.CenteredLines);
|
|
CheckSetting(iniFile, gameID, "ZZT3SelectHack", &flags_.ZZT3SelectHack);
|
|
CheckSetting(iniFile, gameID, "AllowLargeFBTextureOffsets", &flags_.AllowLargeFBTextureOffsets);
|
|
CheckSetting(iniFile, gameID, "AtracLoopHack", &flags_.AtracLoopHack);
|
|
CheckSetting(iniFile, gameID, "DeswizzleDepth", &flags_.DeswizzleDepth);
|
|
CheckSetting(iniFile, gameID, "SplitFramebufferMargin", &flags_.SplitFramebufferMargin);
|
|
CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOn", &flags_.ForceLowerResolutionForEffectsOn);
|
|
CheckSetting(iniFile, gameID, "ForceLowerResolutionForEffectsOff", &flags_.ForceLowerResolutionForEffectsOff);
|
|
CheckSetting(iniFile, gameID, "AllowDownloadCLUT", &flags_.AllowDownloadCLUT);
|
|
CheckSetting(iniFile, gameID, "NearestFilteringOnFramebufferCreate", &flags_.NearestFilteringOnFramebufferCreate);
|
|
CheckSetting(iniFile, gameID, "EnglishOrJapaneseOnly", &flags_.EnglishOrJapaneseOnly);
|
|
CheckSetting(iniFile, gameID, "OldAdrenoPixelDepthRoundingGL", &flags_.OldAdrenoPixelDepthRoundingGL);
|
|
CheckSetting(iniFile, gameID, "ForceCircleButtonConfirm", &flags_.ForceCircleButtonConfirm);
|
|
CheckSetting(iniFile, gameID, "DisallowFramebufferAtOffset", &flags_.DisallowFramebufferAtOffset);
|
|
CheckSetting(iniFile, gameID, "RockmanDash2SoundFix", &flags_.RockmanDash2SoundFix);
|
|
CheckSetting(iniFile, gameID, "ReadbackDepth", &flags_.ReadbackDepth);
|
|
CheckSetting(iniFile, gameID, "BlockTransferDepth", &flags_.BlockTransferDepth);
|
|
CheckSetting(iniFile, gameID, "DaxterRotatedAnalogStick", &flags_.DaxterRotatedAnalogStick);
|
|
CheckSetting(iniFile, gameID, "ForceMaxDepthResolution", &flags_.ForceMaxDepthResolution);
|
|
CheckSetting(iniFile, gameID, "LoadCLUTFromCurrentFrameOnly", &flags_.LoadCLUTFromCurrentFrameOnly);
|
|
CheckSetting(iniFile, gameID, "ForceUMDReadSpeed", &flags_.ForceUMDReadSpeed);
|
|
CheckSetting(iniFile, gameID, "KernelGetSystemTimeLowEatMoreCycles", &flags_.KernelGetSystemTimeLowEatMoreCycles);
|
|
CheckSetting(iniFile, gameID, "TacticsOgreEliminateDebugReadback", &flags_.TacticsOgreEliminateDebugReadback);
|
|
CheckSetting(iniFile, gameID, "FramebufferAllowLargeVerticalOffset", &flags_.FramebufferAllowLargeVerticalOffset);
|
|
CheckSetting(iniFile, gameID, "DisableMemcpySlicing", &flags_.DisableMemcpySlicing);
|
|
CheckSetting(iniFile, gameID, "ForceEnableGPUReadback", &flags_.ForceEnableGPUReadback);
|
|
CheckSetting(iniFile, gameID, "UseFFMPEGFindStreamInfo", &flags_.UseFFMPEGFindStreamInfo);
|
|
CheckSetting(iniFile, gameID, "SoftwareRasterDepth", &flags_.SoftwareRasterDepth);
|
|
CheckSetting(iniFile, gameID, "DisableHLESceFont", &flags_.DisableHLESceFont);
|
|
CheckSetting(iniFile, gameID, "ForceHLEPsmf", &flags_.ForceHLEPsmf);
|
|
CheckSetting(iniFile, gameID, "SaveStatesNotRecommended", &flags_.SaveStatesNotRecommended);
|
|
CheckSetting(iniFile, gameID, "MsgDialogAutoStatus", &flags_.MsgDialogAutoStatus);
|
|
CheckSetting(iniFile, gameID, "NullPageValid", &flags_.NullPageValid);
|
|
CheckSetting(iniFile, gameID, "DetectDestBlendSquared", &flags_.DetectDestBlendSquared);
|
|
CheckSetting(iniFile, gameID, "BoostExactFramebufferMatch", &flags_.BoostExactFramebufferMatch);
|
|
CheckSetting(iniFile, gameID, "PersistentFramebuffers", &flags_.PersistentFramebuffers);
|
|
CheckSetting(iniFile, gameID, "FileCreatedTimeHack", &flags_.FileCreatedTimeHack);
|
|
CheckSetting(iniFile, gameID, "FastEmulatedGPU", &flags_.FastEmulatedGPU);
|
|
CheckSetting(iniFile, gameID, "CorrectCullAfterClip", &flags_.CorrectCullAfterClip);
|
|
CheckSetting(iniFile, gameID, "SpriteBorderFix", &flags_.SpriteBorderFix);
|
|
CheckSetting(iniFile, gameID, "TextureCLUTInShader", &flags_.TextureCLUTInShader);
|
|
CheckSetting(iniFile, gameID, "DisableRangeCulling", &flags_.DisableRangeCulling);
|
|
CheckSetting(iniFile, gameID, "BlockTransferReadbackSmall", &flags_.BlockTransferReadbackSmall);
|
|
CheckSetting(iniFile, gameID, "DepthCLUTIntoHalves", &flags_.DepthCLUTIntoHalves);
|
|
CheckSetting(iniFile, gameID, "AccurateSplines", &flags_.AccurateSplines);
|
|
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);
|
|
}
|
|
|
|
void Compatibility::CheckVRSettings(IniFile &iniFile, const std::string &gameID) {
|
|
CheckSetting(iniFile, gameID, "ForceFlatScreen", &vrCompat_.ForceFlatScreen);
|
|
CheckSetting(iniFile, gameID, "ForceMono", &vrCompat_.ForceMono);
|
|
CheckSetting(iniFile, gameID, "IdentityViewHack", &vrCompat_.IdentityViewHack);
|
|
CheckSetting(iniFile, gameID, "MirroringVariant", &vrCompat_.MirroringVariant);
|
|
CheckSetting(iniFile, gameID, "ProjectionHack", &vrCompat_.ProjectionHack);
|
|
CheckSetting(iniFile, gameID, "Skyplane", &vrCompat_.Skyplane);
|
|
CheckSetting(iniFile, gameID, "UnitsPerMeter", &vrCompat_.UnitsPerMeter);
|
|
|
|
NOTICE_LOG(Log::G3D, "UnitsPerMeter for %s: %f", gameID.c_str(), vrCompat_.UnitsPerMeter);
|
|
}
|
|
|
|
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) {
|
|
if (ignored_.find(option) == ignored_.end()) {
|
|
Section *section = iniFile.GetSection(option);
|
|
if (!section) {
|
|
// Not found, skip.
|
|
return;
|
|
}
|
|
section->Get(gameID, flag);
|
|
|
|
// Shortcut for debugging, sometimes useful to globally enable compat flags.
|
|
bool all = false;
|
|
section->Get("ALL", &all);
|
|
if (all) {
|
|
*flag = true;
|
|
}
|
|
|
|
if (*flag) {
|
|
if (!activeList_.empty()) {
|
|
activeList_ += "\n";
|
|
}
|
|
activeList_ += option;
|
|
activeList_ += ": True";
|
|
}
|
|
}
|
|
}
|
|
|
|
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, float *flag) {
|
|
std::string value;
|
|
Section *section = iniFile.GetSection(option);
|
|
if (section && section->Get(gameID.c_str(), &value)) {
|
|
// Not stof - it throws on a malformed entry, and compat.ini is user-editable.
|
|
float parsed;
|
|
if (sscanf(value.c_str(), "%f", &parsed) != 1) {
|
|
WARN_LOG(Log::Loader, "compat.ini: [%s] %s is not a number: '%s'", option, gameID.c_str(), value.c_str());
|
|
return;
|
|
}
|
|
*flag = parsed;
|
|
|
|
if (!activeList_.empty()) {
|
|
activeList_ += "\n";
|
|
}
|
|
activeList_ += std::string(option) + ": " + std::to_string(*flag);
|
|
}
|
|
}
|
|
|
|
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, CompatFramebuffer *value) {
|
|
std::string text;
|
|
Section *section = iniFile.GetSection(option);
|
|
if (ignored_.find(option) != ignored_.end() || !section || !section->Get(gameID.c_str(), &text)) {
|
|
return;
|
|
}
|
|
CompatFramebuffer parsed{};
|
|
if (sscanf(text.c_str(), "%x,%d,%d", &parsed.address, &parsed.width, &parsed.height) != 3 || parsed.width <= 0 || parsed.height <= 0) {
|
|
WARN_LOG(Log::Loader, "compat.ini: [%s] %s is not address,width,height: '%s'", option, gameID.c_str(), text.c_str());
|
|
return;
|
|
}
|
|
parsed.address &= 0x041FFFFF;
|
|
*value = parsed;
|
|
|
|
if (!activeList_.empty()) {
|
|
activeList_ += "\n";
|
|
}
|
|
activeList_ += std::string(option) + ": " + text;
|
|
}
|
|
|
|
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, uint32_t *address) {
|
|
std::string text;
|
|
Section *section = iniFile.GetSection(option);
|
|
if (ignored_.find(option) != ignored_.end() || !section || !section->Get(gameID.c_str(), &text)) {
|
|
return;
|
|
}
|
|
uint32_t parsed;
|
|
if (sscanf(text.c_str(), "%x", &parsed) != 1) {
|
|
WARN_LOG(Log::Loader, "compat.ini: [%s] %s is not an address: '%s'", option, gameID.c_str(), text.c_str());
|
|
return;
|
|
}
|
|
*address = parsed & 0x041FFFFF;
|
|
|
|
if (!activeList_.empty()) {
|
|
activeList_ += "\n";
|
|
}
|
|
activeList_ += std::string(option) + ": " + text;
|
|
}
|
|
|
|
void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, int *flag) {
|
|
std::string value;
|
|
Section *section = iniFile.GetSection(option);
|
|
if (section && section->Get(gameID.c_str(), &value)) {
|
|
int parsed;
|
|
if (sscanf(value.c_str(), "%d", &parsed) != 1) {
|
|
WARN_LOG(Log::Loader, "compat.ini: [%s] %s is not an integer: '%s'", option, gameID.c_str(), value.c_str());
|
|
return;
|
|
}
|
|
*flag = parsed;
|
|
|
|
if (!activeList_.empty()) {
|
|
activeList_ += ":" + std::to_string(*flag) + "\n";
|
|
}
|
|
activeList_ += std::string(option) + ": " + std::to_string(*flag);
|
|
}
|
|
}
|