Introduce "Compatibility Flags".

These should be used very restrictively, see comment in Compatibility.h.

Should help #8004, by disabling depth rounding in Fight Night round 3.
This commit is contained in:
Henrik Rydgard 2015-09-26 16:01:16 +02:00
parent c6789c28f9
commit b07b002040
16 changed files with 182 additions and 13 deletions

View file

@ -1132,6 +1132,8 @@ add_library(${CoreLibName} ${CoreLinkType}
Core/Config.h Core/Config.h
Core/Core.cpp Core/Core.cpp
Core/Core.h Core/Core.h
Core/Compatibility.cpp
Core/Compatibility.h
Core/CoreParameter.h Core/CoreParameter.h
Core/CoreTiming.cpp Core/CoreTiming.cpp
Core/CoreTiming.h Core/CoreTiming.h

43
Core/Compatibility.cpp Normal file
View file

@ -0,0 +1,43 @@
// 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 "file/ini_file.h"
#include "Core/Compatibility.h"
#include "Core/System.h"
void Compatibility::Load(const std::string &gameID) {
IniFile compat;
Clear();
std::string path = GetSysDirectory(DIRECTORY_SYSTEM) + "compat.ini";
if (compat.Load(path)) {
LoadIniSection(compat, gameID);
}
// This loads from assets.
if (compat.LoadFromVFS("compat.ini")) {
LoadIniSection(compat, gameID);
}
}
void Compatibility::Clear() {
memset(&flags_, 0, sizeof(flags_));
}
void Compatibility::LoadIniSection(IniFile &iniFile, std::string section) {
iniFile.Get(section.c_str(), "NoDepthRounding", &flags_.NoDepthRounding, flags_.NoDepthRounding);
}

70
Core/Compatibility.h Normal file
View file

@ -0,0 +1,70 @@
// 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 <stdint.h>
// 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.
struct CompatFlags {
bool NoDepthRounding;
// GTAMusicFix, ...
};
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_; }
void Load(const std::string &gameID);
private:
void Clear();
void LoadIniSection(IniFile &iniFile, std::string section);
CompatFlags flags_;
};

View file

@ -179,6 +179,7 @@
<ClCompile Include="..\ext\udis86\syn-intel.c" /> <ClCompile Include="..\ext\udis86\syn-intel.c" />
<ClCompile Include="..\ext\udis86\syn.c" /> <ClCompile Include="..\ext\udis86\syn.c" />
<ClCompile Include="..\ext\udis86\udis86.c" /> <ClCompile Include="..\ext\udis86\udis86.c" />
<ClCompile Include="Compatibility.cpp" />
<ClCompile Include="Config.cpp" /> <ClCompile Include="Config.cpp" />
<ClCompile Include="Core.cpp" /> <ClCompile Include="Core.cpp" />
<ClCompile Include="CoreTiming.cpp" /> <ClCompile Include="CoreTiming.cpp" />
@ -501,6 +502,7 @@
<ClInclude Include="..\ext\udis86\types.h" /> <ClInclude Include="..\ext\udis86\types.h" />
<ClInclude Include="..\ext\udis86\udint.h" /> <ClInclude Include="..\ext\udis86\udint.h" />
<ClInclude Include="..\ext\udis86\udis86.h" /> <ClInclude Include="..\ext\udis86\udis86.h" />
<ClInclude Include="Compatibility.h" />
<ClInclude Include="Config.h" /> <ClInclude Include="Config.h" />
<ClInclude Include="Core.h" /> <ClInclude Include="Core.h" />
<ClInclude Include="CoreParameter.h" /> <ClInclude Include="CoreParameter.h" />

View file

