From b07b002040e77d3f7b27df75710c6507e3047e9e Mon Sep 17 00:00:00 2001 From: Henrik Rydgard Date: Sat, 26 Sep 2015 16:01:16 +0200 Subject: [PATCH] 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. --- CMakeLists.txt | 2 + Core/Compatibility.cpp | 43 ++++++++++++++ Core/Compatibility.h | 70 +++++++++++++++++++++++ Core/Core.vcxproj | 2 + Core/Core.vcxproj.filters | 6 ++ Core/CoreParameter.h | 4 ++ Core/HLE/sceKernelThread.cpp | 4 +- Core/MemMap.h | 1 - Core/System.cpp | 5 ++ GPU/Directx9/GPU_DX9.cpp | 11 ++++ GPU/Directx9/VertexShaderGeneratorDX9.cpp | 16 ++++-- GPU/GLES/GLES_GPU.cpp | 8 ++- GPU/GLES/VertexShaderGenerator.cpp | 16 ++++-- GPU/GPUState.h | 1 + android/jni/Android.mk | 1 + assets/compat.ini | 5 ++ 16 files changed, 182 insertions(+), 13 deletions(-) create mode 100644 Core/Compatibility.cpp create mode 100644 Core/Compatibility.h create mode 100644 assets/compat.ini diff --git a/CMakeLists.txt b/CMakeLists.txt index a62fdff293..07f107bc7a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1132,6 +1132,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/Config.h Core/Core.cpp Core/Core.h + Core/Compatibility.cpp + Core/Compatibility.h Core/CoreParameter.h Core/CoreTiming.cpp Core/CoreTiming.h diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp new file mode 100644 index 0000000000..f4fb5a1125 --- /dev/null +++ b/Core/Compatibility.cpp @@ -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); +} diff --git a/Core/Compatibility.h b/Core/Compatibility.h new file mode 100644 index 0000000000..dc22ee0cf2 --- /dev/null +++ b/Core/Compatibility.h @@ -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 +#include + +// 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_; +}; \ No newline at end of file diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 1c8a1de610..797f6505d1 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -179,6 +179,7 @@ + @@ -501,6 +502,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index f62a0aaa9a..08bec703a2 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -622,6 +622,9 @@ FileLoaders + + Core + @@ -1167,6 +1170,9 @@ HLE + + Core + diff --git a/Core/CoreParameter.h b/Core/CoreParameter.h index b4c3a78991..d99d0ea94a 100644 --- a/Core/CoreParameter.h +++ b/Core/CoreParameter.h @@ -19,6 +19,8 @@ #include +#include "Core/Compatibility.h" + enum CPUCore { CPU_INTERPRETER, CPU_JIT, @@ -69,4 +71,6 @@ struct CoreParameter { bool frozen; FileLoader *mountIsoLoader; + + Compatibility compat; }; diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index ff2fda3f1e..353729e99b 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -1237,7 +1237,7 @@ u32 sceKernelReferThreadStatus(u32 threadID, u32 statusPtr) 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; if (wantedSize != 0) @@ -1248,7 +1248,7 @@ u32 sceKernelReferThreadStatus(u32 threadID, u32 statusPtr) } else { - DEBUG_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr); + VERBOSE_LOG(SCEKERNEL, "sceKernelReferThreadStatus(%i, %08x)", threadID, statusPtr); t->nt.nativeSize = THREADINFO_SIZE; u32 sz = std::min(THREADINFO_SIZE, wantedSize); diff --git a/Core/MemMap.h b/Core/MemMap.h index bef76db691..46d4c713ce 100644 --- a/Core/MemMap.h +++ b/Core/MemMap.h @@ -276,7 +276,6 @@ inline void MemcpyUnchecked(const u32 to_address, const u32 from_address, const MemcpyUnchecked(GetPointer(to_address), from_address, len); } -// TODO: This considers 0x88900000 a valid address. Probably not good. inline bool IsValidAddress(const u32 address) { if ((address & 0x3E000000) == 0x08000000) { return true; diff --git a/Core/System.cpp b/Core/System.cpp index 11a30ca291..6d4adf3fd8 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -204,6 +204,11 @@ void CPU_Init() { 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(); mipsr4k.Reset(); diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index 5583dedb48..ab6d3c2a09 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -466,6 +466,17 @@ void DIRECTX9_GPU::UpdateCmdInfo() { cmdInfo_[GE_CMD_VERTEXTYPE].flags |= FLAG_FLUSHBEFOREONCHANGE; 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() { diff --git a/GPU/Directx9/VertexShaderGeneratorDX9.cpp b/GPU/Directx9/VertexShaderGeneratorDX9.cpp index 0021eaa119..a6bcb305ef 100644 --- a/GPU/Directx9/VertexShaderGeneratorDX9.cpp +++ b/GPU/Directx9/VertexShaderGeneratorDX9.cpp @@ -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); } @@ -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. - 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. // The Z range in D3D is different but we compensate for that using parameters. WRITE(p, "\nfloat4 depthRoundZVP(float4 v) {\n"); @@ -328,7 +328,11 @@ void GenerateVertexShaderDX9(int prim, char *buffer, bool useHWTransform) { if (gstate.isModeThrough()) { WRITE(p, " Out.gl_Position = mul(float4(In.position.xyz, 1.0), u_proj_through);\n"); } 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 { // 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"); // 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. diff --git a/GPU/GLES/GLES_GPU.cpp b/GPU/GLES/GLES_GPU.cpp index fec362a726..c17d2d119f 100644 --- a/GPU/GLES/GLES_GPU.cpp +++ b/GPU/GLES/GLES_GPU.cpp @@ -558,9 +558,13 @@ void GLES_GPU::CheckGPUFeatures() { if (!gl_extensions.IsGLES) 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; - } + + // 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 // Arguably, we should turn off GPU_IS_MOBILE on like modern Tegras, etc. diff --git a/GPU/GLES/VertexShaderGenerator.cpp b/GPU/GLES/VertexShaderGenerator.cpp index 9c25ddb1f9..b13842d5f6 100644 --- a/GPU/GLES/VertexShaderGenerator.cpp +++ b/GPU/GLES/VertexShaderGenerator.cpp @@ -354,7 +354,7 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf 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"); } @@ -380,7 +380,7 @@ void GenerateVertexShader(int prim, u32 vertType, char *buffer, bool useHWTransf } // 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. WRITE(p, "\nvec4 depthRoundZVP(vec4 v) {\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"); } else { // 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 { // 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"); // 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. diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 651c4994ce..4bfdb83a07 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -454,6 +454,7 @@ enum { GPU_SUPPORTS_BLEND_MINMAX = FLAG_BIT(4), GPU_SUPPORTS_LOGIC_OP = FLAG_BIT(5), 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_FBO = FLAG_BIT(25), GPU_SUPPORTS_ARB_FRAMEBUFFER_BLIT = FLAG_BIT(26), diff --git a/android/jni/Android.mk b/android/jni/Android.mk index aaaca4c78f..74b5d5a934 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -199,6 +199,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HW/SasAudio.cpp.arm \ $(SRC)/Core/HW/StereoResampler.cpp.arm \ $(SRC)/Core/Core.cpp \ + $(SRC)/Core/Compatibility.cpp \ $(SRC)/Core/Config.cpp \ $(SRC)/Core/CoreTiming.cpp \ $(SRC)/Core/CwCheat.cpp \ diff --git a/assets/compat.ini b/assets/compat.ini new file mode 100644 index 0000000000..cec842a403 --- /dev/null +++ b/assets/compat.ini @@ -0,0 +1,5 @@ +# Fight Night Round 3 +[ULES00270] +NoDepthRounding = true +[ULUS10066] +NoDepthRounding = true