diff --git a/.gitignore b/.gitignore index c1dfc7247d..5c6ef0569f 100644 --- a/.gitignore +++ b/.gitignore @@ -110,3 +110,6 @@ debian/ppsspp/ # YouCompleteMe file .ycm_extra_conf.pyc + +# RenderDoc +*.rdc diff --git a/CMakeLists.txt b/CMakeLists.txt index 7ad547ef15..7b700f3b94 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -831,7 +831,7 @@ elseif(TARGET SDL2::SDL2) set(nativeExtraLibs ${nativeExtraLibs} pthread) endif() set(TargetBin PPSSPPSDL) -else() +elseif(NOT LIBRETRO) message(FATAL_ERROR "Could not find SDL2. Failing.") endif() @@ -1647,6 +1647,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/HLE/sceUsbCam.h Core/HLE/sceUsbGps.cpp Core/HLE/sceUsbGps.h + Core/HLE/sceUsbMic.cpp + Core/HLE/sceUsbMic.h Core/HLE/sceUtility.cpp Core/HLE/sceUtility.h Core/HLE/sceVaudio.cpp 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 c069383dc5..7dd884d49e 100644 --- a/Core/Compatibility.cpp +++ b/Core/Compatibility.cpp @@ -76,7 +76,8 @@ void Compatibility::CheckSettings(IniFile &iniFile, const std::string &gameID) { CheckSetting(iniFile, gameID, "ForceUMDDelay", &flags_.ForceUMDDelay); CheckSetting(iniFile, gameID, "ForceMax60FPS", &flags_.ForceMax60FPS); CheckSetting(iniFile, gameID, "JitInvalidationHack", &flags_.JitInvalidationHack); - CheckSetting(iniFile, gameID, "MMULDisableHack", &flags_.MMULDisableHack); + 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 4c9d8a6260..aea49a7091 100644 --- a/Core/Compatibility.h +++ b/Core/Compatibility.h @@ -76,7 +76,8 @@ struct CompatFlags { bool ForceUMDDelay; bool ForceMax60FPS; bool JitInvalidationHack; - bool MMULDisableHack; + bool HideISOFiles; + bool MoreAccurateVMMUL; }; class IniFile; diff --git a/Core/Config.cpp b/Core/Config.cpp index e4717feb9f..d11b573bf4 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -1209,7 +1209,7 @@ void Config::Load(const char *iniFileName, const char *controllerIniFilename) { //sadly it won't benefit from all the "version conversion" going on up-above //but these configs shouldn't contain older versions anyhow if (bGameSpecific) { - loadGameConfig(gameId_); + loadGameConfig(gameId_, gameIdTitle_); } CleanRecent(); @@ -1531,7 +1531,7 @@ bool Config::saveGameConfig(const std::string &pGameId, const std::string &title return true; } -bool Config::loadGameConfig(const std::string &pGameId) { +bool Config::loadGameConfig(const std::string &pGameId, const std::string &title) { std::string iniFileNameFull = getGameConfigFile(pGameId); if (!hasGameConfig(pGameId)) { @@ -1539,7 +1539,7 @@ bool Config::loadGameConfig(const std::string &pGameId) { return false; } - changeGameSpecific(pGameId); + changeGameSpecific(pGameId, title); IniFile iniFile; iniFile.Load(iniFileNameFull); diff --git a/Core/Config.h b/Core/Config.h index 8f456c4552..c7edea1cbb 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -430,7 +430,7 @@ public: void changeGameSpecific(const std::string &gameId = "", const std::string &title = ""); bool createGameConfig(const std::string &game_id); bool deleteGameConfig(const std::string& pGameId); - bool loadGameConfig(const std::string &game_id); + bool loadGameConfig(const std::string &game_id, const std::string &title); bool saveGameConfig(const std::string &pGameId, const std::string &title); void unloadGameConfig(); std::string getGameConfigFile(const std::string &gameId); diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 937ab02ac4..22d258ecad 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -374,6 +374,7 @@ + @@ -901,6 +902,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index b459851f73..7f096c1b50 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -737,6 +737,9 @@ Debugger\WebSocket + + HLE\Libraries + @@ -1367,6 +1370,9 @@ Debugger\WebSocket + + HLE\Libraries + diff --git a/Core/Debugger/Breakpoints.cpp b/Core/Debugger/Breakpoints.cpp index dd6184d2ce..96f52ae0ba 100644 --- a/Core/Debugger/Breakpoints.cpp +++ b/Core/Debugger/Breakpoints.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include +#include #include #include "Common/Log.h" @@ -28,6 +29,8 @@ #include "Core/MIPS/JitCommon/JitCommon.h" #include "Core/CoreTiming.h" +std::atomic anyMemChecks_(false); + static std::mutex breakPointsMutex_; std::vector CBreakPoints::breakPoints_; u32 CBreakPoints::breakSkipFirstAt_ = 0; @@ -388,6 +391,7 @@ void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, Break check.result = result; memChecks_.push_back(check); + anyMemChecks_ = true; guard.unlock(); Update(); } @@ -395,6 +399,7 @@ void CBreakPoints::AddMemCheck(u32 start, u32 end, MemCheckCondition cond, Break { memChecks_[mc].cond = (MemCheckCondition)(memChecks_[mc].cond | cond); memChecks_[mc].result = (BreakAction)(memChecks_[mc].result | result); + anyMemChecks_ = true; guard.unlock(); Update(); } @@ -410,6 +415,7 @@ void CBreakPoints::RemoveMemCheck(u32 start, u32 end) if (mc != INVALID_MEMCHECK) { memChecks_.erase(memChecks_.begin() + mc); + anyMemChecks_ = !memChecks_.empty(); guard.unlock(); Update(); } @@ -499,6 +505,8 @@ MemCheck *CBreakPoints::GetMemCheckLocked(u32 address, int size) { BreakAction CBreakPoints::ExecMemCheck(u32 address, bool write, int size, u32 pc) { + if (!anyMemChecks_) + return BREAK_ACTION_IGNORE; std::unique_lock guard(memCheckMutex_); auto check = GetMemCheckLocked(address, size); if (check) { diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 90589b479e..4e97463300 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -30,6 +30,7 @@ #include "Core/HLE/sceKernel.h" #include "Core/HW/MemoryStick.h" #include "Core/CoreTiming.h" +#include "Core/System.h" #include "Core/Host.h" #include "Core/Replay.h" #include "Core/Reporting.h" @@ -848,6 +849,7 @@ std::vector DirectoryFileSystem::GetDirListing(std::string path) { return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs()); } + bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles; while (true) { PSPFileInfo entry; if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) @@ -864,10 +866,16 @@ std::vector DirectoryFileSystem::GetDirListing(std::string path) { else entry.size = findData.nFileSizeLow | ((u64)findData.nFileSizeHigh<<32); entry.name = SimulateVFATBug(ConvertWStringToUTF8(findData.cFileName)); + + bool hideFile = false; + if (hideISOFiles && (endsWithNoCase(entry.name, ".cso") || endsWithNoCase(entry.name, ".iso"))) { + // Workaround for DJ Max Portable, see compat.ini. + hideFile = true; + } tmFromFiletime(entry.atime, findData.ftLastAccessTime); tmFromFiletime(entry.ctime, findData.ftCreationTime); tmFromFiletime(entry.mtime, findData.ftLastWriteTime); - if (!listingRoot || (wcscmp(findData.cFileName, L"..") && wcscmp(findData.cFileName, L"."))) + if (!hideFile && (!listingRoot || (wcscmp(findData.cFileName, L"..") && wcscmp(findData.cFileName, L".")))) myVector.push_back(entry); int retval = FindNextFile(hFind, &findData); @@ -893,6 +901,7 @@ std::vector DirectoryFileSystem::GetDirListing(std::string path) { return ReplayApplyDiskListing(myVector, CoreTiming::GetGlobalTimeUs()); } + bool hideISOFiles = PSP_CoreParameter().compat.flags().HideISOFiles; while ((dirp = readdir(dp)) != NULL) { PSPFileInfo entry; struct stat s; @@ -905,10 +914,17 @@ std::vector DirectoryFileSystem::GetDirListing(std::string path) { entry.access = s.st_mode & 0x1FF; entry.name = SimulateVFATBug(dirp->d_name); entry.size = s.st_size; + + bool hideFile = false; + if (hideISOFiles && (endsWithNoCase(entry.name, ".cso") || endsWithNoCase(entry.name, ".iso"))) { + // Workaround for DJ Max Portable, see compat.ini. + hideFile = true; + } + localtime_r((time_t*)&s.st_atime,&entry.atime); localtime_r((time_t*)&s.st_ctime,&entry.ctime); localtime_r((time_t*)&s.st_mtime,&entry.mtime); - if (!listingRoot || (strcmp(dirp->d_name, "..") && strcmp(dirp->d_name, "."))) + if (!hideFile && (!listingRoot || (strcmp(dirp->d_name, "..") && strcmp(dirp->d_name, ".")))) myVector.push_back(entry); } closedir(dp); diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index ed1331f3ad..0f4c440c4b 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -69,6 +69,7 @@ #include "sceUsbAcc.h" #include "sceUsbCam.h" #include "sceUsbGps.h" +#include "sceUsbMic.h" #include "sceUtility.h" #include "sceVaudio.h" #include "sceMt19937.h" @@ -298,6 +299,7 @@ void RegisterAllModules() { Register_sceNetIfhandle(); Register_KUBridge(); Register_sceUsbAcc(); + Register_sceUsbMic(); // add new modules here. } diff --git a/Core/HLE/sceUsbCam.cpp b/Core/HLE/sceUsbCam.cpp index 2f624cb80e..e1b5986e8d 100644 --- a/Core/HLE/sceUsbCam.cpp +++ b/Core/HLE/sceUsbCam.cpp @@ -50,6 +50,17 @@ static int sceUsbCamStopMic() { return 0; } +static int sceUsbCamReadMicBlocking(u32 bufAddr, u32 size) { + INFO_LOG(HLE, "UNIMPL sceUsbCamReadMicBlocking: size: %d", size); + for (unsigned int i = 0; i < size; i++) { + if (Memory::IsValidAddress(bufAddr + i)) { + Memory::Write_U8(i & 0xFF, bufAddr + i); + } + } + hleEatMicro(1000000 / micParam.frequency * (size / 2)); + return size; +} + static int sceUsbCamSetupVideo(u32 paramAddr, u32 workareaAddr, int wasize) { INFO_LOG(HLE, "UNIMPL sceUsbCamSetupVideo"); if (Memory::IsValidRange(paramAddr, sizeof(videoParam))) { @@ -115,7 +126,7 @@ const HLEFunction sceUsbCam[] = { 0X2E930264, nullptr, "sceUsbCamSetupMicEx", '?', "" }, { 0X82A64030, &WrapI_V, "sceUsbCamStartMic", 'i', "" }, { 0X5145868A, &WrapI_V, "sceUsbCamStopMic", 'i', "" }, - { 0X36636925, nullptr, "sceUsbCamReadMicBlocking", '?', "" }, + { 0X36636925, &WrapI_UU, "sceUsbCamReadMicBlocking", 'i', "xx" }, { 0X3DC0088E, nullptr, "sceUsbCamReadMic", '?', "" }, { 0XB048A67D, nullptr, "sceUsbCamWaitReadMicEnd", '?', "" }, { 0XF8847F60, nullptr, "sceUsbCamPollReadMicEnd", '?', "" }, diff --git a/Core/HLE/sceUsbMic.cpp b/Core/HLE/sceUsbMic.cpp new file mode 100644 index 0000000000..306f527da1 --- /dev/null +++ b/Core/HLE/sceUsbMic.cpp @@ -0,0 +1,52 @@ +// Copyright (c) 2019- 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 + +#include "base/NativeApp.h" +#include "Common/ChunkFile.h" +#include "Core/HLE/HLE.h" +#include "Core/HLE/FunctionWrappers.h" +#include "Core/HLE/sceUsbMic.h" +#include "Core/MemMapHelpers.h" + +static int sceUsbMicInputBlocking(u32 size, u32 samplerate, u32 bufAddr) { + INFO_LOG(HLE, "UNIMPL sceUsbMicInputBlocking: size: %d, samplerate: %d", size, samplerate); + for (unsigned int i = 0; i < size; i++) { + if (Memory::IsValidAddress(bufAddr + i)) { + Memory::Write_U8(i & 0xFF, bufAddr + i); + } + } + hleEatMicro(1000000 / samplerate * (size / 2)); + return size; +} + +const HLEFunction sceUsbMic[] = +{ + {0x06128E42, nullptr, "sceUsbMicPollInputEnd", '?', "" }, + {0x2E6DCDCD, &WrapI_UUU, "sceUsbMicInputBlocking", 'i', "xxx" }, + {0x45310F07, nullptr, "sceUsbMicInputInitEx", '?', "" }, + {0x5F7F368D, nullptr, "sceUsbMicInput", '?', "" }, + {0x63400E20, nullptr, "sceUsbMicGetInputLength", '?', "" }, + {0xB8E536EB, nullptr, "sceUsbMicInputInit", '?', "" }, + {0xF899001C, nullptr, "sceUsbMicWaitInputEnd", '?', "" }, +}; + +void Register_sceUsbMic() +{ + RegisterModule("sceUsbMic", ARRAY_SIZE(sceUsbMic), sceUsbMic); +} diff --git a/Core/HLE/sceUsbMic.h b/Core/HLE/sceUsbMic.h new file mode 100644 index 0000000000..6540246f12 --- /dev/null +++ b/Core/HLE/sceUsbMic.h @@ -0,0 +1,20 @@ +// Copyright (c) 2019- 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 + +void Register_sceUsbMic(); diff --git a/Core/MIPS/ARM/ArmCompVFPU.cpp b/Core/MIPS/ARM/ArmCompVFPU.cpp index 61c9686543..94cd0d811c 100644 --- a/Core/MIPS/ARM/ArmCompVFPU.cpp +++ b/Core/MIPS/ARM/ArmCompVFPU.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 "Core/System.h" #include "Core/MIPS/ARM/ArmJit.h" #include "Core/MIPS/ARM/ArmRegCache.h" @@ -1468,15 +1469,17 @@ namespace MIPSComp } void ArmJit::Comp_Vmmul(MIPSOpcode op) { - if (PSP_CoreParameter().compat.flags().MMULDisableHack) - DISABLE; 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/ARM/ArmCompVFPUNEON.cpp b/Core/MIPS/ARM/ArmCompVFPUNEON.cpp index 3d1a997ae6..db1af06b6f 100644 --- a/Core/MIPS/ARM/ArmCompVFPUNEON.cpp +++ b/Core/MIPS/ARM/ArmCompVFPUNEON.cpp @@ -857,8 +857,6 @@ void ArmJit::CompNEON_Vmmov(MIPSOpcode op) { } void ArmJit::CompNEON_Vmmul(MIPSOpcode op) { - if (PSP_CoreParameter().compat.flags().MMULDisableHack) - DISABLE; CONDITIONAL_DISABLE(VFPU_MTX_VMMUL); MatrixSize msz = GetMtxSize(op); diff --git a/Core/MIPS/ARM64/Arm64CompVFPU.cpp b/Core/MIPS/ARM64/Arm64CompVFPU.cpp index 9eccb24b7e..69d6a302c6 100644 --- a/Core/MIPS/ARM64/Arm64CompVFPU.cpp +++ b/Core/MIPS/ARM64/Arm64CompVFPU.cpp @@ -21,16 +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/System.h" - #include "Common/Arm64Emitter.h" #include "Core/MIPS/ARM64/Arm64Jit.h" #include "Core/MIPS/ARM64/Arm64RegCache.h" @@ -1215,13 +1215,17 @@ namespace MIPSComp { } void Arm64Jit::Comp_Vmmul(MIPSOpcode op) { - if (PSP_CoreParameter().compat.flags().MMULDisableHack) - DISABLE; CONDITIONAL_DISABLE(VFPU_MTX_VMMUL); 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); int n = GetMatrixSide(sz); diff --git a/Core/MIPS/IR/IRCompVFPU.cpp b/Core/MIPS/IR/IRCompVFPU.cpp index 96d4bb9c22..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" @@ -1238,13 +1239,17 @@ namespace MIPSComp { // This may or may not be a win when using the IR interpreter... // Many more instructions to interpret. void IRFrontend::Comp_Vmmul(MIPSOpcode op) { - if (PSP_CoreParameter().compat.flags().MMULDisableHack) - DISABLE; CONDITIONAL_DISABLE(VFPU_MTX_VMMUL); 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; + } + // 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 9b4f091f5a..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,6 +469,8 @@ 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++) { union { float f; uint32_t u; } sum = { 0.0f }; @@ -473,11 +480,23 @@ namespace MIPSInt ApplySwizzleT(&t[a * 4], V_Quad); } - 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; + 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 { + 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.f; @@ -603,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 @@ -1134,11 +1153,18 @@ namespace MIPSInt ReadVector(t, sz, vt); ApplySwizzleT(t, V_Quad); - d.f = vfpu_dot(s, t); - if (my_isnan(d.f)) { - d.u = 0x7f800001; - } else if ((d.u & 0x7F800000) == 0) { - d.u &= 0xFF800000; + 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.f, V_Single); @@ -1177,7 +1203,14 @@ namespace MIPSInt } ApplyPrefixST(s, VFPURewritePrefix(VFPU_CTRL_SPREFIX, sprefixRemove, sprefixAdd), V_Quad); - float sum = vfpu_dot(s, t); + float sum = 0.0f; + 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); WriteVector(&d, V_Single, vd); @@ -1391,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; @@ -1413,7 +1452,14 @@ 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 = vfpu_dot(s, t); + 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); PC += 4; @@ -1444,7 +1490,14 @@ namespace MIPSInt tprefixAdd = 0; ApplyPrefixST(t, VFPURewritePrefix(VFPU_CTRL_TPREFIX, tprefixRemove, tprefixAdd), V_Quad); - d = vfpu_dot(s, t); + 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); PC += 4; @@ -1576,24 +1629,36 @@ namespace MIPSInt ReadMatrix(s, msz, vs); ReadVector(t, sz, vt); - 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 (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; + } } - } - for (int i = 0; i < ins; i++) { - d.f[i] = vfpu_dot(&s[i * 4], t2); + 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; + 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]; + } } } @@ -1618,12 +1683,19 @@ 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.f[ins] = vfpu_dot(&s[ins * 4], t); + 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; + 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. @@ -2029,10 +2101,12 @@ namespace MIPSInt case 8: d.f[i] = s[i] * t[i]; break; //vmul } - if (my_isnan(d.f[i])) { - d.u[i] = 0x7f800001; - } else if ((d.u[i] & 0x7F800000) == 0) { - d.u[i] &= 0xFF800000; + 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; + } } } @@ -2066,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 8de54a7834..280dec90e4 100644 --- a/Core/MIPS/MIPSVFPUUtils.cpp +++ b/Core/MIPS/MIPSVFPUUtils.cpp @@ -15,9 +15,12 @@ // Official git repository and contact information can be found at // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. +#include #include -#include +#include +#include +#include "Common/BitScan.h" #include "Common/CommonFuncs.h" #include "Core/Reporting.h" #include "Core/MIPS/MIPS.h" @@ -243,14 +246,36 @@ void ReadMatrix(float *rd, MatrixSize size, int reg) { default: _assert_msg_(JIT, 0, "%s: Bad matrix size", __FUNCTION__); } - for (int i = 0; i < side; i++) { - for (int j = 0; j < side; j++) { - int index = mtx * 4; - if (transpose) - index += ((row+i)&3) + ((col+j)&3)*32; - else - index += ((col+j)&3) + ((row+i)&3)*32; - rd[j*4 + i] = V(index); + // The voffset ordering is now integrated in these formulas, + // eliminating a table lookup. + const float *v = currentMIPS->v + (size_t)mtx * 16; + if (transpose) { + if (side == 4 && col == 0 && row == 0) { + // Fast path: Simple 4x4 transpose. TODO: Optimize. + for (int j = 0; j < 4; j++) { + for (int i = 0; i < 4; i++) { + rd[j * 4 + i] = v[i * 4 + j]; + } + } + } else { + for (int j = 0; j < side; j++) { + for (int i = 0; i < side; i++) { + int index = ((row + i) & 3) * 4 + ((col + j) & 3); + rd[j * 4 + i] = v[index]; + } + } + } + } else { + if (side == 4 && col == 0 && row == 0) { + // Fast path + memcpy(rd, v, sizeof(float) * 16); // rd[j * 4 + i] = v[j * 4 + i]; + } else { + for (int j = 0; j < side; j++) { + for (int i = 0; i < side; i++) { + int index = ((col + j) & 3) * 4 + ((row + i) & 3); + rd[j * 4 + i] = v[index]; + } + } } } } @@ -275,16 +300,38 @@ void WriteMatrix(const float *rd, MatrixSize size, int reg) { ERROR_LOG_REPORT(CPU, "Write mask used with vfpu matrix instruction."); } - for (int i = 0; i < side; i++) { - for (int j = 0; j < side; j++) { - // Hm, I wonder if this should affect matrices at all. - if (j != side -1 || !currentMIPS->VfpuWriteMask(i)) { - int index = mtx * 4; - if (transpose) - index += ((row+i)&3) + ((col+j)&3)*32; - else - index += ((col+j)&3) + ((row+i)&3)*32; - V(index) = rd[j*4+i]; + // The voffset ordering is now integrated in these formulas, + // eliminating a table lookup. + float *v = currentMIPS->v + (size_t)mtx * 16; + if (transpose) { + if (side == 4 && row == 0 && col == 0 && currentMIPS->VfpuWriteMask() == 0x0) { + // Fast path: Simple 4x4 transpose. TODO: Optimize. + for (int j = 0; j < side; j++) { + for (int i = 0; i < side; i++) { + v[i * 4 + j] = rd[j * 4 + i]; + } + } + } else { + for (int j = 0; j < side; j++) { + for (int i = 0; i < side; i++) { + if (j != side - 1 || !currentMIPS->VfpuWriteMask(i)) { + int index = ((row + i) & 3) * 4 + ((col + j) & 3); + v[index] = rd[j * 4 + i]; + } + } + } + } + } else { + if (side == 4 && row == 0 && col == 0 && currentMIPS->VfpuWriteMask() == 0x0) { + memcpy(v, rd, sizeof(float) * 16); // v[j * 4 + i] = rd[j * 4 + i]; + } else { + for (int j = 0; j < side; j++) { + for (int i = 0; i < side; i++) { + if (j != side - 1 || !currentMIPS->VfpuWriteMask(i)) { + int index = ((col + j) & 3) * 4 + ((row + i) & 3); + v[index] = rd[j * 4 + i]; + } + } } } } @@ -612,6 +659,10 @@ 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; @@ -626,32 +677,58 @@ float vfpu_dot(float a[4], float b[4]) { union float2int { uint32_t i; float f; - } intermed[4]; + }; + float2int result; + float2int src[2]; - for (int i = 0; i < 4; i++) { - intermed[i].f = a[i] * b[i]; - } - - uint32_t exps[4]; + int32_t exps[4]; int32_t mants[4]; - uint32_t max_exp = 0; - uint32_t last_inf = 0; + int32_t signs[4]; + int32_t max_exp = 0; + int32_t last_inf = -1; for (int i = 0; i < 4; i++) { - exps[i] = get_uexp(intermed[i].i); - // Preserve extra bits of precision in the mantissa during the add. - mants[i] = get_mant(intermed[i].i) << EXTRA_BITS; + 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) { - bool diff_sign = last_inf && get_sign(last_inf) != get_sign(intermed[i].i); - bool mant_nan = mants[i] != (0x00800000 << EXTRA_BITS); - if (diff_sign || mant_nan) { - intermed[0].i = 0x7F800001; - return intermed[0].f; + 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 = intermed[i].i; + last_inf = signs[i]; } } @@ -661,9 +738,9 @@ float vfpu_dot(float a[4], float b[4]) { if (exp >= 32) { mants[i] = 0; } else { - mants[i] >>= max_exp - exps[i]; + mants[i] >>= exp; } - if (get_sign(intermed[i].i)) { + if (signs[i]) { mants[i] = -mants[i]; } mant_sum += mants[i]; @@ -674,30 +751,147 @@ float vfpu_dot(float a[4], float b[4]) { sign_sum = 0x80000000; mant_sum = -mant_sum; } - // Chop off the extra bits. + + // 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) { + if (mant_sum == 0 || max_exp <= 0) { return 0.0f; } - while (mant_sum < 0x00800000) { - mant_sum <<= 1; - max_exp -= 1; + 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; } - while (mant_sum >= 0x01000000) { - mant_sum >>= 1; - max_exp += 1; - } - _dbg_assert_(JIT, (mant_sum & 0x00800000) != 0); + _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) { + } else if (max_exp <= 0) { return 0.0f; } - intermed[0].i = sign_sum | (max_exp << 23) | (mant_sum & 0x007FFFFF); - return intermed[0].f; + 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 8e3d52bf70..cf7c1e7426 100644 --- a/Core/MIPS/MIPSVFPUUtils.h +++ b/Core/MIPS/MIPSVFPUUtils.h @@ -98,6 +98,8 @@ inline float vfpu_clamp(float v, float min, float max) { } 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 diff --git a/Core/MIPS/x86/CompVFPU.cpp b/Core/MIPS/x86/CompVFPU.cpp index d9e08953ba..89bcf93fbc 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,13 +2805,16 @@ void Jit::Comp_VScl(MIPSOpcode op) { } void Jit::Comp_Vmmul(MIPSOpcode op) { - if (PSP_CoreParameter().compat.flags().MMULDisableHack) - DISABLE; 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/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 1cb8da8613..2f2122bb4c 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -252,7 +252,7 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) { } //in case we didn't go through EmuScreen::boot - g_Config.loadGameConfig(id); + g_Config.loadGameConfig(id, g_paramSFO.GetValueString("TITLE")); host->SendUIMessage("config_loaded", ""); INFO_LOG(LOADER,"Loading %s...", bootpath.c_str()); diff --git a/Core/Util/GameManager.cpp b/Core/Util/GameManager.cpp index 10586b3086..b2fc34767e 100644 --- a/Core/Util/GameManager.cpp +++ b/Core/Util/GameManager.cpp @@ -133,7 +133,7 @@ void GameManager::Update() { } } -void countSlashes(std::string fileName, int *slashLocation, int *slashCount) { +static void countSlashes(const std::string &fileName, int *slashLocation, int *slashCount) { *slashCount = 0; int lastSlashLocation = -1; *slashLocation = -1; @@ -206,9 +206,7 @@ ZipFileContents DetectZipFileContents(struct zip *z, ZipFileInfo *info) { isoFileIndex = i; } } else if (zippedName.find("textures.ini") != std::string::npos) { - int slashCount = 0; - int slashLocation = -1; - countSlashes(zippedName, &slashLocation, &slashCount); + int slashLocation = (int)zippedName.find_last_of('/'); if (stripCharsTexturePack == -1 || slashLocation < stripCharsTexturePack + 1) { stripCharsTexturePack = slashLocation + 1; isTexturePack = true; @@ -499,6 +497,9 @@ bool GameManager::InstallMemstickGame(struct zip *z, const std::string &zipfile, for (int i = 0; i < info.numFiles; i++) { const char *fn = zip_get_name(z, i, 0); std::string zippedName = fn; + if (zippedName.length() < (size_t)info.stripChars) { + continue; + } std::string outFilename = dest + zippedName.substr(info.stripChars); bool isDir = *outFilename.rbegin() == '/'; if (!isDir && outFilename.find("/") != std::string::npos) { @@ -521,8 +522,8 @@ bool GameManager::InstallMemstickGame(struct zip *z, const std::string &zipfile, for (int i = 0; i < info.numFiles; i++) { const char *fn = zip_get_name(z, i, 0); // Note that we do NOT write files that are not in a directory, to avoid random - // README files etc. - if (fileAllowed(fn)) { + // README files etc. (unless allowRoot is true.) + if (fileAllowed(fn) && strlen(fn) > (size_t)info.stripChars) { fn += info.stripChars; std::string outFilename = dest + fn; bool isDir = *outFilename.rbegin() == '/'; diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index a03fca92b0..ef1a9eb8e8 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -987,7 +987,6 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { CalculatePostShaderUniforms(vfb->bufferWidth, vfb->bufferHeight, renderWidth_, renderHeight_, &uniforms); BindPostShader(uniforms); DrawTextureFlags flags = g_Config.iBufFilter == SCALE_LINEAR ? DRAWTEX_LINEAR : DRAWTEX_NEAREST; - flags = flags | DRAWTEX_TO_BACKBUFFER; DrawActiveTexture(0, 0, fbo_w, fbo_h, fbo_w, fbo_h, 0.0f, 0.0f, 1.0f, 1.0f, ROTATION_LOCKED_HORIZONTAL, flags); draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); @@ -1010,6 +1009,7 @@ void FramebufferManagerCommon::CopyDisplayToOutput() { flags = flags | DRAWTEX_TO_BACKBUFFER; // Fullscreen Image SetViewport2D(0, 0, pixelWidth_, pixelHeight_); + draw_->SetScissorRect(0, 0, pixelWidth_, pixelHeight_); DrawActiveTexture(x, y, w, h, (float)pixelWidth_, (float)pixelHeight_, u0, v0, u1, v1, uvRotation, flags); } else { shaderManager_->DirtyLastShader(); // dirty lastShader_ BEFORE drawing @@ -1308,7 +1308,8 @@ void FramebufferManagerCommon::FindTransferFramebuffers(VirtualFramebuffer *&dst // Some games use mismatching bitdepths. But make sure the stride matches. // If it doesn't, generally this means we detected the framebuffer with too large a height. - bool match = yOffset < dstYOffset && (int)yOffset <= (int)vfb->height - dstHeight; + // Use bufferHeight in case of buffers that resize up and down often per frame (Valkyrie Profile.) + bool match = yOffset < dstYOffset && (int)yOffset <= (int)vfb->bufferHeight - dstHeight; if (match && vfb_byteStride != byteStride) { // Grand Knights History copies with a mismatching stride but a full line at a time. // Makes it hard to detect the wrong transfers in e.g. God of War. @@ -1338,7 +1339,7 @@ void FramebufferManagerCommon::FindTransferFramebuffers(VirtualFramebuffer *&dst const u32 byteOffset = srcBasePtr - vfb_address; const u32 byteStride = srcStride * bpp; const u32 yOffset = byteOffset / byteStride; - bool match = yOffset < srcYOffset && (int)yOffset <= (int)vfb->height - srcHeight; + bool match = yOffset < srcYOffset && (int)yOffset <= (int)vfb->bufferHeight - srcHeight; if (match && vfb_byteStride != byteStride) { if (width != srcStride || (byteStride * height != vfb_byteStride && byteStride * height != vfb_byteWidth)) { match = false; diff --git a/GPU/GLES/VertexShaderGeneratorGLES.cpp b/GPU/GLES/VertexShaderGeneratorGLES.cpp index c7e36d2b58..0621be566d 100644 --- a/GPU/GLES/VertexShaderGeneratorGLES.cpp +++ b/GPU/GLES/VertexShaderGeneratorGLES.cpp @@ -848,7 +848,7 @@ void GenerateVertexShader(const VShaderID &id, char *buffer, uint32_t *attrMask, const char *outMin = "projPos.x < u_cullRangeMin.x || projPos.y < u_cullRangeMin.y || projPos.z < u_cullRangeMin.z"; const char *outMax = "projPos.x > u_cullRangeMax.x || projPos.y > u_cullRangeMax.y || projPos.z > u_cullRangeMax.z"; WRITE(p, " if (%s || %s) {\n", outMin, outMax); - WRITE(p, " outPos.w = u_cullRangeMax.w;\n"); + WRITE(p, " outPos.xyzw = vec4(u_cullRangeMax.w);\n"); WRITE(p, " }\n"); WRITE(p, " }\n"); } diff --git a/GPU/Vulkan/FramebufferVulkan.cpp b/GPU/Vulkan/FramebufferVulkan.cpp index 918dafdbed..faa104aba7 100644 --- a/GPU/Vulkan/FramebufferVulkan.cpp +++ b/GPU/Vulkan/FramebufferVulkan.cpp @@ -305,8 +305,8 @@ void FramebufferManagerVulkan::DrawActiveTexture(float x, float y, float w, floa if ((flags & DRAWTEX_TO_BACKBUFFER) && g_display_rotation != DisplayRotation::ROTATE_0) { for (int i = 0; i < 4; i++) { - // backwards notation, should fix that... Vec3 v(vtx[i].x, vtx[i].y, 0.0f); + // backwards notation, should fix that... v = v * g_display_rot_matrix; vtx[i].x = v.x; vtx[i].y = v.y; diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index 18f6c1b6c0..6507f0f0de 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -136,6 +136,7 @@ void GPU_Vulkan::LoadCache(std::string filename) { // it can just memcpy the finished shader binaries out of the pipeline cache file. bool result = shaderManagerVulkan_->LoadCache(f); if (result) { + // WARNING: See comment in LoadCache if you are tempted to flip the second parameter to true. result = pipelineManager_->LoadCache(f, false, shaderManagerVulkan_, draw_, drawEngine_.GetPipelineLayout()); } fclose(f); @@ -159,6 +160,7 @@ void GPU_Vulkan::SaveCache(std::string filename) { if (!f) return; shaderManagerVulkan_->SaveCache(f); + // WARNING: See comment in LoadCache if you are tempted to flip the second parameter to true. pipelineManager_->SaveCache(f, false, shaderManagerVulkan_, draw_); INFO_LOG(G3D, "Saved Vulkan pipeline cache"); fclose(f); diff --git a/GPU/Vulkan/PipelineManagerVulkan.cpp b/GPU/Vulkan/PipelineManagerVulkan.cpp index 278ea2f9e1..9b253c1acc 100644 --- a/GPU/Vulkan/PipelineManagerVulkan.cpp +++ b/GPU/Vulkan/PipelineManagerVulkan.cpp @@ -582,6 +582,7 @@ void PipelineManagerVulkan::SaveCache(FILE *file, bool saveRawPipelineCache, Sha uint32_t size; if (saveRawPipelineCache) { + // WARNING: See comment in LoadCache before using this path. VkResult result = vkGetPipelineCacheData(vulkan_->GetDevice(), pipelineCache_, &dataSize, nullptr); uint32_t size = (uint32_t)dataSize; if (result != VK_SUCCESS) { @@ -670,6 +671,7 @@ bool PipelineManagerVulkan::LoadCache(FILE *file, bool loadRawPipelineCache, Sha uint32_t size = 0; if (loadRawPipelineCache) { + // WARNING: Do not use this path until after reading and implementing https://zeux.io/2019/07/17/serializing-pipeline-cache/ ! bool success = fread(&size, sizeof(size), 1, file) == 1; if (!size || !success) { WARN_LOG(G3D, "Zero-sized Vulkan pipeline cache."); diff --git a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp index b4c952e4a9..5a42af5ea3 100644 --- a/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp +++ b/GPU/Vulkan/VertexShaderGeneratorVulkan.cpp @@ -48,9 +48,6 @@ static const char *vulkan_glsl_preamble = // texcoord = 2 // fog = 3 - - - #undef WRITE #define WRITE p+=sprintf @@ -637,7 +634,7 @@ bool GenerateVulkanGLSLVertexShader(const VShaderID &id, char *buffer) { const char *outMin = "projPos.x < base.cullRangeMin.x || projPos.y < base.cullRangeMin.y || projPos.z < base.cullRangeMin.z"; const char *outMax = "projPos.x > base.cullRangeMax.x || projPos.y > base.cullRangeMax.y || projPos.z > base.cullRangeMax.z"; WRITE(p, " if (%s || %s) {\n", outMin, outMax); - WRITE(p, " outPos.w = base.cullRangeMax.w;\n"); + WRITE(p, " outPos.xyzw = vec4(base.cullRangeMax.w);\n"); WRITE(p, " }\n"); WRITE(p, " }\n"); } diff --git a/ext/native/base/NKCodeFromQt.h b/Qt/NKCodeFromQt.h similarity index 98% rename from ext/native/base/NKCodeFromQt.h rename to Qt/NKCodeFromQt.h index ee09d63417..2a87141720 100644 --- a/ext/native/base/NKCodeFromQt.h +++ b/Qt/NKCodeFromQt.h @@ -1,7 +1,9 @@ +#pragma once + #include "util/const_map.h" #include -// TODO: Add any missing keys +// TODO: Add any missing keys static const std::map KeyMapRawQttoNative = InitConstMap (Qt::Key_P, NKCODE_P) (Qt::Key_O, NKCODE_O) diff --git a/Qt/QtMain.h b/Qt/QtMain.h index 3e165a8d9f..bf351c84f7 100644 --- a/Qt/QtMain.h +++ b/Qt/QtMain.h @@ -33,7 +33,7 @@ QTM_USE_NAMESPACE #include "thin3d/thin3d.h" #include "base/NativeApp.h" #include "net/resolve.h" -#include "base/NKCodeFromQt.h" +#include "NKCodeFromQt.h" #include "Common/GraphicsContext.h" #include "Core/Core.h" diff --git a/ext/native/base/NKCodeFromSDL.h b/SDL/NKCodeFromSDL.h similarity index 98% rename from ext/native/base/NKCodeFromSDL.h rename to SDL/NKCodeFromSDL.h index 7485786d3a..d71654c945 100644 --- a/ext/native/base/NKCodeFromSDL.h +++ b/SDL/NKCodeFromSDL.h @@ -3,7 +3,7 @@ #include "util/const_map.h" #include -// TODO: Add any missing keys +// TODO: Add any missing keys static const std::map KeyMapRawSDLtoNative = InitConstMap (SDLK_UNKNOWN, NKCODE_UNKNOWN) (SDLK_p, NKCODE_P) diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 4c4a8677eb..0d001f32ad 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -27,7 +27,7 @@ SDLJoystick *joystick = NULL; #include "input/input_state.h" #include "input/keycodes.h" #include "net/resolve.h" -#include "base/NKCodeFromSDL.h" +#include "NKCodeFromSDL.h" #include "util/const_map.h" #include "util/text/utf8.h" #include "math/math_util.h" diff --git a/UI/EmuScreen.cpp b/UI/EmuScreen.cpp index f4775f3ae9..e27344be76 100644 --- a/UI/EmuScreen.cpp +++ b/UI/EmuScreen.cpp @@ -202,7 +202,7 @@ void EmuScreen::bootGame(const std::string &filename) { return; if (!info->id.empty()) { - g_Config.loadGameConfig(info->id); + g_Config.loadGameConfig(info->id, info->GetTitle()); // Reset views in case controls are in a different place. RecreateViews(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 4e5624f6a3..b5f266a34c 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -136,7 +136,8 @@ void GameSettingsScreen::CreateViews() { ReloadAllPostShaderInfo(); if (editThenRestore_) { - g_Config.loadGameConfig(gameID_); + std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0); + g_Config.loadGameConfig(gameID_, info->GetTitle()); } iAlternateSpeedPercent1_ = g_Config.iFpsLimit1 < 0 ? -1 : (g_Config.iFpsLimit1 * 100) / 60; @@ -1111,6 +1112,9 @@ void GameSettingsScreen::onFinish(DialogResult result) { Reporting::UpdateConfig(); g_Config.Save("GameSettingsScreen::onFinish"); if (editThenRestore_) { + // In case we didn't have the title yet before, try again. + std::shared_ptr info = g_gameInfoCache->GetInfo(nullptr, gamePath_, 0); + g_Config.changeGameSpecific(gameID_, info->GetTitle()); g_Config.unloadGameConfig(); } diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index af2e255820..5add771001 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -982,6 +982,7 @@ void NativeRender(GraphicsContext *graphicsContext) { break; } + // Compensate for rotated display if needed. if (g_display_rotation != DisplayRotation::ROTATE_0) { ortho = ortho * g_display_rot_matrix; } diff --git a/UI/TouchControlVisibilityScreen.cpp b/UI/TouchControlVisibilityScreen.cpp index 5dad49c7d9..1c7f23af4e 100644 --- a/UI/TouchControlVisibilityScreen.cpp +++ b/UI/TouchControlVisibilityScreen.cpp @@ -80,7 +80,7 @@ void TouchControlVisibilityScreen::CreateViews() { toggles_.push_back({ "Select", &g_Config.touchSelectKey.show, I_SELECT }); toggles_.push_back({ "Dpad", &g_Config.touchDpad.show, -1 }); toggles_.push_back({ "Analog Stick", &g_Config.touchAnalogStick.show, -1 }); - toggles_.push_back({ "Right Analog Stick", &g_Config.touchRightAnalogStick.show, -1 }); + toggles_.push_back({ "Right Analog Stick\n(not used by most games)", &g_Config.touchRightAnalogStick.show, -1 }); toggles_.push_back({ "Unthrottle", &g_Config.touchUnthrottleKey.show, -1 }); toggles_.push_back({ "Combo0", &g_Config.touchCombo0.show, I_1 }); toggles_.push_back({ "Combo1", &g_Config.touchCombo1.show, I_2 }); diff --git a/UWP/NativeUWP/NativeUWP.vcxproj.filters b/UWP/NativeUWP/NativeUWP.vcxproj.filters index 2bda07e6a3..c1658f0e62 100644 --- a/UWP/NativeUWP/NativeUWP.vcxproj.filters +++ b/UWP/NativeUWP/NativeUWP.vcxproj.filters @@ -505,12 +505,6 @@ base - - base - - - base - base diff --git a/Windows/MainWindow.cpp b/Windows/MainWindow.cpp index 95a7c0c134..017034b2b9 100644 --- a/Windows/MainWindow.cpp +++ b/Windows/MainWindow.cpp @@ -132,7 +132,6 @@ namespace MainWindow // gross hack bool noFocusPause = false; // TOGGLE_PAUSE state to override pause on lost focus bool trapMouse = true; // Handles some special cases(alt+tab, win menu) when game is running and mouse is confined - bool mouseScrollUsed = false; #define MAX_LOADSTRING 100 const TCHAR *szWindowClass = TEXT("PPSSPPWnd"); @@ -260,7 +259,6 @@ namespace MainWindow } void RelaseMouseWheel() { - if (mouseScrollUsed) { // For simplicity release both wheel events KeyInput key; key.deviceId = DEVICE_ID_MOUSE; @@ -269,8 +267,6 @@ namespace MainWindow NativeKey(key); key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; NativeKey(key); - mouseScrollUsed = false; - } } static void HandleSizeChange(int newSizingType) { @@ -710,27 +706,6 @@ namespace MainWindow } break; - case WM_MOUSEWHEEL: - { - int wheelDelta = (short)(wParam >> 16); - KeyInput key; - key.deviceId = DEVICE_ID_MOUSE; - - if (wheelDelta < 0) { - key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; - wheelDelta = -wheelDelta; - } else { - key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; - } - // There's no separate keyup event for mousewheel events, - // so we set mouseScrollUsed here and always release if it's true. - key.flags = KEY_DOWN | KEY_HASWHEELDELTA | (wheelDelta << 16); - mouseScrollUsed = true; - SetTimer(hwndMain, TIMER_WHEELRELEASE, WHEELRELEASE_DELAY_MS, 0); - NativeKey(key); - } - break; - case WM_TOUCH: { touchHandler.handleTouchEvent(hWnd, message, wParam, lParam); @@ -847,6 +822,27 @@ namespace MainWindow } break; + // Wheel events have to stay in WndProc for compatibility with older Windows(7). See #12156 + case WM_MOUSEWHEEL: + { + int wheelDelta = (short)(wParam >> 16); + KeyInput key; + key.deviceId = DEVICE_ID_MOUSE; + + if (wheelDelta < 0) { + key.keyCode = NKCODE_EXT_MOUSEWHEEL_DOWN; + wheelDelta = -wheelDelta; + } else { + key.keyCode = NKCODE_EXT_MOUSEWHEEL_UP; + } + // There's no separate keyup event for mousewheel events, + // so we release it with a slight delay. + key.flags = KEY_DOWN | KEY_HASWHEELDELTA | (wheelDelta << 16); + SetTimer(hwndMain, TIMER_WHEELRELEASE, WHEELRELEASE_DELAY_MS, 0); + NativeKey(key); + } + break; + case WM_TIMER: // Hack: Take the opportunity to also show/hide the mouse cursor in fullscreen mode. switch (wParam) { @@ -858,7 +854,7 @@ namespace MainWindow hideCursor = true; KillTimer(hWnd, TIMER_CURSORMOVEUPDATE); return 0; - // Hack: need to release wheel event without waiting for another wheel event. + // Hack: need to release wheel event with a delay for games to register it was "pressed down". case TIMER_WHEELRELEASE: RelaseMouseWheel(); return 0; diff --git a/Windows/WASAPIStream.cpp b/Windows/WASAPIStream.cpp index 728574ecb7..d92442ae37 100644 --- a/Windows/WASAPIStream.cpp +++ b/Windows/WASAPIStream.cpp @@ -141,7 +141,7 @@ private: #define REFTIMES_PER_SEC (10000000/200) #define REFTIMES_PER_MILLISEC (REFTIMES_PER_SEC / 1000) -WASAPIAudioBackend::WASAPIAudioBackend() { +WASAPIAudioBackend::WASAPIAudioBackend() : threadData_(0) { } WASAPIAudioBackend::~WASAPIAudioBackend() { @@ -177,7 +177,7 @@ bool WASAPIAudioBackend::Init(HWND window, StreamCallback callback, int sampleRa // This to be run only on the thread. class WASAPIAudioThread { public: - WASAPIAudioThread(volatile int &threadData, int &sampleRate, StreamCallback &callback) + WASAPIAudioThread(std::atomic &threadData, int &sampleRate, StreamCallback &callback) : threadData_(threadData), sampleRate_(sampleRate), callback_(callback) { } ~WASAPIAudioThread(); @@ -190,7 +190,7 @@ private: void ShutdownAudioDevice(); bool DetectFormat(); - volatile int &threadData_; + std::atomic &threadData_; int &sampleRate_; StreamCallback &callback_; @@ -200,7 +200,7 @@ private: CMMNotificationClient *notificationClient_ = nullptr; WAVEFORMATEXTENSIBLE *deviceFormat_ = nullptr; IAudioRenderClient *renderClient_ = nullptr; - short *shortBuf_ = nullptr; + int16_t *shortBuf_ = nullptr; enum class Format { UNKNOWN = 0, @@ -210,7 +210,7 @@ private: uint32_t numBufferFrames = 0; Format format_ = Format::UNKNOWN; - REFERENCE_TIME actualDuration_; + REFERENCE_TIME actualDuration_{}; }; WASAPIAudioThread::~WASAPIAudioThread() { @@ -264,6 +264,7 @@ void WASAPIAudioThread::ShutdownAudioDevice() { bool WASAPIAudioThread::DetectFormat() { // Don't know if PCM16 ever shows up here, the documentation only talks about float... but let's blindly // try to support it :P + format_ = Format::UNKNOWN; if (deviceFormat_->Format.wFormatTag == WAVE_FORMAT_EXTENSIBLE) { if (!memcmp(&deviceFormat_->SubFormat, &KSDATAFORMAT_SUBTYPE_IEEE_FLOAT, sizeof(deviceFormat_->SubFormat))) { @@ -285,10 +286,13 @@ bool WASAPIAudioThread::DetectFormat() { delete [] shortBuf_; shortBuf_ = nullptr; + if (format_ == Format::UNKNOWN) { + return false; + } BYTE *pData; HRESULT hresult = renderClient_->GetBuffer(numBufferFrames, &pData); - int numSamples = numBufferFrames * deviceFormat_->Format.nChannels; + const int numSamples = numBufferFrames * deviceFormat_->Format.nChannels; if (format_ == Format::IEEE_FLOAT) { memset(pData, 0, sizeof(float) * numSamples); shortBuf_ = new short[numBufferFrames * deviceFormat_->Format.nChannels]; @@ -298,15 +302,13 @@ bool WASAPIAudioThread::DetectFormat() { hresult = renderClient_->ReleaseBuffer(numBufferFrames, 0); actualDuration_ = (REFERENCE_TIME)((double)REFTIMES_PER_SEC * numBufferFrames / deviceFormat_->Format.nSamplesPerSec); - return true; } void WASAPIAudioThread::Run() { // Adapted from http://msdn.microsoft.com/en-us/library/windows/desktop/dd316756(v=vs.85).aspx - HRESULT hresult; - hresult = CoCreateInstance(CLSID_MMDeviceEnumerator, + HRESULT hresult = CoCreateInstance(CLSID_MMDeviceEnumerator, nullptr, /* Object is not created as the part of the aggregate */ CLSCTX_ALL, IID_IMMDeviceEnumerator, (void **)&deviceEnumerator_); if (FAILED(hresult)) diff --git a/Windows/WASAPIStream.h b/Windows/WASAPIStream.h index 2a708928cb..b9e2169a78 100644 --- a/Windows/WASAPIStream.h +++ b/Windows/WASAPIStream.h @@ -1,5 +1,6 @@ #pragma once +#include #include "WindowsAudio.h" // This should only be included from WindowsAudio.cpp and WASAPIStream.cpp. @@ -20,5 +21,5 @@ private: HANDLE hThread_ = nullptr; StreamCallback callback_ = nullptr; int sampleRate_ = 0; - volatile int threadData_ = 0; + std::atomic threadData_; }; diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 985b8efa8e..3853fa124a 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -394,6 +394,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HLE/sceUsbAcc.cpp \ $(SRC)/Core/HLE/sceUsbCam.cpp \ $(SRC)/Core/HLE/sceUsbGps.cpp \ + $(SRC)/Core/HLE/sceUsbMic.cpp \ $(SRC)/Core/HLE/sceUtility.cpp \ $(SRC)/Core/HLE/sceVaudio.cpp \ $(SRC)/Core/HLE/scePspNpDrm_user.cpp \ diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index c61acb745e..8d6e5f4c44 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -645,7 +645,7 @@ extern "C" void JNICALL Java_org_ppsspp_ppsspp_NativeApp_backbufferResize(JNIEnv ILOG("Size change detected (previously %d,%d) - calling NativeResized()", old_w, old_h); NativeResized(); } else { - ILOG("Size didn't change."); + ILOG("NativeApp::backbufferReisze: Size didn't change."); } } diff --git a/assets/compat.ini b/assets/compat.ini index 29dabaf503..19205e8a07 100644 --- a/assets/compat.ini +++ b/assets/compat.ini @@ -332,24 +332,6 @@ ULJS00201 = true # Bijin Tokei Portable not even a game, but needs it to load 2 miniatures, they will be low res, but better than black NPJH00055 = true -[MMULDisableHack] -# Force software MMUL to fix Tekken leg shaking -# Tekken 5 DR -ULJS00048 = true -ULUS10139 = true -UCES00356 = true -ULJS19013 = true -NPJH50137 = true - -# Tekken 6 -ULUS10466 = true -ULES01376 = true -ULJS00224 = true -NPUH10047 = true -ULAS42214 = true -ULJS19054 = true -NPJH50184 = true - [RequireBufferedRendering] # Warn the user that the game will not work and have issue, if buffered rendering is not enabled. # Midnight Club: LA Remix @@ -513,13 +495,13 @@ NPJH50036 = true # Burnout games have problems with this on Mali, and have no use for it # Legends -ULES00125 = true -ULUS10025 = true -ULJM05228 = true -NPJH50305 = true -ULJM05049 = true -ULKS46027 = true -ULAS42019 = true +#ULES00125 = true +#ULUS10025 = true +#ULJM05228 = true +#NPJH50305 = true +#ULJM05049 = true +#ULKS46027 = true +#ULAS42019 = true # Dominator ULUS10236 = true @@ -551,15 +533,17 @@ ULES01376 = true ULUS10466 = true [MGS2AcidHack] +ULUS10006 = true # Metal Gear Acid ULES00008 = true +ULJM05001 = true +ULAS42007 = true +ULJM08001 = true +ULUS10077 = true # Metal Gear Acid 2 +ULAS42035 = true ULES00284 = true ULJM05047 = true ULKS46065 = true -ULJM08001 = true -ULJM05001 = true -ULAS42007 = true -ULUS10006 = true -ULUS10077 = true +ULJM08011 = true [SonicRivalsHack] ULES00622 = true # SR1 @@ -735,3 +719,44 @@ ULES00034 = true ULES00035 = true # MTX MotoTrax ULUS10138 = true + +[HideISOFiles] +# DJ Max Portable has some crude copy-protection functionality where it looks for ISO/CSO files +# in a few directories. Prevent this by hiding the files from the game. +# To be sure, catch all versions and remixes of the game that's been seen in reports. +# It checks the following directories: +# / +# /PSP/ +# /PSP/COMMON +# /PSP/GAME + +ULKS46116 = true +ULKS46189 = true +ULKS46190 = true +ULKS46236 = true +ULKS46240 = true +ULKS46059 = true +ULUS10403 = true +ULUS10538 = true +ULKS46050 = true +ULJM05836 = true +ULJM46236 = true +ULJM06034 = true +NPHH00260 = true +CF0020046 = true +CF0020074 = true +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 diff --git a/assets/gamecontrollerdb.txt b/assets/gamecontrollerdb.txt index 13a4acd5cb..cbc06396c4 100644 --- a/assets/gamecontrollerdb.txt +++ b/assets/gamecontrollerdb.txt @@ -193,3 +193,4 @@ bd1200000000000015d0000000000000,Tomee SNES USB Controller,x:b3,a:b2,b:b1,y:b0,b 03000000380700008532000010010000,MadCatz Madcatz Fightpad,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftshoulder:b4,rightshoulder:b6,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,lefttrigger:b5,righttrigger:b7, 030000000d0f0000ee00000011010000,HORI CO.,LTD. HORIPAD mini4,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, 030000000d0f0000c100000011010000,HORI CO.,LTD. HORIPAD S,platform:Linux,a:b1,b:b2,y:b3,x:b0,start:b9,guide:b12,back:b8,leftstick:b10,rightstick:b11,leftshoulder:b4,rightshoulder:b5,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a2,righty:a3,lefttrigger:b6,righttrigger:b7, +03000000ad1b000003f5000033050000,Hori Fighting Stick VX,platform:Linux,a:b0,b:b1,x:b2,y:b3,back:b8,guide:b10,start:b9,leftshoulder:b4,rightshoulder:b5,-leftx:h0.8,+leftx:h0.2,-lefty:h0.1,+lefty:h0.4,lefttrigger:b6,righttrigger:b7, diff --git a/assets/lang b/assets/lang index 03707d64bf..b2d7f53fa0 160000 --- a/assets/lang +++ b/assets/lang @@ -1 +1 @@ -Subproject commit 03707d64bffc594d5707780a34ada77e8f43cdc0 +Subproject commit b2d7f53fa056586504590d271ab5f3198bfc2acd diff --git a/build.gradle b/build.gradle index 43f215cd6c..dcf3372b38 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ buildscript { jcenter() } dependencies { - classpath 'com.android.tools.build:gradle:3.4.1' + classpath 'com.android.tools.build:gradle:3.4.2' } } diff --git a/ext/CMakeLists.txt b/ext/CMakeLists.txt index b259a6f059..c9bbff9c47 100644 --- a/ext/CMakeLists.txt +++ b/ext/CMakeLists.txt @@ -8,6 +8,11 @@ endif() set(ENABLE_GLSLANG_BINARIES OFF CACHE BOOL "let's not build binaries we don't need" FORCE) set(SPIRV_CROSS_EXCEPTIONS_TO_ASSERTIONS ON CACHE BOOL "let's not use exceptions" FORCE) +# This is really a workaround for an NDK 20 compiler issue (PPSSPP issue #12105), but shouldn't hurt. +if(ANDROID) +set(ENABLE_HLSL OFF CACHE BOOL "let's not build HLSL support we don't need" FORCE) +endif() + add_subdirectory(glslang) add_subdirectory(snappy) add_subdirectory(udis86) diff --git a/ext/native/thin3d/VulkanQueueRunner.cpp b/ext/native/thin3d/VulkanQueueRunner.cpp index 49d0aa70ab..fdbdf3ebe9 100644 --- a/ext/native/thin3d/VulkanQueueRunner.cpp +++ b/ext/native/thin3d/VulkanQueueRunner.cpp @@ -158,7 +158,6 @@ void VulkanQueueRunner::InitBackbufferRenderPass() { dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dep.srcAccessMask = 0; dep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; - dep.dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT; VkRenderPassCreateInfo rp_info{ VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO }; rp_info.attachmentCount = 2; @@ -380,14 +379,18 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st // Planned optimizations: // * Create copies of render target that are rendered to multiple times and textured from in sequence, and push those render passes // as early as possible in the frame (Wipeout billboards). + // * Merge subsequent render passes to the same target that are interspersed with unrelated draws to other render targets (God of War). - for (int j = 0; j < (int)steps.size() - 1; j++) { + for (int j = 0; j < (int)steps.size(); j++) { if (steps[j]->stepType == VKRStepType::RENDER && + steps[j]->render.framebuffer && steps[j]->render.finalColorLayout == VK_IMAGE_LAYOUT_UNDEFINED) { // Just leave it at color_optimal. steps[j]->render.finalColorLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } + } + for (int j = 0; j < (int)steps.size() - 1; j++) { // Push down empty "Clear/Store" renderpasses, and merge them with the first "Load/Store" to the same framebuffer. // Actually let's just bother with the first one for now. This affects Wipeout Pure. if (steps.size() > 1 && steps[j]->stepType == VKRStepType::RENDER && @@ -459,11 +462,18 @@ void VulkanQueueRunner::RunSteps(VkCommandBuffer cmd, std::vector &st case VKRStepType::RENDER_SKIP: break; } + } + + // Deleting all in one go should be easier on the instruction cache than deleting + // them as we go - and easier to debug because we can look backwards in the frame. + for (size_t i = 0; i < steps.size(); i++) { delete steps[i]; } } void VulkanQueueRunner::ApplyMGSHack(std::vector &steps) { + // Really need a sane way to express transforms of steps. + // We want to turn a sequence of copy,render(1),copy,render(1),copy,render(1) to copy,copy,copy,render(n). for (int i = 0; i < (int)steps.size() - 3; i++) { @@ -531,6 +541,78 @@ void VulkanQueueRunner::ApplyMGSHack(std::vector &steps) { break; } } + + // There's also a post processing effect using depals that's just brutal in some parts + // of the game. + for (int i = 0; i < (int)steps.size() - 3; i++) { + int last = -1; + if (!(steps[i]->stepType == VKRStepType::RENDER && + steps[i + 1]->stepType == VKRStepType::RENDER && + steps[i + 2]->stepType == VKRStepType::RENDER && + steps[i]->render.numDraws == 1 && + steps[i + 1]->render.numDraws == 1 && + steps[i + 2]->render.numDraws == 1 && + steps[i]->render.color == VKRRenderPassAction::DONT_CARE && + steps[i + 1]->render.color == VKRRenderPassAction::KEEP && + steps[i + 2]->render.color == VKRRenderPassAction::DONT_CARE)) + continue; + VKRFramebuffer *depalFramebuffer = steps[i]->render.framebuffer; + VKRFramebuffer *targetFramebuffer = steps[i + 1]->render.framebuffer; + // OK, found the start of a post-process sequence. Let's scan until we find the end. + for (int j = i; j < steps.size() - 3; j++) { + if (((j - i) & 1) == 0) { + // This should be a depal draw. + if (steps[j]->render.numDraws != 1) + break; + if (steps[j]->render.color != VKRRenderPassAction::DONT_CARE) + break; + if (steps[j]->render.framebuffer != depalFramebuffer) + break; + last = j; + } else { + // This should be a target draw. + if (steps[j]->render.numDraws != 1) + break; + if (steps[j]->render.color != VKRRenderPassAction::KEEP) + break; + if (steps[j]->render.framebuffer != targetFramebuffer) + break; + last = j; + } + } + + if (last == -1) + continue; + + // Combine the depal renders. + for (int j = i + 2; j <= last + 1; j += 2) { + for (int k = 0; k < (int)steps[j]->commands.size(); k++) { + switch (steps[j]->commands[k].cmd) { + case VKRRenderCommand::DRAW: + case VKRRenderCommand::DRAW_INDEXED: + steps[i]->commands.push_back(steps[j]->commands[k]); + break; + } + } + steps[j]->stepType = VKRStepType::RENDER_SKIP; + } + + // Combine the target renders. + for (int j = i + 3; j <= last; j += 2) { + for (int k = 0; k < (int)steps[j]->commands.size(); k++) { + switch (steps[j]->commands[k].cmd) { + case VKRRenderCommand::DRAW: + case VKRRenderCommand::DRAW_INDEXED: + steps[i + 1]->commands.push_back(steps[j]->commands[k]); + break; + } + } + steps[j]->stepType = VKRStepType::RENDER_SKIP; + } + + // We're done - we only expect one of these sequences per frame. + break; + } } void VulkanQueueRunner::ApplySonicHack(std::vector &steps) { @@ -715,6 +797,9 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c VkPipelineStageFlags dstStage{}; switch (barrier.oldLayout) { case VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL: + barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; + break; case VK_IMAGE_LAYOUT_UNDEFINED: barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; srcStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; @@ -756,6 +841,22 @@ void VulkanQueueRunner::PerformRenderPass(const VKRStep &step, VkCommandBuffer c return; } + if (step.render.framebuffer && step.render.framebuffer->color.layout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL) { + VkImageMemoryBarrier barrier{}; + barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; + barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; + barrier.subresourceRange.layerCount = 1; + barrier.subresourceRange.levelCount = 1; + barrier.image = step.render.framebuffer->color.image; + barrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; + barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_COLOR_ATTACHMENT_READ_BIT; + barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; + barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; + vkCmdPipelineBarrier(cmd, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); + } + // This is supposed to bind a vulkan render pass to the command buffer. PerformBindFramebufferAsRenderTarget(step, cmd); @@ -874,6 +975,8 @@ void VulkanQueueRunner::PerformBindFramebufferAsRenderTarget(const VKRStep &step int w; int h; if (step.render.framebuffer) { + _dbg_assert_(G3D, step.render.finalColorLayout != VK_IMAGE_LAYOUT_UNDEFINED); + VKRFramebuffer *fb = step.render.framebuffer; framebuf = fb->framebuf; w = fb->width; diff --git a/ext/native/thin3d/VulkanRenderManager.cpp b/ext/native/thin3d/VulkanRenderManager.cpp index 444d3111bb..50a7efb849 100644 --- a/ext/native/thin3d/VulkanRenderManager.cpp +++ b/ext/native/thin3d/VulkanRenderManager.cpp @@ -400,6 +400,8 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR return; } } + + // More redundant bind elimination. if (curRenderStep_ && curRenderStep_->commands.size() == 0 && curRenderStep_->render.color == VKRRenderPassAction::KEEP && curRenderStep_->render.depth == VKRRenderPassAction::KEEP && curRenderStep_->render.stencil == VKRRenderPassAction::KEEP) { // Can trivially kill the last empty render step. assert(steps_.back() == curRenderStep_); @@ -412,10 +414,9 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR } VKRStep *step = new VKRStep{ VKRStepType::RENDER }; - // This is what queues up new passes, and can end previous ones. step->render.framebuffer = fb; step->render.color = color; - step->render.depth= depth; + step->render.depth = depth; step->render.stencil = stencil; step->render.clearColor = clearColor; step->render.clearDepth = clearDepth; @@ -426,8 +427,13 @@ void VulkanRenderManager::BindFramebufferAsRenderTarget(VKRFramebuffer *fb, VKRR steps_.push_back(step); curRenderStep_ = step; - curWidth_ = fb ? fb->width : vulkan_->GetBackbufferWidth(); - curHeight_ = fb ? fb->height : vulkan_->GetBackbufferHeight(); + if (fb) { + curWidth_ = fb->width; + curHeight_ = fb->height; + } else { + curWidth_ = vulkan_->GetBackbufferWidth(); + curHeight_ = vulkan_->GetBackbufferHeight(); + } } bool VulkanRenderManager::CopyFramebufferToMemorySync(VKRFramebuffer *src, int aspectBits, int x, int y, int w, int h, Draw::DataFormat destFormat, uint8_t *pixels, int pixelStride) { @@ -923,10 +929,10 @@ void VulkanRenderManager::Submit(int frame, bool triggerFence) { cmdBufs[numCmdBufs++] = frameData.mainCmd; VkSubmitInfo submit_info{ VK_STRUCTURE_TYPE_SUBMIT_INFO }; + VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; if (triggerFence && !frameData.skipSwap) { submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &acquireSemaphore_; - VkPipelineStageFlags waitStage[1]{ VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; submit_info.pWaitDstStageMask = waitStage; } submit_info.commandBufferCount = (uint32_t)numCmdBufs;