@ -622,6 +622,9 @@
<ClCompile Include="FileLoaders\DiskCachingFileLoader.cpp"> <ClCompile Include="FileLoaders\DiskCachingFileLoader.cpp">
<Filter>FileLoaders</Filter> <Filter>FileLoaders</Filter>
</ClCompile> </ClCompile>
<ClCompile Include="Compatibility.cpp">
<Filter>Core</Filter>
</ClCompile>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<ClInclude Include="ELF\ElfReader.h"> <ClInclude Include="ELF\ElfReader.h">
@ -1167,6 +1170,9 @@
<ClInclude Include="HLE\ThreadQueueList.h"> <ClInclude Include="HLE\ThreadQueueList.h">
<Filter>HLE</Filter> <Filter>HLE</Filter>
</ClInclude> </ClInclude>
<ClInclude Include="Compatibility.h">
<Filter>Core</Filter>
</ClInclude>
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<None Include="CMakeLists.txt" /> <None Include="CMakeLists.txt" />

View file

@ -19,6 +19,8 @@
#include <string> #include <string>
#include "Core/Compatibility.h"
enum CPUCore { enum CPUCore {
CPU_INTERPRETER, CPU_INTERPRETER,
CPU_JIT, CPU_JIT,
@ -69,4 +71,6 @@ struct CoreParameter {
bool frozen; bool frozen;
FileLoader *mountIsoLoader; FileLoader *mountIsoLoader;
Compatibility compat;
}; };

View file

@ -1237,7 +1237,7 @@ u32 sceKernelReferThreadStatus(u32 threadID, u32 statusPtr)
return SCE_KERNEL_ERROR_ILLEGAL_SIZE; return SCE_KERNEL_ERROR_ILLEGAL_SIZE;
} }
DEBUG_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr); VERBOSE_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr);
t->nt.nativeSize = THREADINFO_SIZE_AFTER_260; t->nt.nativeSize = THREADINFO_SIZE_AFTER_260;
if (wantedSize != 0) if (wantedSize != 0)
@ -1248,7 +1248,7 @@ u32 sceKernelReferThreadStatus(u32 threadID, u32 statusPtr)
} }
else else
{ {
DEBUG_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr); VERBOSE_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr);
t->nt.nativeSize = THREADINFO_SIZE; t->nt.nativeSize = THREADINFO_SIZE;
u32 sz = std::min(THREADINFO_SIZE, wantedSize); u32 sz = std::min(THREADINFO_SIZE, wantedSize);

View file

