PSP boot: Move more of the startup process into the loading thread. Simplifies the code a bit.

This commit is contained in:
Henrik Rydgård 2025-03-30 10:46:22 +02:00
parent 19c7d795a7
commit ee90d2acc1
10 changed files with 92 additions and 164 deletions

View file

@ -14,4 +14,6 @@
typedef bool (*BadAccessHandler)(uintptr_t address, void *context);
void InstallExceptionHandler(BadAccessHandler accessHandler);
// Implementation note: This must be a no-op if InstallExceptionHandler hasn't been called.
void UninstallExceptionHandler();

View file

@ -434,7 +434,7 @@ void __KernelMemoryInit()
kernelMemory.Init(PSP_GetKernelMemoryBase(), PSP_GetKernelMemoryEnd() - PSP_GetKernelMemoryBase(), false);
userMemory.Init(PSP_GetUserMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetUserMemoryBase(), false);
volatileMemory.Init(PSP_GetVolatileMemoryStart(), PSP_GetVolatileMemoryEnd() - PSP_GetVolatileMemoryStart(), false);
ParallelMemset(&g_threadManager, Memory::GetPointerWrite(PSP_GetKernelMemoryBase()), 0, PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase(), TaskPriority::HIGH);
Memory::Memset(PSP_GetKernelMemoryBase(), 0, PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase());
NotifyMemInfo(MemBlockFlags::WRITE, PSP_GetKernelMemoryBase(), PSP_GetUserMemoryEnd() - PSP_GetKernelMemoryBase(), "MemInit");
INFO_LOG(Log::sceKernel, "Kernel and user memory pools initialized");

View file

@ -15,10 +15,7 @@
// Official git repository and contact information can be found at
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
#include <thread>
#include "Core/Core.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/System/Request.h"
#include "Common/File/AndroidContentURI.h"
@ -49,8 +46,6 @@
#include "Core/PSPLoaders.h"
#include "Core/HLE/sceKernelModule.h"
static std::thread g_loadingThread;
static void UseLargeMem(int memsize) {
if (memsize != 1) {
// Nothing requested.
@ -252,7 +247,6 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) {
} else {
*error_string = "A PSP game couldn't be found on the disc.";
}
coreState = CORE_BOOT_ERROR;
return false;
}
@ -270,38 +264,11 @@ bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string) {
g_Config.loadGameConfig(id, g_paramSFO.GetValueString("TITLE"));
System_PostUIMessage(UIMessage::CONFIG_LOADED);
INFO_LOG(Log::Loader, "Loading %s...", bootpath.c_str());
PSPLoaders_Shutdown();
// Note: this thread reads the game binary, loads caches, and links HLE while UI spins.
// To do something deterministically when the game starts, disabling this thread won't be enough.
// Instead: Use Core_ListenLifecycle() or watch coreState.
g_loadingThread = std::thread([bootpath] {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
return;
AndroidJNIThreadContext jniContext;
INFO_LOG(Log::System, "Loading executable...");
// TODO: We can't use the initial error_string pointer.
bool success = __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString);
if (success && coreState == CORE_POWERUP) {
if (PSP_CoreParameter().startBreak) {
coreState = CORE_STEPPING_CPU;
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
} else {
coreState = CORE_RUNNING_CPU;
}
} else {
coreState = CORE_BOOT_ERROR;
// TODO: This is a crummy way to communicate the error...
PSP_CoreParameter().fileToStart.clear();
}
});
return true;
return __KernelLoadExec(bootpath.c_str(), 0, &PSP_CoreParameter().errorString);
}
// TODO: Move this to common. Merge with ResolvePath?
static Path NormalizePath(const Path &path) {
if (path.Type() != PathType::NATIVE) {
// Nothing to do - these can't be non-normalized.
@ -375,7 +342,6 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) {
// If root is not a subpath of path, we can't boot the game.
if (!pathNorm.StartsWith(rootNorm)) {
*error_string = "Cannot boot ELF located outside mountRoot.";
coreState = CORE_BOOT_ERROR;
return false;
}
@ -438,65 +404,12 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) {
File::Rename(oldNamePrefix.WithExtraExtension(".jpg"), newPrefix.WithExtraExtension(".jpg"));
}
PSPLoaders_Shutdown();
// Note: See Load_PSP_ISO for notes about this thread.
g_loadingThread = std::thread([finalName] {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
return;
AndroidJNIThreadContext jniContext;
bool success = __KernelLoadExec(finalName.c_str(), 0, &PSP_CoreParameter().errorString);
if (success && coreState == CORE_POWERUP) {
if (PSP_CoreParameter().startBreak) {
coreState = CORE_STEPPING_CPU;
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
} else {
coreState = CORE_RUNNING_CPU;
}
} else {
coreState = CORE_BOOT_ERROR;
// TODO: This is a crummy way to communicate the error...
PSP_CoreParameter().fileToStart.clear();
}
});
return true;
return __KernelLoadExec(finalName.c_str(), 0, error_string);
}
bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) {
auto umd = std::make_shared<BlobFileSystem>(&pspFileSystem, fileLoader, "data.ppdmp");
pspFileSystem.Mount("disc0:", umd);
PSPLoaders_Shutdown();
// Note: See Load_PSP_ISO for notes about this thread.
g_loadingThread = std::thread([] {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
return;
AndroidJNIThreadContext jniContext;
bool success = __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString);
if (success && coreState == CORE_POWERUP) {
if (PSP_CoreParameter().startBreak) {
coreState = CORE_STEPPING_CPU;
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
} else {
coreState = CORE_RUNNING_CPU;
}
} else {
coreState = CORE_BOOT_ERROR;
// TODO: This is a crummy way to communicate the error...
PSP_CoreParameter().fileToStart.clear();
}
});
return true;
}
void PSPLoaders_Shutdown() {
if (g_loadingThread.joinable())
g_loadingThread.join();
return __KernelLoadGEDump("disc0:/data.ppdmp", &PSP_CoreParameter().errorString);
}

