Merge pull request #20165 from hrydgard/reintroduce-and-fix-cache-in-ram

Reintroduce and fix feature checks for "Cache full ISO in RAM"
This commit is contained in:
Henrik Rydgård 2025-03-27 02:10:28 +01:00 committed by GitHub
commit a4af129983
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
17 changed files with 405 additions and 1 deletions

View file

@ -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

View file

@ -219,6 +219,8 @@ enum SystemProperty {
SYSPROP_CAN_READ_BATTERY_PERCENTAGE,
SYSPROP_BATTERY_PERCENTAGE,
SYSPROP_ENOUGH_RAM_FOR_FULL_ISO,
};
enum class SystemNotification {

View file

@ -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),

View file

@ -122,6 +122,7 @@ public:
int iIOTimingMethod;
int iLockedCPUSpeed;
bool bAutoSaveSymbolMap;
bool bCacheFullIsoInRam;
int iRemoteISOPort;
std::string sLastRemoteISOServer;
int iLastRemoteISOPort;

View file

@ -667,6 +667,7 @@
<ClCompile Include="FileLoaders\DiskCachingFileLoader.cpp" />
<ClCompile Include="FileLoaders\HTTPFileLoader.cpp" />
<ClCompile Include="FileLoaders\LocalFileLoader.cpp" />
<ClCompile Include="FileLoaders\RamCachingFileLoader.cpp" />
<ClCompile Include="FileLoaders\RetryingFileLoader.cpp" />
<ClCompile Include="FileSystems\BlockDevices.cpp" />
<ClCompile Include="FileSystems\DirectoryFileSystem.cpp" />
@ -1255,6 +1256,7 @@
<ClInclude Include="FileLoaders\DiskCachingFileLoader.h" />
<ClInclude Include="FileLoaders\HTTPFileLoader.h" />
<ClInclude Include="FileLoaders\LocalFileLoader.h" />
<ClInclude Include="FileLoaders\RamCachingFileLoader.h" />
<ClInclude Include="FileLoaders\RetryingFileLoader.h" />
<ClInclude Include="FileSystems\BlockDevices.h" />
<ClInclude Include="FileSystems\DirectoryFileSystem.h" />

View file

@ -661,6 +661,9 @@
<ClCompile Include="HW\SasReverb.cpp">
<Filter>HW</Filter>
</ClCompile>
<ClCompile Include="FileLoaders\RamCachingFileLoader.cpp">
<Filter>FileLoaders</Filter>
</ClCompile>
<ClCompile Include="MIPS\IR\IRCompALU.cpp">
<Filter>MIPS\IR</Filter>
</ClCompile>
@ -1887,6 +1890,9 @@
<ClInclude Include="HW\SasReverb.h">
<Filter>HW</Filter>
</ClInclude>
<ClInclude Include="FileLoaders\RamCachingFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>
<ClInclude Include="MIPS\IR\IRJit.h">
<Filter>MIPS\IR</Filter>
</ClInclude>

View file

@ -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 <algorithm>
#include <thread>
#include <cstring>
#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<std::mutex> 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<std::mutex> guard(blocksMutex_);
blocks_.clear();
if (cache_ != nullptr) {
free(cache_);
cache_ = nullptr;
}
}
void RamCachingFileLoader::Cancel() {
if (aheadThreadRunning_) {
std::lock_guard<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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<std::mutex> 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;
}

View file

@ -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 <vector>
#include <mutex>
#include <thread>
#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<u8> blocks_;
std::mutex blocksMutex_;
u32 aheadRemaining_;
s64 aheadPos_;
std::thread aheadThread_;
bool aheadThreadRunning_ = false;
bool aheadCancel_ = false;
};

View file

@ -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.

View file

@ -625,6 +625,12 @@ bool System_GetPropertyBool(SystemProperty prop) {
#endif
case SYSPROP_CAN_READ_BATTERY_PERCENTAGE:
return true;
case SYSPROP_ENOUGH_RAM_FOR_FULL_ISO:
#if defined(MOBILE_DEVICE)
return false;
#else
return true;
#endif
default:
return false;
}

View file

@ -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" };

View file

@ -163,6 +163,7 @@
<ClInclude Include="..\..\Core\FileLoaders\DiskCachingFileLoader.h" />
<ClInclude Include="..\..\Core\FileLoaders\HTTPFileLoader.h" />
<ClInclude Include="..\..\Core\FileLoaders\LocalFileLoader.h" />
<ClInclude Include="..\..\Core\FileLoaders\RamCachingFileLoader.h" />
<ClInclude Include="..\..\Core\FileLoaders\RetryingFileLoader.h" />
<ClInclude Include="..\..\Core\FileSystems\BlobFileSystem.h" />
<ClInclude Include="..\..\Core\FileSystems\BlockDevices.h" />
@ -425,6 +426,7 @@
<ClCompile Include="..\..\Core\FileLoaders\DiskCachingFileLoader.cpp" />
<ClCompile Include="..\..\Core\FileLoaders\HTTPFileLoader.cpp" />
<ClCompile Include="..\..\Core\FileLoaders\LocalFileLoader.cpp" />
<ClCompile Include="..\..\Core\FileLoaders\RamCachingFileLoader.cpp" />
<ClCompile Include="..\..\Core\FileLoaders\RetryingFileLoader.cpp" />
<ClCompile Include="..\..\Core\FileSystems\BlobFileSystem.cpp" />
<ClCompile Include="..\..\Core\FileSystems\BlockDevices.cpp" />

View file

@ -306,6 +306,9 @@
<ClCompile Include="..\..\Core\FileLoaders\LocalFileLoader.cpp">
<Filter>FileLoaders</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\FileLoaders\RamCachingFileLoader.cpp">
<Filter>FileLoaders</Filter>
</ClCompile>
<ClCompile Include="..\..\Core\FileLoaders\RetryingFileLoader.cpp">
<Filter>FileLoaders</Filter>
</ClCompile>
@ -1395,6 +1398,9 @@
<ClInclude Include="..\..\Core\FileLoaders\LocalFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\FileLoaders\RamCachingFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>
<ClInclude Include="..\..\Core\FileLoaders\RetryingFileLoader.h">
<Filter>FileLoaders</Filter>
</ClInclude>

View file

@ -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;
}

View file

@ -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 \

View file

@ -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 \

View file

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