@ -276,7 +276,6 @@ inline void MemcpyUnchecked(const u32 to_address, const u32 from_address, const
MemcpyUnchecked(GetPointer(to_address), from_address, len); MemcpyUnchecked(GetPointer(to_address), from_address, len);
} }
// TODO: This considers 0x88900000 a valid address. Probably not good.
inline bool IsValidAddress(const u32 address) { inline bool IsValidAddress(const u32 address) {
if ((address & 0x3E000000) == 0x08000000) { if ((address & 0x3E000000) == 0x08000000) {
return true; return true;

View file

@ -204,6 +204,11 @@ void CPU_Init() {
break; break;
} }
// Here we have read the PARAM.SFO, let's see if we need any compatibility overrides.
std::string discID = g_paramSFO.GetValueString("DISC_ID");
if (!discID.empty())
coreParameter.compat.Load(discID);
Memory::Init(); Memory::Init();
mipsr4k.Reset(); mipsr4k.Reset();

View file

@ -466,6 +466,17 @@ void DIRECTX9_GPU::UpdateCmdInfo() {
cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE; cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE;
cmdInfo_[GE_CMD_VERTEXTYPE].func = &DIRECTX9_GPU::Execute_VertexType; cmdInfo_[GE_CMD_VERTEXTYPE].func = &DIRECTX9_GPU::Execute_VertexType;
} }
u32 features = 0;
// Set some flags that may be convenient in the future if we merge the backends more.
features |= GPU_SUPPORTS_BLEND_MINMAX;
features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL;
if (!PSP_CoreParameter().compat.flags().NoDepthRounding)
features |= GPU_ROUND_DEPTH_TO_16BIT;
gstate_c.featureFlags = features;
} }
DIRECTX9_GPU::~DIRECTX9_GPU() { DIRECTX9_GPU::~DIRECTX9_GPU() {

View file

@ -226,7 +226,7 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
} }
} }
if (!gstate.isModeThrough()) { if (!gstate.isModeThrough() && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, "float4 u_depthRange : register(c%i);\n", CONST_VS_DEPTHRANGE); WRITE(p, "float4 u_depthRange : register(c%i);\n", CONST_VS_DEPTHRANGE);
} }
@ -286,7 +286,7 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
// Confirmed: Through mode gets through exactly the same in GL and D3D in Phantasy Star: Text is 38023.0 in the test scene. // Confirmed: Through mode gets through exactly the same in GL and D3D in Phantasy Star: Text is 38023.0 in the test scene.
if (!gstate.isModeThrough()) { if (!gstate.isModeThrough() && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
// Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection. // Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection.
// The Z range in D3D is different but we compensate for that using parameters. // The Z range in D3D is different but we compensate for that using parameters.
WRITE(p, "\nfloat4 depthRoundZVP(float4 v) {\n"); WRITE(p, "\nfloat4 depthRoundZVP(float4 v) {\n");
@ -328,7 +328,11 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
if (gstate.isModeThrough()) { if (gstate.isModeThrough()) {
WRITE(p, " Out.gl_Position = mul(float4(In.position.xyz, 1.0), u_proj_through);\n"); WRITE(p, " Out.gl_Position = mul(float4(In.position.xyz, 1.0), u_proj_through);\n");
} else { } else {
WRITE(p, " Out.gl_Position = depthRoundZVP(mul(float4(In.position.xyz, 1.0), u_proj));\n"); if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, " Out.gl_Position = depthRoundZVP(mul(float4(In.position.xyz, 1.0), u_proj));\n");
} else {
WRITE(p, " Out.gl_Position = mul(float4(In.position.xyz, 1.0), u_proj);\n");
}
} }
} else { } else {
// Step 1: World Transform / Skinning // Step 1: World Transform / Skinning
@ -417,7 +421,11 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) {
WRITE(p, " float4 viewPos = float4(mul(float4(worldpos, 1.0), u_view), 1.0);\n"); WRITE(p, " float4 viewPos = float4(mul(float4(worldpos, 1.0), u_view), 1.0);\n");
// Final view and projection transforms. // Final view and projection transforms.
WRITE(p, " Out.gl_Position = depthRoundZVP(mul(viewPos, u_proj));\n"); if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, " Out.gl_Position = depthRoundZVP(mul(viewPos, u_proj));\n");
} else {
WRITE(p, " Out.gl_Position = mul(viewPos, u_proj);\n");
}
// TODO: Declare variables for dots for shade mapping if needed. // TODO: Declare variables for dots for shade mapping if needed.

View file