View file

@ -966,10 +966,6 @@ void identify_and_load_callback(int result, const char *error_message, rc_client
g_isIdentifying = false;
}
bool IsReadyToStart() {
return !g_isLoggingIn;
}
void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoader) {
bool homebrew = false;
switch (fileType) {

View file

@ -96,7 +96,6 @@ bool HasAchievementsOrLeaderboards();
bool LoginAsync(const char *username, const char *password);
void Logout();
bool IsReadyToStart();
void SetGame(const Path &path, IdentifiedFileType fileType, FileLoader *fileLoader);
void ChangeUMD(const Path &path, FileLoader *fileLoader); // for in-game UMD change
void UnloadGame(); // Call when leaving a game.

View file

@ -40,6 +40,7 @@
#include "Common/File/DirListing.h"
#include "Common/File/AndroidContentURI.h"
#include "Common/TimeUtil.h"
#include "Common/Thread/ThreadUtil.h"
#include "Common/GraphicsContext.h"
#include "Core/RetroAchievements.h"
#include "Core/MemFault.h"
@ -83,6 +84,7 @@ CoreParameter g_CoreParameter;
static FileLoader *g_loadedFile;
// For background loading thread.
static std::mutex loadingLock;
static std::thread g_loadingThread;
bool coreCollectDebugStats = false;
static int coreCollectDebugStatsCounter = 0;
@ -218,7 +220,7 @@ static void GetBootError(IdentifiedFileType type, std::string *errorString) {
#ifdef WIN32
*errorString = "RAR file detected (Require WINRAR)";
#else
*error_string = "RAR file detected (Require UnRAR)";
*errorString = "RAR file detected (Require UnRAR)";
#endif
break;
@ -226,7 +228,7 @@ static void GetBootError(IdentifiedFileType type, std::string *errorString) {
#ifdef WIN32
*errorString = "ZIP file detected (Require WINRAR)";
#else
*error_string = "ZIP file detected (Require UnRAR)";
*errorString = "ZIP file detected (Require UnRAR)";
#endif
break;
@ -249,8 +251,6 @@ static void GetBootError(IdentifiedFileType type, std::string *errorString) {
// NOTE: The loader has already been fully resolved (ResolveFileLoaderTarget) and identified here.
static bool CPU_Init(FileLoader *fileLoader, IdentifiedFileType type, std::string *errorString) {
coreState = CORE_POWERUP;
// Default memory settings
// Seems to be the safest place currently..
Memory::g_MemorySize = Memory::RAM_NORMAL_SIZE; // 32 MB of ram by default
@ -416,10 +416,6 @@ PSP_LoadingLock::~PSP_LoadingLock() {
void CPU_Shutdown() {
UninstallExceptionHandler();
// Since we load on a background thread, wait for startup to complete.
PSP_LoadingLock lock;
PSPLoaders_Shutdown();
GPURecord::Replay_Unload();
if (g_Config.bAutoSaveSymbolMap) {
@ -441,15 +437,14 @@ void CPU_Shutdown() {
g_loadedFile = nullptr;
delete g_CoreParameter.mountIsoLoader;
g_CoreParameter.mountIsoLoader = nullptr;
delete g_symbolMap;
g_symbolMap = nullptr;
g_lua.Shutdown();
g_CoreParameter.mountIsoLoader = nullptr;
}
// TODO: Maybe loadedFile doesn't even belong here...
// Used for UMD switching only.
void UpdateLoadedFile(FileLoader *fileLoader) {
delete g_loadedFile;
g_loadedFile = fileLoader;
@ -477,20 +472,14 @@ void PSP_ForceDebugStats(bool enable) {
_assert_(coreCollectDebugStatsCounter >= 0);
}
bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
bool PSP_InitStart(const CoreParameter &coreParam) {
if (pspIsIniting || pspIsQuitting) {
ERROR_LOG(Log::System, "Can't start loader thread - initing or quitting");
return false;
}
if (!Achievements::IsReadyToStart()) {
return false;
}
coreState = CORE_POWERUP;
// TODO: Move almost all of this into the thread.
NOTICE_LOG(Log::Boot, "PPSSPP %s", PPSSPP_GIT_VERSION);
Core_NotifyLifecycle(CoreLifecycle::STARTING);
GraphicsContext *temp = g_CoreParameter.graphicsContext;
g_CoreParameter = coreParam;
if (g_CoreParameter.graphicsContext == nullptr) {
@ -499,6 +488,22 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
g_CoreParameter.errorString.clear();
pspIsIniting = true;
std::string *error_string = &g_CoreParameter.errorString;
INFO_LOG(Log::System, "Starting loader thread...");
g_loadingThread = std::thread([error_string]() {
SetCurrentThreadName("ExecLoader");
PSP_LoadingLock guard;
if (coreState != CORE_POWERUP)
return;
AndroidJNIThreadContext jniContext;
NOTICE_LOG(Log::Boot, "PPSSPP %s", PPSSPP_GIT_VERSION);
Core_NotifyLifecycle(CoreLifecycle::STARTING);
Path filename = g_CoreParameter.fileToStart;
FileLoader *loadedFile = ResolveFileLoaderTarget(ConstructFileLoader(filename));
@ -520,8 +525,6 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
}
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.
std::string errorString;
Achievements::SetGame(filename, type, loadedFile);
}
@ -530,16 +533,24 @@ bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string) {
// it gets written to from the loader thread that gets spawned.
if (!CPU_Init(loadedFile, type, &g_CoreParameter.errorString)) {
CPU_Shutdown();
coreState = CORE_BOOT_ERROR;
g_CoreParameter.fileToStart.clear();
*error_string = g_CoreParameter.errorString;
if (error_string->empty()) {
*error_string = "Failed initializing CPU/Memory";
}
pspIsIniting = false;
return false;
return;
}
// After CPU_Init returns, the loader thread will keep working for a bit, while we exit here and come back later through
// PSP_InitUpdate.
if (PSP_CoreParameter().startBreak) {
coreState = CORE_STEPPING_CPU;
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
} else {
coreState = CORE_RUNNING_CPU;
}
});
return true;
}
@ -557,6 +568,10 @@ bool PSP_InitUpdate(std::string *error_string) {
*error_string = g_CoreParameter.errorString;
}
// Since we load on a background thread, wait for startup to complete.
_dbg_assert_(g_loadingThread.joinable());
g_loadingThread.join();
if (success && gpu == nullptr) {
INFO_LOG(Log::System, "Starting graphics...");
Draw::DrawContext *draw = g_CoreParameter.graphicsContext ? g_CoreParameter.graphicsContext->GetDrawContext() : nullptr;
@ -591,7 +606,8 @@ bool PSP_InitUpdate(std::string *error_string) {
// Most platforms should not use this one, they should call PSP_InitStart and then do their thing
// while repeatedly calling PSP_InitUpdate. This is basically just for libretro convenience.
bool PSP_Init(const CoreParameter &coreParam, std::string *error_string) {
if (!PSP_InitStart(coreParam, error_string))
// InitStart doesn't really fail anymore.
if (!PSP_InitStart(coreParam))
return false;
while (!PSP_InitUpdate(error_string))

View file

@ -72,7 +72,7 @@ GPUBackend GetGPUBackend();
std::string GetGPUBackendDevice();
bool PSP_Init(const CoreParameter &coreParam, std::string *error_string);
bool PSP_InitStart(const CoreParameter &coreParam, std::string *error_string);
bool PSP_InitStart(const CoreParameter &coreParam);
bool PSP_InitUpdate(std::string *error_string);
bool PSP_IsIniting();
bool PSP_IsInited();

View file

@ -251,6 +251,8 @@ void __PPGeInit() {
int height[12]{};
int flags = 0;
// TODO: Load the atlas on a thread!
bool loadedZIM = !skipZIM && LoadZIM("ppge_atlas.zim", width, height, &flags, imageData);
if (!skipZIM && !loadedZIM) {
ERROR_LOG(Log::sceGe, "Failed to load ppge_atlas.zim.\n\nPlace it in the directory \"assets\" under your PPSSPP directory.\n\nPPGe stuff will not be drawn.");

View file

@ -355,10 +355,9 @@ void EmuScreen::bootGame(const Path &filename) {
coreParam.pixelWidth = g_display.pixel_xres;
coreParam.pixelHeight = g_display.pixel_yres;
std::string error_string;
if (!PSP_InitStart(coreParam, &error_string)) {
// PSP_InitStart can't really fail anymore, unless it's called at the wrong time. It just starts the loader thread.
if (!PSP_InitStart(coreParam)) {
bootPending_ = false;
errorMessage_ = error_string;
ERROR_LOG(Log::Boot, "InitStart bootGame error: %s", errorMessage_.c_str());
}
@ -552,10 +551,8 @@ void EmuScreen::sendMessage(UIMessage message, const char *value) {
PSP_Shutdown();
bootPending_ = true;
System_Notify(SystemNotification::DISASSEMBLY);
std::string resetError;
if (!PSP_InitStart(PSP_CoreParameter(), &resetError)) {
ERROR_LOG(Log::Loader, "Error resetting: %s", resetError.c_str());
if (!PSP_InitStart(PSP_CoreParameter())) {
ERROR_LOG(Log::Loader, "Error resetting");
stopRender_ = true;
screenManager()->switchScreen(new MainScreen());
return;

View file

@ -184,9 +184,9 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
if (opt.compare || opt.bench)
coreParameter.collectDebugOutput = &output;
std::string error_string;
if (!PSP_InitStart(coreParameter, &error_string)) {
fprintf(stderr, "Failed to start '%s'. Error: %s\n", coreParameter.fileToStart.c_str(), error_string.c_str());
if (!PSP_InitStart(coreParameter)) {
// Shouldn't really happen anymore, the errors happen later in PSP_InitUpdate.
fprintf(stderr, "Failed to start '%s'.\n", coreParameter.fileToStart.c_str());
printf("TESTERROR\n");
TeamCityPrint("testIgnored name='%s' message='PRX/ELF missing'", currentTestName.c_str());
GitHubActionsPrint("error", "PRX/ELF missing for %s", currentTestName.c_str());
@ -198,9 +198,12 @@ bool RunAutoTest(HeadlessHost *headlessHost, CoreParameter &coreParameter, const
if (opt.compare)
headlessHost->SetComparisonScreenshot(ExpectedScreenshotFromFilename(coreParameter.fileToStart), opt.maxScreenshotError);
std::string error_string;
while (!PSP_InitUpdate(&error_string))
sleep_ms(1, "auto-test");
if (!PSP_IsInited()) {
TeamCityPrint("%s", error_string.c_str());
TeamCityPrint("testFailed name='%s' message='Startup failed'", currentTestName.c_str());
TeamCityPrint("testFinished name='%s'", currentTestName.c_str());
GitHubActionsPrint("error", "Test init failed for %s", currentTestName.c_str());