diff --git a/CMakeLists.txt b/CMakeLists.txt index 25bde24f96..63b4b5ff62 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2361,6 +2361,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/FileLoaders/HTTPFileLoader.h Core/FileLoaders/LocalFileLoader.cpp Core/FileLoaders/LocalFileLoader.h + Core/FileLoaders/RamCachingFileLoader.cpp + Core/FileLoaders/RamCachingFileLoader.h Core/FileLoaders/RetryingFileLoader.cpp Core/FileLoaders/RetryingFileLoader.h Core/MIPS/MIPS.cpp diff --git a/Common/System/System.h b/Common/System/System.h index 8bc5d71b98..e171d9f760 100644 --- a/Common/System/System.h +++ b/Common/System/System.h @@ -219,6 +219,8 @@ enum SystemProperty { SYSPROP_CAN_READ_BATTERY_PERCENTAGE, SYSPROP_BATTERY_PERCENTAGE, + + SYSPROP_ENOUGH_RAM_FOR_FULL_ISO, }; enum class SystemNotification { diff --git a/Core/Config.cpp b/Core/Config.cpp index 2ea788dc48..1b39b9210b 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -280,6 +280,7 @@ static const ConfigSetting generalSettings[] = { // "default" means let emulator decide, "" means disable. ConfigSetting("ReportingHost", &g_Config.sReportHost, "default", CfgFlag::DEFAULT), ConfigSetting("AutoSaveSymbolMap", &g_Config.bAutoSaveSymbolMap, false, CfgFlag::PER_GAME), + ConfigSetting("CacheFullIsoInRam", &g_Config.bCacheFullIsoInRam, false, CfgFlag::PER_GAME), ConfigSetting("RemoteISOPort", &g_Config.iRemoteISOPort, 0, CfgFlag::DEFAULT), ConfigSetting("LastRemoteISOServer", &g_Config.sLastRemoteISOServer, "", CfgFlag::DEFAULT), ConfigSetting("LastRemoteISOPort", &g_Config.iLastRemoteISOPort, 0, CfgFlag::DEFAULT), diff --git a/Core/Config.h b/Core/Config.h index 54bc7dcb08..75cd5ff65b 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -122,6 +122,7 @@ public: int iIOTimingMethod; int iLockedCPUSpeed; bool bAutoSaveSymbolMap; + bool bCacheFullIsoInRam; int iRemoteISOPort; std::string sLastRemoteISOServer; int iLastRemoteISOPort; diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index 4c93ee4e8e..bd5959c82c 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -667,6 +667,7 @@ + @@ -1255,6 +1256,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 0f4a8298f9..ff72532a1d 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -661,6 +661,9 @@ HW + + FileLoaders + MIPS\IR @@ -1887,6 +1890,9 @@ HW + + FileLoaders + MIPS\IR diff --git a/Core/FileLoaders/RamCachingFileLoader.cpp b/Core/FileLoaders/RamCachingFileLoader.cpp new file mode 100644 index 0000000000..5e7eb07e62 --- /dev/null +++ b/Core/FileLoaders/RamCachingFileLoader.cpp @@ -0,0 +1,272 @@ +// Copyright (c) 2015- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#include +#include +#include + +#include "Common/Thread/ThreadUtil.h" +#include "Common/TimeUtil.h" +#include "Common/Log.h" +#include "Core/FileLoaders/RamCachingFileLoader.h" + +// Takes ownership of backend. +RamCachingFileLoader::RamCachingFileLoader(FileLoader *backend) + : ProxiedFileLoader(backend) { + filesize_ = backend->FileSize(); + if (filesize_ > 0) { + InitCache(); + } +} + +RamCachingFileLoader::~RamCachingFileLoader() { + if (filesize_ > 0) { + ShutdownCache(); + } +} + +bool RamCachingFileLoader::Exists() { + if (exists_ == -1) { + exists_ = ProxiedFileLoader::Exists() ? 1 : 0; + } + return exists_ == 1; +} + +bool RamCachingFileLoader::ExistsFast() { + if (exists_ == -1) { + return ProxiedFileLoader::ExistsFast(); + } + return exists_ == 1; +} + +bool RamCachingFileLoader::IsDirectory() { + if (isDirectory_ == -1) { + isDirectory_ = ProxiedFileLoader::IsDirectory() ? 1 : 0; + } + return isDirectory_ == 1; +} + +s64 RamCachingFileLoader::FileSize() { + return filesize_; +} + +size_t RamCachingFileLoader::ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags) { + size_t readSize = 0; + if (cache_ == nullptr || (flags & Flags::HINT_UNCACHED) != 0) { + readSize = backend_->ReadAt(absolutePos, bytes, data, flags); + } else { + readSize = ReadFromCache(absolutePos, bytes, data); + // While in case the cache size is too small for the entire read. + while (readSize < bytes) { + SaveIntoCache(absolutePos + readSize, bytes - readSize, flags); + size_t bytesFromCache = ReadFromCache(absolutePos + readSize, bytes - readSize, (u8 *)data + readSize); + readSize += bytesFromCache; + if (bytesFromCache == 0) { + // We can't read any more. + break; + } + } + + StartReadAhead(absolutePos + readSize); + } + return readSize; +} + +void RamCachingFileLoader::InitCache() { + std::lock_guard guard(blocksMutex_); + u32 blockCount = (u32)((filesize_ + BLOCK_SIZE - 1) >> BLOCK_SHIFT); + // Overallocate for the last block. + cache_ = (u8 *)malloc((size_t)blockCount << BLOCK_SHIFT); + if (cache_ == nullptr) { + ERROR_LOG(Log::IO, "Failed to allocate cache for Cache full ISO in RAM! Will fall back to regular reads."); + return; + } + aheadRemaining_ = blockCount; + blocks_.resize(blockCount); +} + +void RamCachingFileLoader::ShutdownCache() { + Cancel(); + + // We can't delete while the thread is running, so have to wait. + // This should only happen from the menu. + while (aheadThreadRunning_) { + sleep_ms(1, "shutdown-ram-cache-poll"); + } + if (aheadThread_.joinable()) + aheadThread_.join(); + + std::lock_guard guard(blocksMutex_); + blocks_.clear(); + if (cache_ != nullptr) { + free(cache_); + cache_ = nullptr; + } +} + +void RamCachingFileLoader::Cancel() { + if (aheadThreadRunning_) { + std::lock_guard guard(blocksMutex_); + aheadCancel_ = true; + } + + ProxiedFileLoader::Cancel(); +} + +size_t RamCachingFileLoader::ReadFromCache(s64 pos, size_t bytes, void *data) { + s64 cacheStartPos = pos >> BLOCK_SHIFT; + s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT; + if ((size_t)cacheEndPos >= blocks_.size()) { + cacheEndPos = blocks_.size() - 1; + } + + size_t readSize = 0; + size_t offset = (size_t)(pos - (cacheStartPos << BLOCK_SHIFT)); + u8 *p = (u8 *)data; + + // Clamp bytes to what's actually available. + if (pos + (s64)bytes > filesize_) { + // Should've been caught above, but just in case. + if (pos >= filesize_) { + return 0; + } + bytes = (size_t)(filesize_ - pos); + } + + std::lock_guard guard(blocksMutex_); + for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) { + if (blocks_[(size_t)i] == 0) { + return readSize; + } + + size_t toRead = std::min(bytes - readSize, (size_t)BLOCK_SIZE - offset); + s64 cachePos = (i << BLOCK_SHIFT) + offset; + memcpy(p + readSize, &cache_[cachePos], toRead); + readSize += toRead; + + // Don't need an offset after the first read. + offset = 0; + } + return readSize; +} + +void RamCachingFileLoader::SaveIntoCache(s64 pos, size_t bytes, Flags flags) { + s64 cacheStartPos = pos >> BLOCK_SHIFT; + s64 cacheEndPos = (pos + bytes - 1) >> BLOCK_SHIFT; + if ((size_t)cacheEndPos >= blocks_.size()) { + cacheEndPos = blocks_.size() - 1; + } + + size_t blocksToRead = 0; + { + std::lock_guard guard(blocksMutex_); + for (s64 i = cacheStartPos; i <= cacheEndPos; ++i) { + if (blocks_[(size_t)i] == 0) { + ++blocksToRead; + if (blocksToRead >= MAX_BLOCKS_PER_READ) { + break; + } + + // TODO: Shouldn't we break as soon as we see a 1? + } + } + } + + s64 cacheFilePos = cacheStartPos << BLOCK_SHIFT; + size_t bytesRead = backend_->ReadAt(cacheFilePos, blocksToRead << BLOCK_SHIFT, &cache_[cacheFilePos], flags); + + // In case there was an error, let's not mark blocks that failed to read as read. + u32 blocksActuallyRead = (u32)((bytesRead + BLOCK_SIZE - 1) >> BLOCK_SHIFT); + { + std::lock_guard guard(blocksMutex_); + + // In case they were simultaneously read. + u32 blocksRead = 0; + for (size_t i = 0; i < blocksActuallyRead; ++i) { + if (blocks_[(size_t)cacheStartPos + i] == 0) { + blocks_[(size_t)cacheStartPos + i] = 1; + ++blocksRead; + } + } + + if (aheadRemaining_ != 0) { + aheadRemaining_ -= blocksRead; + } + } +} + +void RamCachingFileLoader::StartReadAhead(s64 pos) { + if (cache_ == nullptr) { + return; + } + + std::lock_guard guard(blocksMutex_); + aheadPos_ = pos; + if (aheadThreadRunning_) { + // Already going. + return; + } + + aheadThreadRunning_ = true; + aheadCancel_ = false; + if (aheadThread_.joinable()) + aheadThread_.join(); + aheadThread_ = std::thread([this] { + SetCurrentThreadName("FileLoaderReadAhead"); + + AndroidJNIThreadContext jniContext; + + while (aheadRemaining_ != 0 && !aheadCancel_) { + // Where should we look? + const u32 cacheStartPos = NextAheadBlock(); + if (cacheStartPos == 0xFFFFFFFF) { + // Must be full. + break; + } + u32 cacheEndPos = cacheStartPos + BLOCK_READAHEAD - 1; + if (cacheEndPos >= blocks_.size()) { + cacheEndPos = (u32)blocks_.size() - 1; + } + + for (u32 i = cacheStartPos; i <= cacheEndPos; ++i) { + if (blocks_[i] == 0) { + SaveIntoCache((u64)i << BLOCK_SHIFT, BLOCK_SIZE * BLOCK_READAHEAD, Flags::NONE); + break; + } + } + } + + aheadThreadRunning_ = false; + }); +} + +u32 RamCachingFileLoader::NextAheadBlock() { + std::lock_guard guard(blocksMutex_); + + // If we had an aheadPos_ set, start reading from there and go forward. + u32 startFrom = (u32)(aheadPos_ >> BLOCK_SHIFT); + // But next time, start from the beginning again. + aheadPos_ = 0; + + for (u32 i = startFrom; i < blocks_.size(); ++i) { + if (blocks_[i] == 0) { + return i; + } + } + + return 0xFFFFFFFF; +} diff --git a/Core/FileLoaders/RamCachingFileLoader.h b/Core/FileLoaders/RamCachingFileLoader.h new file mode 100644 index 0000000000..dde5e74ecb --- /dev/null +++ b/Core/FileLoaders/RamCachingFileLoader.h @@ -0,0 +1,72 @@ +// Copyright (c) 2015- PPSSPP Project. + +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, version 2.0 or later versions. + +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License 2.0 for more details. + +// A copy of the GPL 2.0 should have been included with the program. +// If not, see http://www.gnu.org/licenses/ + +// Official git repository and contact information can be found at +// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. + +#pragma once + +#include +#include +#include + +#include "Common/CommonTypes.h" +#include "Core/Loaders.h" + +class RamCachingFileLoader : public ProxiedFileLoader { +public: + RamCachingFileLoader(FileLoader *backend); + ~RamCachingFileLoader(); + + bool Exists() override; + bool ExistsFast() override; + bool IsDirectory() override; + s64 FileSize() override; + + size_t ReadAt(s64 absolutePos, size_t bytes, size_t count, void *data, Flags flags = Flags::NONE) override { + return ReadAt(absolutePos, bytes * count, data, flags) / bytes; + } + size_t ReadAt(s64 absolutePos, size_t bytes, void *data, Flags flags = Flags::NONE) override; + + void Cancel() override; + +private: + void InitCache(); + void ShutdownCache(); + size_t ReadFromCache(s64 pos, size_t bytes, void *data); + // Guaranteed to read at least one block into the cache. + void SaveIntoCache(s64 pos, size_t bytes, Flags flags); + void StartReadAhead(s64 pos); + u32 NextAheadBlock(); + + enum { + BLOCK_SIZE = 65536, + BLOCK_SHIFT = 16, + MAX_BLOCKS_PER_READ = 16, + BLOCK_READAHEAD = 4, + }; + + s64 filesize_ = 0; + u8 *cache_ = nullptr; + int exists_ = -1; + int isDirectory_ = -1; + + std::vector blocks_; + std::mutex blocksMutex_; + u32 aheadRemaining_; + s64 aheadPos_; + std::thread aheadThread_; + bool aheadThreadRunning_ = false; + bool aheadCancel_ = false; +}; diff --git a/Core/System.cpp b/Core/System.cpp index 1ee819ede8..27a27998ef 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -57,6 +57,7 @@ #include "Core/Core.h" #include "Core/CoreTiming.h" #include "Core/CoreParameter.h" +#include "Core/FileLoaders/RamCachingFileLoader.h" #include "Core/FileSystems/MetaFileSystem.h" #include "Core/Loaders.h" #include "Core/PSPLoaders.h" @@ -424,6 +425,20 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) { IdentifiedFileType type = Identify_File(loadedFile, &g_CoreParameter.errorString); g_CoreParameter.fileType = type; + if (System_GetPropertyBool(SYSPROP_ENOUGH_RAM_FOR_FULL_ISO)) { + if (g_Config.bCacheFullIsoInRam) { + switch (g_CoreParameter.fileType) { + case IdentifiedFileType::PSP_ISO: + case IdentifiedFileType::PSP_ISO_NP: + loadedFile = new RamCachingFileLoader(loadedFile); + break; + default: + INFO_LOG(Log::System, "RAM caching is on, but file is not an ISO, so ignoring"); + break; + } + } + } + if (g_Config.bAchievementsEnable) { // Need to re-identify after ResolveFileLoaderTarget - although in practice probably not, // but also, re-using the identification would require some plumbing, to be done later. diff --git a/SDL/SDLMain.cpp b/SDL/SDLMain.cpp index 1dd710ac35..537f250762 100644 --- a/SDL/SDLMain.cpp +++ b/SDL/SDLMain.cpp @@ -625,7 +625,13 @@ bool System_GetPropertyBool(SystemProperty prop) { #endif case SYSPROP_CAN_READ_BATTERY_PERCENTAGE: return true; -default: + case SYSPROP_ENOUGH_RAM_FOR_FULL_ISO: +#if defined(MOBILE_DEVICE) + return false; +#else + return true; +#endif + default: return false; } } diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 4148077e77..a6d2e9d55a 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1341,6 +1341,10 @@ void GameSettingsScreen::CreateSystemSettings(UI::ViewGroup *systemSettings) { if (System_GetPropertyBool(SYSPROP_HAS_KEYBOARD)) systemSettings->Add(new CheckBox(&g_Config.bBypassOSKWithKeyboard, sy->T("Use system native keyboard"))); + if (System_GetPropertyBool(SYSPROP_ENOUGH_RAM_FOR_FULL_ISO)) { + systemSettings->Add(new CheckBox(&g_Config.bCacheFullIsoInRam, sy->T("Cache ISO in RAM", "Cache full ISO in RAM")))->SetEnabled(!PSP_IsInited()); + } + systemSettings->Add(new CheckBox(&g_Config.bCheckForNewVersion, sy->T("VersionCheck", "Check for new versions of PPSSPP"))); systemSettings->Add(new CheckBox(&g_Config.bScreenshotsAsPNG, sy->T("Screenshots as PNG"))); static const char *screenshotModeChoices[] = { "Final processed image", "Raw game image" }; diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 5d59554e4d..d53eda7dc4 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -163,6 +163,7 @@ + @@ -425,6 +426,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index cf6274c2bb..d19c655206 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -306,6 +306,9 @@ FileLoaders + + FileLoaders + FileLoaders @@ -1395,6 +1398,9 @@ FileLoaders + + FileLoaders + FileLoaders diff --git a/Windows/main.cpp b/Windows/main.cpp index 5b38d02c75..f9ede5e66f 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -406,6 +406,8 @@ bool System_GetPropertyBool(SystemProperty prop) { return true; case SYSPROP_CAN_READ_BATTERY_PERCENTAGE: return true; + case SYSPROP_ENOUGH_RAM_FOR_FULL_ISO: + return true; default: return false; } diff --git a/android/jni/Android.mk b/android/jni/Android.mk index f0b1d905a7..c061a27b73 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -607,6 +607,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/FileLoaders/DiskCachingFileLoader.cpp \ $(SRC)/Core/FileLoaders/HTTPFileLoader.cpp \ $(SRC)/Core/FileLoaders/LocalFileLoader.cpp \ + $(SRC)/Core/FileLoaders/RamCachingFileLoader.cpp \ $(SRC)/Core/FileLoaders/RetryingFileLoader.cpp \ $(SRC)/Core/MemFault.cpp \ $(SRC)/Core/MemMap.cpp \ diff --git a/libretro/Makefile.common b/libretro/Makefile.common index d71adc9ce1..604cb9861b 100644 --- a/libretro/Makefile.common +++ b/libretro/Makefile.common @@ -656,6 +656,7 @@ SOURCES_CXX += \ $(COREDIR)/FileLoaders/CachingFileLoader.cpp \ $(COREDIR)/FileLoaders/DiskCachingFileLoader.cpp \ $(COREDIR)/FileLoaders/RetryingFileLoader.cpp \ + $(COREDIR)/FileLoaders/RamCachingFileLoader.cpp \ $(COREDIR)/FileLoaders/LocalFileLoader.cpp \ $(COREDIR)/CoreTiming.cpp \ $(COREDIR)/CwCheat.cpp \ diff --git a/libretro/libretro.cpp b/libretro/libretro.cpp index 9cc0f060ad..aea0227eb7 100644 --- a/libretro/libretro.cpp +++ b/libretro/libretro.cpp @@ -591,6 +591,15 @@ static void check_variables(CoreParameter &coreParam) if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) g_Config.iLockedCPUSpeed = atoi(var.value); + var.key = "ppsspp_cache_iso"; + if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) + { + if (!strcmp(var.value, "disabled")) + g_Config.bCacheFullIsoInRam = false; + else + g_Config.bCacheFullIsoInRam = true; + } + var.key = "ppsspp_cheats"; if (environ_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &var) && var.value) {