@ -558,9 +558,13 @@ void GLES_GPU::CheckGPUFeatures() {
if (!gl_extensions.IsGLES) if (!gl_extensions.IsGLES)
features |= GPU_SUPPORTS_LOGIC_OP; features |= GPU_SUPPORTS_LOGIC_OP;
if (gl_extensions.GLES3 || !gl_extensions.IsGLES) { if (gl_extensions.GLES3 || !gl_extensions.IsGLES)
features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL; features |= GPU_SUPPORTS_TEXTURE_LOD_CONTROL;
}
// In the future, also disable this when we get a proper 16-bit depth buffer.
if (!PSP_CoreParameter().compat.flags().NoDepthRounding)
features |= GPU_ROUND_DEPTH_TO_16BIT;
#ifdef MOBILE_DEVICE #ifdef MOBILE_DEVICE
// Arguably, we should turn off GPU_IS_MOBILE on like modern Tegras, etc. // Arguably, we should turn off GPU_IS_MOBILE on like modern Tegras, etc.

View file

@ -354,7 +354,7 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
WRITE(p, "uniform highp vec2 u_fogcoef;\n"); WRITE(p, "uniform highp vec2 u_fogcoef;\n");
} }
if (!gstate.isModeThrough()) { if (!gstate.isModeThrough() && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, "uniform highp vec4 u_depthRange;\n"); WRITE(p, "uniform highp vec4 u_depthRange;\n");
} }
@ -380,7 +380,7 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
} }
// See comment above this function (GenerateVertexShader). // See comment above this function (GenerateVertexShader).
if (!gstate.isModeThrough()) { if (!gstate.isModeThrough() && gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
// Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection. // Apply the projection and viewport to get the Z buffer value, floor to integer, undo the viewport and projection.
WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n"); WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\n");
WRITE(p, " float z = v.z / v.w;\n"); WRITE(p, " float z = v.z / v.w;\n");
@ -418,7 +418,11 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
WRITE(p, " gl_Position = u_proj_through * vec4(position.xyz, 1.0);\n"); WRITE(p, " gl_Position = u_proj_through * vec4(position.xyz, 1.0);\n");
} else { } else {
// The viewport is used in this case, so need to compensate for that. // The viewport is used in this case, so need to compensate for that.
WRITE(p, " gl_Position = depthRoundZVP(u_proj * vec4(position.xyz, 1.0));\n"); if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, " gl_Position = depthRoundZVP(u_proj * vec4(position.xyz, 1.0));\n");
} else {
WRITE(p, " gl_Position = u_proj * vec4(position.xyz, 1.0);\n");
}
} }
} else { } else {
// Step 1: World Transform / Skinning // Step 1: World Transform / Skinning
@ -511,7 +515,11 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf
WRITE(p, " vec4 viewPos = u_view * vec4(worldpos, 1.0);\n"); WRITE(p, " vec4 viewPos = u_view * vec4(worldpos, 1.0);\n");
// Final view and projection transforms. // Final view and projection transforms.
WRITE(p, " gl_Position = depthRoundZVP(u_proj * viewPos);\n"); if (gstate_c.Supports(GPU_ROUND_DEPTH_TO_16BIT)) {
WRITE(p, " gl_Position = depthRoundZVP(u_proj * viewPos);\n");
} else {
WRITE(p, " gl_Position = u_proj * viewPos;\n");
}
// TODO: Declare variables for dots for shade mapping if needed. // TODO: Declare variables for dots for shade mapping if needed.

View file

@ -454,6 +454,7 @@ enum {
GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4), GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4),
GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5), GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5),
GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20), GPU_SUPPORTS_ANY_FRAMEBUFFER_FETCH = FLAG_BIT(20),
GPU_ROUND_DEPTH_TO_16BIT = FLAG_BIT(23), // Can be disabled either per game or if we use a real 16-bit depth buffer
GPU_SUPPORTS_TEXTURE_LOD_CONTROL = FLAG_BIT(24), GPU_SUPPORTS_TEXTURE_LOD_CONTROL = FLAG_BIT(24),
GPU_SUPPORTS_FBO = FLAG_BIT(25), GPU_SUPPORTS_FBO = FLAG_BIT(25),
GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT = FLAG_BIT(26), GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT = FLAG_BIT(26),

View file

@ -199,6 +199,7 @@ EXEC_AND_LIB_FILES := \
$(SRC)/Core/HW/SasAudio.cpp.arm \ $(SRC)/Core/HW/SasAudio.cpp.arm \
$(SRC)/Core/HW/StereoResampler.cpp.arm \ $(SRC)/Core/HW/StereoResampler.cpp.arm \
$(SRC)/Core/Core.cpp \ $(SRC)/Core/Core.cpp \
$(SRC)/Core/Compatibility.cpp \
$(SRC)/Core/Config.cpp \ $(SRC)/Core/Config.cpp \
$(SRC)/Core/CoreTiming.cpp \ $(SRC)/Core/CoreTiming.cpp \
$(SRC)/Core/CwCheat.cpp \ $(SRC)/Core/CwCheat.cpp \

5
assets/compat.ini Normal file
View file

@ -0,0 +1,5 @@
# Fight Night Round 3
[ULES00270]
NoDepthRounding = true
[ULUS10066]
NoDepthRounding = true