diff --git a/Common/BitScan.h b/Common/BitScan.h index 729c320759..4a8a8c28ac 100644 --- a/Common/BitScan.h +++ b/Common/BitScan.h @@ -1,6 +1,7 @@ #pragma once #include "ppsspp_config.h" +#include #if PPSSPP_PLATFORM(WINDOWS) #include "Common/CommonWindows.h" diff --git a/Core/Compatibility.cpp b/Core/Compatibility.cpp index 71f67ebe4a..4763cf4c25 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -65,6 +65,7 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ForceMax60FPS", &flags_.ForceMax60FPS); CheckSetting(iniFile, gameID, "JitInvalidationHack", &flags_.JitInvalidationHack); CheckSetting(iniFile, gameID, "HideISOFiles", &flags_.HideISOFiles); + CheckSetting(iniFile, gameID, "MoreAccurateVMMUL", &flags_.MoreAccurateVMMUL); } void Compatibility::CheckSetting(IniFile &iniFile, const std::string &gameID, const char *option, bool *flag) { diff --git a/Core/Compatibility.h b/Core/Compatibility.h index c85267cf46..8632813e26 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -65,6 +65,7 @@ struct CompatFlags { bool ForceMax60FPS; bool JitInvalidationHack; bool HideISOFiles; + bool MoreAccurateVMMUL; }; class IniFile; diff --git a/Core/MIPS/ARM/ArmCompVFPU.cpp b/Core/MIPS/ARM/ArmCompVFPU.cpp index 4893820081..94cd0d811c 100644 --- a/Core/MIPS/ARM/ArmCompVFPU.cpp +++ b/Core/MIPS/ARM/ArmCompVFPU.cpp @@ -21,14 +21,16 @@ #include #include "math/math_util.h" +#include "Core/Compatibility.h" +#include "Core/Config.h" #include "Core/MemMap.h" +#include "Core/Reporting.h" +#include "Core/System.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Common/CPUDetect.h" -#include "Core/Config.h" -#include "Core/Reporting.h" #include "Core/MIPS/ARM/ArmJit.h" #include "Core/MIPS/ARM/ArmRegCache.h" @@ -1468,12 +1470,16 @@ namespace MIPSComp void ArmJit::Comp_Vmmul(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_MTX_VMMUL); - if (js.HasUnknownPrefix()) { + if (!js.HasNoPrefix()) { DISABLE; } NEON_IF_AVAILABLE(CompNEON_Vmmul); - // TODO: This probably ignores prefixes? + if (PSP_CoreParameter().compat.flags().MoreAccurateVMMUL) { + // Fall back to interpreter, which has the accurate implementation. + // Later we might do something more optimized here. + DISABLE; + } MatrixSize sz = GetMtxSize(op); int n = GetMatrixSide(sz); diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 243890f1ea..69d6a302c6 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -21,15 +21,16 @@ #include #include "math/math_util.h" +#include "Core/Compatibility.h" +#include "Core/Config.h" #include "Core/MemMap.h" +#include "Core/Reporting.h" +#include "Core/System.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSTables.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Common/CPUDetect.h" -#include "Core/Config.h" -#include "Core/Reporting.h" - #include "Common/Arm64Emitter.h" #include "Core/MIPS/ARM64/Arm64Jit.h" #include "Core/MIPS/ARM64/Arm64RegCache.h" @@ -1219,6 +1220,12 @@ namespace MIPSComp { DISABLE; } + if (PSP_CoreParameter().compat.flags().MoreAccurateVMMUL) { + // Fall back to interpreter, which has the accurate implementation. + // Later we might do something more optimized here. + DISABLE; + } + MatrixSize sz = GetMtxSize(op); int n = GetMatrixSide(sz); diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index fe69d4f7e1..13fa39b2c2 100644 --- a/Core/MIPS/IR/IRCompVFPU.cpp +++ b/Core/MIPS/IR/IRCompVFPU.cpp @@ -20,6 +20,7 @@ #include "math/math_util.h" #include "Common/CPUDetect.h" +#include "Core/Compatibility.h" #include "Core/Config.h" #include "Core/MemMap.h" #include "Core/MIPS/MIPS.h" @@ -29,6 +30,7 @@ #include "Core/MIPS/IR/IRFrontend.h" #include "Core/MIPS/IR/IRRegCache.h" #include "Core/Reporting.h" +#include "Core/System.h" // All functions should have CONDITIONAL_DISABLE, so we can narrow things down to a file quickly. @@ -1242,6 +1244,12 @@ namespace MIPSComp { DISABLE; } + if (PSP_CoreParameter().compat.flags().MoreAccurateVMMUL) { + // Fall back to interpreter, which has the accurate implementation. + // Later we might do something more optimized here. + DISABLE; + } + // Matrix multiply (weird prefixes) // D[0 .. N, 0 .. M] = S[0 .. N, 0 .. M]' * T[0 .. N, 0 .. M] // Note: Behaves as if it's implemented through a series of vdots. diff --git a/Core/MIPS/MIPSIntVFPU.cpp b/Core/MIPS/MIPSIntVFPU.cpp index bb6c84abe5..8e3ad0ba01 100644 --- a/Core/MIPS/MIPSIntVFPU.cpp +++ b/Core/MIPS/MIPSIntVFPU.cpp @@ -23,9 +23,11 @@ #include "math/math_util.h" +#include "Core/Compatibility.h" #include "Core/Core.h" -#include "Core/Reporting.h" #include "Core/MemMap.h" +#include "Core/Reporting.h" +#include "Core/System.h" #include "Core/MIPS/MIPS.h" #include "Core/MIPS/MIPSInt.h" @@ -70,6 +72,9 @@ #define M_SQRT1_2 0.707106781186547524401f #endif +static const bool USE_VFPU_DOT = false; +static const bool USE_VPFU_SQRT = false; + union FloatBits { float f[4]; u32 u[4]; @@ -464,22 +469,37 @@ namespace MIPSInt ReadMatrix(s, sz, vs); ReadMatrix(t, sz, vt); + // TODO: Always use the more accurate path in interpreter? + bool useAccurateDot = USE_VFPU_DOT || PSP_CoreParameter().compat.flags().MoreAccurateVMMUL; for (int a = 0; a < n; a++) { for (int b = 0; b < n; b++) { - float sum = 0.0f; + union { float f; uint32_t u; } sum = { 0.0f }; if (a == n - 1 && b == n - 1) { // S and T prefixes work on the final (or maybe first, in reverse?) dot. ApplySwizzleS(&s[b * 4], V_Quad); ApplySwizzleT(&t[a * 4], V_Quad); - for (int c = 0; c < 4; c++) { - sum += s[b * 4 + c] * t[a * 4 + c]; + } + + if (useAccurateDot) { + sum.f = vfpu_dot(&s[b * 4], &t[a * 4]); + if (my_isnan(sum.f)) { + sum.u = 0x7f800001; + } else if ((sum.u & 0x7F800000) == 0) { + sum.u &= 0xFF800000; } } else { - for (int c = 0; c < n; c++) { - sum += s[b * 4 + c] * t[a * 4 + c]; + if (a == n - 1 && b == n - 1) { + for (int c = 0; c < 4; c++) { + sum.f += s[b * 4 + c] * t[a * 4 + c]; + } + } else { + for (int c = 0; c < n; c++) { + sum.f += s[b * 4 + c] * t[a * 4 + c]; + } } } - d[a * 4 + b] = sum; + + d[a * 4 + b] = sum.f; } } @@ -602,13 +622,13 @@ namespace MIPSInt case 4: if (s[i] <= 0) d[i] = 0; else {if(s[i] > 1.0f) d[i] = 1.0f; else d[i] = s[i];} break; // vsat0 case 5: if (s[i] < -1.0f) d[i] = -1.0f; else {if(s[i] > 1.0f) d[i] = 1.0f; else d[i] = s[i];} break; // vsat1 case 16: d[i] = 1.0f / s[i]; break; //vrcp - case 17: d[i] = 1.0f / sqrtf(s[i]); break; //vrsq + case 17: d[i] = USE_VPFU_SQRT ? vfpu_rsqrt(s[i]) : 1.0f / sqrtf(s[i]); break; //vrsq case 18: { d[i] = vfpu_sin(s[i]); } break; //vsin case 19: { d[i] = vfpu_cos(s[i]); } break; //vcos case 20: d[i] = powf(2.0f, s[i]); break; //vexp2 case 21: d[i] = logf(s[i])/log(2.0f); break; //vlog2 - case 22: d[i] = fabsf(sqrtf(s[i])); break; //vsqrt + case 22: d[i] = USE_VPFU_SQRT ? vfpu_sqrt(s[i]) : fabsf(sqrtf(s[i])); break; //vsqrt case 23: d[i] = asinf(s[i]) / M_PI_2; break; //vasin case 24: d[i] = -1.0f / s[i]; break; // vnrcp case 26: { d[i] = -vfpu_sin(s[i]); } break; // vnsin @@ -1123,7 +1143,7 @@ namespace MIPSInt void Int_VDot(MIPSOpcode op) { float s[4]{}, t[4]{}; - float d; + union { float f; uint32_t u; } d; int vd = _VD; int vs = _VS; int vt = _VT; @@ -1132,12 +1152,23 @@ namespace MIPSInt ApplySwizzleS(s, V_Quad); ReadVector(t, sz, vt); ApplySwizzleT(t, V_Quad); - d = 0.0f; - for (int i = 0; i < 4; i++) { - d += s[i] * t[i]; + + if (USE_VFPU_DOT) { + d.f = vfpu_dot(s, t); + if (my_isnan(d.f)) { + d.u = 0x7f800001; + } else if ((d.u & 0x7F800000) == 0) { + d.u &= 0xFF800000; + } + } else { + d.f = 0.0f; + for (int i = 0; i < 4; i++) { + d.f += s[i] * t[i]; + } } - ApplyPrefixD(&d, V_Single); - WriteVector(&d, V_Single, vd); + + ApplyPrefixD(&d.f, V_Single); + WriteVector(&d.f, V_Single, vd); PC += 4; EatPrefixes(); } @@ -1173,8 +1204,12 @@ namespace MIPSInt ApplyPrefixST(s, VFPURewritePrefix(VFPU_CTRL_SPREFIX, sprefixRemove, sprefixAdd), V_Quad); float sum = 0.0f; - for (int i = 0; i < 4; i++) { - sum += s[i] * t[i]; + if (USE_VFPU_DOT) { + sum = vfpu_dot(s, t); + } else { + for (int i = 0; i < 4; i++) { + sum += s[i] * t[i]; + } } d = my_isnan(sum) ? fabsf(sum) : sum; ApplyPrefixD(&d, V_Single); @@ -1389,8 +1424,14 @@ namespace MIPSInt u32 tprefixAdd = VFPU_SWIZZLE(1, 0, 0, 0); ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - d[0] = s[0] * t[0] - s[1] * t[1]; - d[0] += s[2] * t[2] + s[3] * t[3]; + if (USE_VFPU_DOT) { + s[1] = -s[1]; + d[0] = vfpu_dot(s, t); + } else { + d[0] = s[0] * t[0] - s[1] * t[1]; + d[0] += s[2] * t[2] + s[3] * t[3]; + } + ApplyPrefixD(d, sz); WriteVector(d, V_Single, vd); PC += 4; @@ -1411,9 +1452,13 @@ namespace MIPSInt u32 tprefixAdd = VFPU_MAKE_CONSTANTS(VFPUConst::ONE, VFPUConst::ONE, VFPUConst::ONE, VFPUConst::ONE); ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - d = 0.0f; - for (int i = 0; i < 4; i++) { - d += s[i] * t[i]; + if (USE_VFPU_DOT) { + d = vfpu_dot(s, t); + } else { + d = 0.0f; + for (int i = 0; i < 4; i++) { + d += s[i] * t[i]; + } } ApplyPrefixD(&d, V_Single); WriteVector(&d, V_Single, vd); @@ -1445,9 +1490,13 @@ namespace MIPSInt tprefixAdd = 0; ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - d = 0.0f; - for (int i = 0; i < 4; i++) { - d += s[i] * t[i]; + if (USE_VFPU_DOT) { + d = vfpu_dot(s, t); + } else { + d = 0.0f; + for (int i = 0; i < 4; i++) { + d += s[i] * t[i]; + } } ApplyPrefixD(&d, V_Single); WriteVector(&d, V_Single, vd); @@ -1565,7 +1614,8 @@ namespace MIPSInt } void Int_Vtfm(MIPSOpcode op) { - float s[16]{}, t[4]{}, d[4]; + float s[16]{}, t[4]{}; + FloatBits d; int vd = _VD; int vs = _VS; int vt = _VT; @@ -1579,13 +1629,36 @@ namespace MIPSInt ReadMatrix(s, msz, vs); ReadVector(t, sz, vt); - for (int i = 0; i < ins; i++) { - d[i] = s[i * 4] * t[0]; - for (int k = 1; k < tn; k++) { - d[i] += s[i * 4 + k] * t[k]; + if (USE_VFPU_DOT) { + float t2[4]; + for (int i = 0; i < 4; i++) { + if (i < tn) { + t2[i] = t[i]; + } else if (i == ins) { + t2[i] = 1.0f; + } else { + t2[i] = 0.0f; + } } - if (ins >= n) { - d[i] += s[i * 4 + ins]; + + for (int i = 0; i < ins; i++) { + d.f[i] = vfpu_dot(&s[i * 4], t2); + + if (my_isnan(d.f[i])) { + d.u[i] = 0x7f800001; + } else if ((d.u[i] & 0x7F800000) == 0) { + d.u[i] &= 0xFF800000; + } + } + } else { + for (int i = 0; i < ins; i++) { + d.f[i] = s[i * 4] * t[0]; + for (int k = 1; k < tn; k++) { + d.f[i] += s[i * 4 + k] * t[k]; + } + if (ins >= n) { + d.f[i] += s[i * 4 + ins]; + } } } @@ -1610,17 +1683,27 @@ namespace MIPSInt ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); // Really this is the operation all rows probably use (with constant wiring.) - d[ins] = s[ins * 4] * t[0]; - for (int k = 1; k < 4; k++) { - d[ins] += s[ins * 4 + k] * t[k]; + if (USE_VFPU_DOT) { + d.f[ins] = vfpu_dot(&s[ins * 4], t); + + if (my_isnan(d.f[ins])) { + d.u[ins] = 0x7f800001; + } else if ((d.u[ins] & 0x7F800000) == 0) { + d.u[ins] &= 0xFF800000; + } + } else { + d.f[ins] = s[ins * 4] * t[0]; + for (int k = 1; k < 4; k++) { + d.f[ins] += s[ins * 4 + k] * t[k]; + } } // D prefix applies to the last element only. u32 lastmask = (currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] & (1 << 8)) << ins; u32 lastsat = (currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] & 3) << (ins + ins); currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] = lastmask | lastsat; - ApplyPrefixD(d, sz); - WriteVector(d, sz, vd); + ApplyPrefixD(d.f, sz); + WriteVector(d.f, sz, vd); PC += 4; EatPrefixes(); } @@ -1967,7 +2050,8 @@ namespace MIPSInt } void Int_VecDo3(MIPSOpcode op) { - float s[4], t[4], d[4]; + float s[4], t[4]; + FloatBits d; int vd = _VD; int vs = _VS; int vt = _VT; @@ -2011,10 +2095,18 @@ namespace MIPSInt for (int i = 0; i < n; i++) { switch (optype) { - case 0: d[i] = s[i] + t[i]; break; //vadd - case 1: d[i] = s[i] - t[i]; break; //vsub - case 7: d[i] = s[i] / t[i]; break; //vdiv - case 8: d[i] = s[i] * t[i]; break; //vmul + case 0: d.f[i] = s[i] + t[i]; break; //vadd + case 1: d.f[i] = s[i] - t[i]; break; //vsub + case 7: d.f[i] = s[i] / t[i]; break; //vdiv + case 8: d.f[i] = s[i] * t[i]; break; //vmul + } + + if (USE_VFPU_DOT) { + if (my_isnan(d.f[i])) { + d.u[i] = (d.u[i] & 0xff800001) | 1; + } else if ((d.u[i] & 0x7F800000) == 0) { + d.u[i] &= 0xFF800000; + } } } @@ -2023,12 +2115,12 @@ namespace MIPSInt u32 lastmask = (currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] & (1 << 8)) << (n - 1); u32 lastsat = (currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] & 3) << (n + n - 2); currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] = lastmask | lastsat; - ApplyPrefixD(d, sz); + ApplyPrefixD(d.f, sz); } else { - RetainInvalidSwizzleST(d, sz); - ApplyPrefixD(d, sz); + RetainInvalidSwizzleST(d.f, sz); + ApplyPrefixD(d.f, sz); } - WriteVector(d, sz, vd); + WriteVector(d.f, sz, vd); PC += 4; EatPrefixes(); } @@ -2048,27 +2140,61 @@ namespace MIPSInt switch (sz) { case V_Triple: // vcrsp.t - d[0] = s[1]*t[2] - s[2]*t[1]; - d[1] = s[2]*t[0] - s[0]*t[2]; + { + if (USE_VFPU_DOT) { + float t0[4] = { 0.0f, t[2], -t[1], 0.0f }; + float t1[4] = { -t[2], 0.0f, t[0], 0.0f }; + d[0] = vfpu_dot(s, t0); + d[1] = vfpu_dot(s, t1); + } else { + d[0] = s[1] * t[2] - s[2] * t[1]; + d[1] = s[2] * t[0] - s[0] * t[2]; + } // T prefix forces swizzle and negate, can be used to have weird constants. tprefixAdd = VFPU_SWIZZLE(1, 0, 3, 2) | VFPU_NEGATE(0, 1, 0, 0); ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); ApplySwizzleS(s, V_Quad); - d[2] = s[0] * t[0] + s[1] * t[1] + s[2] * t[2] + s[3] * t[3]; + if (USE_VFPU_DOT) { + // TODO: But flush any infs to 0? This seems sketchy. + for (int i = 0; i < 4; ++i) { + if (my_isinf(s[i])) + s[i] = 0.0f; + if (my_isinf(t[i])) + t[i] = 0.0f; + } + d[2] = vfpu_dot(s, t); + } else { + d[2] = s[0] * t[0] + s[1] * t[1] + s[2] * t[2] + s[3] * t[3]; + } break; + } case V_Quad: // vqmul.q - d[0] = s[0]*t[3] + s[1]*t[2] - s[2]*t[1] + s[3]*t[0]; - d[1] = -s[0]*t[2] + s[1]*t[3] + s[2]*t[0] + s[3]*t[1]; - d[2] = s[0]*t[1] - s[1]*t[0] + s[2]*t[3] + s[3]*t[2]; + { + if (USE_VFPU_DOT) { + float t0[4] = { t[3], t[2], -t[1], t[0] }; + float t1[4] = { -t[2], t[3], t[0], t[1] }; + float t2[4] = { t[1], -t[0], t[3], t[2] }; + d[0] = vfpu_dot(s, t0); + d[1] = vfpu_dot(s, t1); + d[2] = vfpu_dot(s, t2); + } else { + d[0] = s[0] * t[3] + s[1] * t[2] - s[2] * t[1] + s[3] * t[0]; + d[1] = -s[0] * t[2] + s[1] * t[3] + s[2] * t[0] + s[3] * t[1]; + d[2] = s[0] * t[1] - s[1] * t[0] + s[2] * t[3] + s[3] * t[2]; + } // T prefix forces swizzle and negate, can be used to have weird constants. tprefixAdd = VFPU_SWIZZLE(0, 1, 2, 3) | VFPU_NEGATE(1, 1, 1, 0); ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); ApplySwizzleS(s, sz); - d[3] = s[0] * t[0] + s[1] * t[1] + s[2] * t[2] + s[3] * t[3]; + if (USE_VFPU_DOT) + d[3] = vfpu_dot(s, t); + else + d[3] = s[0] * t[0] + s[1] * t[1] + s[2] * t[2] + s[3] * t[3]; break; + } case V_Pair: // t swizzles invalid so the multiply is always zero. diff --git a/Core/MIPS/MIPSVFPUUtils.cpp b/Core/MIPS/MIPSVFPUUtils.cpp index 6c07bfef5a..7e46209c76 100644 --- a/Core/MIPS/MIPSVFPUUtils.cpp +++ b/Core/MIPS/MIPSVFPUUtils.cpp @@ -15,9 +15,11 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include #include +#include "Common/BitScan.h" #include "Common/CommonFuncs.h" #include "Core/Reporting.h" #include "Core/MIPS/MIPS.h" @@ -607,3 +609,244 @@ float Float16ToFloat32(unsigned short l) } return f; } + +static uint32_t get_uexp(uint32_t x) { + return (x >> 23) & 0xFF; +} + +int32_t get_exp(uint32_t x) { + return get_uexp(x) - 127; +} + +static int32_t get_mant(uint32_t x) { + // Note: this returns the hidden 1. + return (x & 0x007FFFFF) | 0x00800000; +} + +static int32_t get_sign(uint32_t x) { + return x & 0x80000000; +} + +float vfpu_dot(float a[4], float b[4]) { + static const int EXTRA_BITS = 2; + union float2int { + uint32_t i; + float f; + }; + float2int result; + float2int src[2]; + + int32_t exps[4]; + int32_t mants[4]; + int32_t signs[4]; + int32_t max_exp = 0; + int32_t last_inf = -1; + + for (int i = 0; i < 4; i++) { + src[0].f = a[i]; + src[1].f = b[i]; + + int32_t aexp = get_uexp(src[0].i); + int32_t bexp = get_uexp(src[1].i); + int32_t amant = get_mant(src[0].i) << EXTRA_BITS; + int32_t bmant = get_mant(src[1].i) << EXTRA_BITS; + + exps[i] = aexp + bexp - 127; + if (aexp == 255) { + // INF * 0 = NAN + if ((src[0].i & 0x007FFFFF) != 0 || bexp == 0) { + result.i = 0x7F800001; + return result.f; + } + mants[i] = get_mant(0) << EXTRA_BITS; + exps[i] = 255; + } else if (bexp == 255) { + if ((src[1].i & 0x007FFFFF) != 0 || aexp == 0) { + result.i = 0x7F800001; + return result.f; + } + mants[i] = get_mant(0) << EXTRA_BITS; + exps[i] = 255; + } else { + // TODO: Adjust precision? + uint64_t adjust = (uint64_t)amant * (uint64_t)bmant; + mants[i] = (adjust >> (23 + EXTRA_BITS)) & 0x7FFFFFFF; + } + signs[i] = get_sign(src[0].i) ^ get_sign(src[1].i); + + if (exps[i] > max_exp) { + max_exp = exps[i]; + } + if (exps[i] >= 255) { + // Infinity minus infinity is not a real number. + if (last_inf != -1 && signs[i] != last_inf) { + result.i = 0x7F800001; + return result.f; + } + last_inf = signs[i]; + } + } + + int32_t mant_sum = 0; + for (int i = 0; i < 4; i++) { + int exp = max_exp - exps[i]; + if (exp >= 32) { + mants[i] = 0; + } else { + mants[i] >>= exp; + } + if (signs[i]) { + mants[i] = -mants[i]; + } + mant_sum += mants[i]; + } + + uint32_t sign_sum = 0; + if (mant_sum < 0) { + sign_sum = 0x80000000; + mant_sum = -mant_sum; + } + + // Truncate off the extra bits now. We want to zero them for rounding purposes. + mant_sum >>= EXTRA_BITS; + + if (mant_sum == 0 || max_exp <= 0) { + return 0.0f; + } + + int8_t shift = (int8_t)clz32_nonzero(mant_sum) - 8; + if (shift < 0) { + // Round to even if we'd shift away a 0.5. + const uint32_t round_bit = 1 << (-shift - 1); + if ((mant_sum & round_bit) && (mant_sum & (round_bit << 1))) { + mant_sum += round_bit; + shift = (int8_t)clz32_nonzero(mant_sum) - 8; + } else if ((mant_sum & round_bit) && (mant_sum & (round_bit - 1))) { + mant_sum += round_bit; + shift = (int8_t)clz32_nonzero(mant_sum) - 8; + } + mant_sum >>= -shift; + max_exp += -shift; + } else { + mant_sum <<= shift; + max_exp -= shift; + } + _dbg_assert_msg_(JIT, (mant_sum & 0x00800000) != 0, "Mantissa wrong: %08x", mant_sum); + + if (max_exp >= 255) { + max_exp = 255; + mant_sum = 0; + } else if (max_exp <= 0) { + return 0.0f; + } + + result.i = sign_sum | (max_exp << 23) | (mant_sum & 0x007FFFFF); + return result.f; +} + +// TODO: This is still not completely accurate compared to the PSP's vsqrt. +float vfpu_sqrt(float a) { + union float2int { + uint32_t u; + float f; + }; + float2int val; + val.f = a; + + if ((val.u & 0xff800000) == 0x7f800000) { + if ((val.u & 0x007fffff) != 0) { + val.u = 0x7f800001; + } + return val.f; + } + if ((val.u & 0x7f800000) == 0) { + // Kill any sign. + val.u = 0; + return val.f; + } + if (val.u & 0x80000000) { + val.u = 0x7f800001; + return val.f; + } + + int k = get_exp(val.u); + uint32_t sp = get_mant(val.u); + int less_bits = k & 1; + k >>= 1; + + uint32_t z = 0x00C00000 >> less_bits; + int64_t halfsp = sp >> 1; + halfsp <<= 23 - less_bits; + for (int i = 0; i < 6; ++i) { + z = (z >> 1) + (uint32_t)(halfsp / z); + } + + val.u = ((k + 127) << 23) | ((z << less_bits) & 0x007FFFFF); + // The lower two bits never end up set on the PSP, it seems like. + val.u &= 0xFFFFFFFC; + + return val.f; +} + +static inline uint32_t mant_mul(uint32_t a, uint32_t b) { + uint64_t m = (uint64_t)a * (uint64_t)b; + if (m & 0x007FFFFF) { + m += 0x01437000; + } + return m >> 23; +} + +float vfpu_rsqrt(float a) { + union float2int { + uint32_t u; + float f; + }; + float2int val; + val.f = a; + + if (val.u == 0x7f800000) { + return 0.0f; + } + if ((val.u & 0x7fffffff) > 0x7f800000) { + val.u = (val.u & 0x80000000) | 0x7f800001; + return val.f; + } + if ((val.u & 0x7f800000) == 0) { + val.u = (val.u & 0x80000000) | 0x7f800000; + return val.f; + } + if (val.u & 0x80000000) { + val.u = 0xff800001; + return val.f; + } + + int k = get_exp(val.u); + uint32_t sp = get_mant(val.u); + int less_bits = k & 1; + k = -(k >> 1); + + uint32_t z = 0x00800000 >> less_bits; + uint32_t halfsp = sp >> (1 + less_bits); + for (int i = 0; i < 6; ++i) { + uint32_t oldz = z; + uint32_t zsq = mant_mul(z, z); + uint32_t correction = 0x00C00000 - mant_mul(halfsp, zsq); + z = mant_mul(z, correction); + } + + int8_t shift = (int8_t)clz32_nonzero(z) - 8 + less_bits; + if (shift < 1) { + z >>= -shift; + k += -shift; + } else if (shift > 0) { + z <<= shift; + k -= shift; + } + + z >>= less_bits; + + val.u = ((k + 127) << 23) | (z & 0x007FFFFF); + val.u &= 0xFFFFFFFC; + + return val.f; +} diff --git a/Core/MIPS/MIPSVFPUUtils.h b/Core/MIPS/MIPSVFPUUtils.h index 1489e33fc0..cf7c1e7426 100644 --- a/Core/MIPS/MIPSVFPUUtils.h +++ b/Core/MIPS/MIPSVFPUUtils.h @@ -97,6 +97,10 @@ inline float vfpu_clamp(float v, float min, float max) { return v >= max ? max : (v <= min ? min : v); } +float vfpu_dot(float a[4], float b[4]); +float vfpu_sqrt(float a); +float vfpu_rsqrt(float a); + #define VFPU_FLOAT16_EXP_MAX 0x1f #define VFPU_SH_FLOAT16_SIGN 15 #define VFPU_MASK_FLOAT16_SIGN 0x1 diff --git a/Core/MIPS/x86/CompVFPU.cpp b/Core/MIPS/x86/CompVFPU.cpp index 0b87ef5ea0..ff7bd79f2b 100644 --- a/Core/MIPS/x86/CompVFPU.cpp +++ b/Core/MIPS/x86/CompVFPU.cpp @@ -29,9 +29,11 @@ #include "math/math_util.h" #include "Common/CPUDetect.h" -#include "Core/MemMap.h" +#include "Core/Compatibility.h" #include "Core/Config.h" +#include "Core/MemMap.h" #include "Core/Reporting.h" +#include "Core/System.h" #include "Core/MIPS/MIPSAnalyst.h" #include "Core/MIPS/MIPSCodeUtils.h" #include "Core/MIPS/MIPSVFPUUtils.h" @@ -2803,10 +2805,15 @@ void Jit::Comp_VScl(MIPSOpcode op) { void Jit::Comp_Vmmul(MIPSOpcode op) { CONDITIONAL_DISABLE(VFPU_MTX_VMMUL); - - // TODO: This probably ignores prefixes? - if (js.HasUnknownPrefix()) + if (!js.HasNoPrefix()) { DISABLE; + } + + if (PSP_CoreParameter().compat.flags().MoreAccurateVMMUL) { + // Fall back to interpreter, which has the accurate implementation. + // Later we might do something more optimized here. + DISABLE; + } MatrixSize sz = GetMtxSize(op); VectorSize vsz = GetVectorSize(sz); diff --git a/assets/compat.ini b/assets/compat.ini index 15aee5a550..f1917c5882 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -620,3 +620,14 @@ NPJH50471 = true ULJM06033 = true NPJH50559 = true NPEH00030 = true + +[MoreAccurateVMMUL] +# Fixes leg shaking in Tekken 6. The potential for slowdown in other games is large enough +# that we will not generally apply this accurate mode where not needed. +ULUS10466 = true +ULES01376 = true +ULJS00224 = true +NPUH10047 = true +ULAS42214 = true +ULJS19054 = true +NPJH50184 = true