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)
{