From 3f20b32041606fb87dd75f13604ebafb81872061 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Thu, 25 Dec 2014 19:02:49 -0800 Subject: [PATCH 1/6] Add an option to prevent cpu stall on slow IO. This is good for any slow storage, including: * Hard disk spinning up. * Generally slow (cheap) SD cards. * HTTP or Samba streaming. May possibly cause bugs in some cases where timing is unrealistic. That being said, as long as the game is a UMD game, and there's caching (could enable memory caching for storage), it should not be a problem usually. --- Core/Config.cpp | 1 + Core/Config.h | 8 +++++++ Core/HLE/sceIo.cpp | 47 ++++++++++++++++++++++++-------------- Core/HW/AsyncIOManager.cpp | 5 ++++ Core/HW/AsyncIOManager.h | 1 + UI/GameSettingsScreen.cpp | 3 +++ 6 files changed, 48 insertions(+), 17 deletions(-) diff --git a/Core/Config.cpp b/Core/Config.cpp index 128ab7179f..31cbb554df 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -337,6 +337,7 @@ static ConfigSetting cpuSettings[] = { ConfigSetting("AtomicAudioLocks", &g_Config.bAtomicAudioLocks, false, true, true), ReportedConfigSetting("SeparateIOThread", &g_Config.bSeparateIOThread, true, true, true), + ReportedConfigSetting("IOTimingMethod", &g_Config.iIOTimingMethod, IOTIMING_FAST, true, true), ConfigSetting("FastMemoryAccess", &g_Config.bFastMemory, true, true, true), ReportedConfigSetting("FuncReplacements", &g_Config.bFuncReplacements, true, true, true), ReportedConfigSetting("CPUSpeed", &g_Config.iLockedCPUSpeed, 0, true, true), diff --git a/Core/Config.h b/Core/Config.h index 7aebc374d3..46779cef23 100644 --- a/Core/Config.h +++ b/Core/Config.h @@ -50,6 +50,13 @@ enum { GPU_BACKEND_DIRECT3D9 = 1, }; +// For iIOTimingMethod. +enum IOTimingMethods { + IOTIMING_FAST = 0, + IOTIMING_HOST = 1, + IOTIMING_REALISTIC = 2, +}; + namespace http { class Download; class Downloader; @@ -109,6 +116,7 @@ public: // Definitely cannot be changed while game is running. bool bSeparateCPUThread; + int iIOTimingMethod; bool bSeparateIOThread; bool bAtomicAudioLocks; int iLockedCPUSpeed; diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 6b2d4cee80..7dc150342b 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -240,7 +240,7 @@ public: /******************************************************************************/ -void __IoCompleteAsyncIO(int fd); +void __IoCompleteAsyncIO(FileNode *f); static void TellFsThreadEnded (SceUID threadID) { pspFileSystem.ThreadEnded(threadID); @@ -305,7 +305,6 @@ static void __IoFreeFd(int fd, u32 &error) { // For now, let's at least delay the callback mnotification. static void __IoAsyncNotify(u64 userdata, int cyclesLate) { int fd = (int) userdata; - __IoCompleteAsyncIO(fd); u32 error; FileNode *f = __IoGetFd(fd, error); @@ -314,6 +313,16 @@ static void __IoAsyncNotify(u64 userdata, int cyclesLate) { return; } + if (g_Config.iIOTimingMethod == IOTIMING_HOST) { + if (!ioManager.HasResult(f->handle)) { + // Try again in another 0.5ms until the IO completes on the host. + CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, asyncNotifyEvent, userdata); + return; + } + } + + __IoCompleteAsyncIO(f); + if (f->waitingThreads.empty()) { return; } @@ -350,6 +359,14 @@ static void __IoSyncNotify(u64 userdata, int cyclesLate) { return; } + if (g_Config.iIOTimingMethod == IOTIMING_HOST) { + if (!ioManager.HasResult(f->handle)) { + // Try again in another 0.5ms until the IO completes on the host. + CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, syncNotifyEvent, userdata); + return; + } + } + f->pendingAsyncResult = false; f->hasAsyncResult = false; @@ -567,22 +584,18 @@ static u32 sceKernelStderr() { return PSP_STDERR; } -void __IoCompleteAsyncIO(int fd) { - u32 error; - FileNode *f = __IoGetFd(fd, error); - if (f) { - AsyncIOResult managerResult; - if (ioManager.WaitResult(f->handle, managerResult)) { - f->asyncResult = managerResult; - } else { - // It's okay, not all operations are deferred. - } - if (f->callbackID) { - __KernelNotifyCallback(f->callbackID, f->callbackArg); - } - f->pendingAsyncResult = false; - f->hasAsyncResult = true; +void __IoCompleteAsyncIO(FileNode *f) { + AsyncIOResult managerResult; + if (ioManager.WaitResult(f->handle, managerResult)) { + f->asyncResult = managerResult; + } else { + // It's okay, not all operations are deferred. } + if (f->callbackID) { + __KernelNotifyCallback(f->callbackID, f->callbackArg); + } + f->pendingAsyncResult = false; + f->hasAsyncResult = true; } void __IoCopyDate(ScePspDateTime& date_out, const tm& date_in) diff --git a/Core/HW/AsyncIOManager.cpp b/Core/HW/AsyncIOManager.cpp index c022f16404..d3ff681ba5 100644 --- a/Core/HW/AsyncIOManager.cpp +++ b/Core/HW/AsyncIOManager.cpp @@ -47,6 +47,11 @@ void AsyncIOManager::Shutdown() { results_.clear(); } +bool AsyncIOManager::HasResult(u32 handle) { + lock_guard guard(resultsLock_); + return results_.find(handle) != results_.end(); +} + bool AsyncIOManager::PopResult(u32 handle, AsyncIOResult &result) { lock_guard guard(resultsLock_); if (results_.find(handle) != results_.end()) { diff --git a/Core/HW/AsyncIOManager.h b/Core/HW/AsyncIOManager.h index 76d6f5e7f9..643814236a 100644 --- a/Core/HW/AsyncIOManager.h +++ b/Core/HW/AsyncIOManager.h @@ -55,6 +55,7 @@ public: void ScheduleOperation(AsyncIOEvent ev); void Shutdown(); + bool HasResult(u32 handle); bool PopResult(u32 handle, AsyncIOResult &result); bool WaitResult(u32 handle, AsyncIOResult &result); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index b134433370..b26ba3a0ae 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -392,6 +392,9 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new CheckBox(&g_Config.bSeparateCPUThread, s->T("Multithreaded (experimental)"))); systemSettings->Add(new CheckBox(&g_Config.bSeparateIOThread, s->T("I/O on thread (experimental)")))->SetEnabled(!PSP_IsInited()); + static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)" }; + View *ioTimingMethod = systemSettings->Add(new PopupMultiChoice(&g_Config.iIOTimingMethod, s->T("IO timing method"), ioTimingMethods, 0, ARRAY_SIZE(ioTimingMethods), s, screenManager())); + ioTimingMethod->SetEnabledPtr(&g_Config.bSeparateIOThread); systemSettings->Add(new CheckBox(&g_Config.bForceLagSync, s->T("Force real clock sync (slower, less lag)"))); systemSettings->Add(new PopupSliderChoice(&g_Config.iLockedCPUSpeed, 0, 1000, s->T("Change CPU Clock", "Change CPU Clock (0 = default) (unstable)"), screenManager())); #ifndef MOBILE_DEVICE From 0599c04c1cea5167f21a81f62bff8ce211a8ab71 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Sat, 13 Dec 2014 20:12:37 -0800 Subject: [PATCH 2/6] Properly unschedule IO events on file close. Oops, was waking threads but not bumping events. --- Core/HLE/sceIo.cpp | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 7dc150342b..167b3b8946 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -173,7 +173,7 @@ public: } void DoState(PointerWrap &p) override { - auto s = p.Section("FileNode", 1); + auto s = p.Section("FileNode", 2); if (!s) return; @@ -204,6 +204,9 @@ public: } p.Do(waitingThreads); + if (s >= 2) { + p.Do(waitingSyncThreads); + } p.Do(pausedWaits); } @@ -229,6 +232,7 @@ public: PGD_DESC *pgdInfo; std::vector waitingThreads; + std::vector waitingSyncThreads; // Key is the callback id it was for, or if no callback, the thread id. // Value is actually meaningless but kept for consistency with other wait types. std::map pausedWaits; @@ -279,6 +283,11 @@ static void __IoFreeFd(int fd, u32 &error) { HLEKernel::ResumeFromWait(f->waitingThreads[i], WAITTYPE_ASYNCIO, f->GetUID(), (int)SCE_KERNEL_ERROR_WAIT_DELETE); } + CoreTiming::UnscheduleEvent(asyncNotifyEvent, fd); + for (size_t i = 0; i < f->waitingSyncThreads.size(); ++i) { + CoreTiming::UnscheduleEvent(syncNotifyEvent, ((u64)f->waitingSyncThreads[i] << 32) | fd); + } + // Discard any pending results. AsyncIOResult managerResult; ioManager.WaitResult(f->handle, managerResult); @@ -378,6 +387,7 @@ static void __IoSyncNotify(u64 userdata, int cyclesLate) { } HLEKernel::ResumeFromWait(threadID, WAITTYPE_IO, fd, result); + f->waitingSyncThreads.erase(std::remove(f->waitingSyncThreads.begin(), f->waitingSyncThreads.end(), threadID), f->waitingSyncThreads.end()); } static void __IoAsyncBeginCallback(SceUID threadID, SceUID prevCallbackId) { @@ -812,6 +822,7 @@ static u32 sceIoRead(int id, u32 data_addr, int size) { __IoSchedSync(f, id, us); __KernelWaitCurThread(WAITTYPE_IO, id, 0, 0, false, "io read"); + f->waitingSyncThreads.push_back(__KernelGetCurThread()); return 0; } else if (result >= 0) { DEBUG_LOG(SCEIO, "%x=sceIoRead(%d, %08x, %x)", result, id, data_addr, size); @@ -933,6 +944,7 @@ static u32 sceIoWrite(int id, u32 data_addr, int size) { __IoSchedSync(f, id, us); __KernelWaitCurThread(WAITTYPE_IO, id, 0, 0, false, "io write"); + f->waitingSyncThreads.push_back(__KernelGetCurThread()); return 0; } else if (result >= 0) { DEBUG_LOG(SCEIO, "%x=sceIoWrite(%d, %08x, %x)", result, id, data_addr, size); From 0abd95e7840e58017ab1d3696312ba7273511ff9 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 27 Dec 2013 21:38:20 -0800 Subject: [PATCH 3/6] Typo. --- Core/HLE/sceIo.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 167b3b8946..811c246279 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -1391,7 +1391,7 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o return ERROR_MEMSTICK_DEVCTL_BAD_PARAMS; } break; - // TODO: What does these do? Seem to require a u32 in, no output. + // TODO: What do these do? Seem to require a u32 in, no output. case 0x01F100A6: case 0x01F100A8: case 0x01F100A9: From d6a113809b4fa8816c26b161033f1bd83088a280 Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 27 Dec 2013 16:36:51 -0800 Subject: [PATCH 4/6] Add basic infrastructure to time io better. This allows the filesystems to more accurately control timing. But they're not actually doing it yet (same timing as before.) --- Core/FileSystems/DirectoryFileSystem.cpp | 20 +++++ Core/FileSystems/DirectoryFileSystem.h | 4 + Core/FileSystems/FileSystem.h | 4 + Core/FileSystems/ISOFileSystem.cpp | 14 +++- Core/FileSystems/ISOFileSystem.h | 11 ++- Core/FileSystems/MetaFileSystem.cpp | 24 +++++- Core/FileSystems/MetaFileSystem.h | 2 + Core/FileSystems/VirtualDiscFileSystem.cpp | 11 +++ Core/FileSystems/VirtualDiscFileSystem.h | 2 + Core/HLE/sceIo.cpp | 91 +++++++++++++--------- Core/HW/AsyncIOManager.cpp | 51 ++++++++++-- Core/HW/AsyncIOManager.h | 27 ++++++- 12 files changed, 210 insertions(+), 51 deletions(-) diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index 3bf4328b6d..292484296e 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -600,6 +600,11 @@ int DirectoryFileSystem::DevType(u32 handle) { } size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) { + int ignored; + return ReadFile(handle, pointer, size, ignored); +} + +size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) { EntryMap::iterator iter = entries.find(handle); if (iter != entries.end()) { @@ -613,6 +618,11 @@ size_t DirectoryFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) { } size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) { + int ignored; + return WriteFile(handle, pointer, size, ignored); +} + +size_t DirectoryFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) { EntryMap::iterator iter = entries.find(handle); if (iter != entries.end()) { @@ -971,6 +981,11 @@ int VFSFileSystem::DevType(u32 handle) { } size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) { + int ignored; + return ReadFile(handle, pointer, size, ignored); +} + +size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) { DEBUG_LOG(FILESYS,"VFSFileSystem::ReadFile %08x %p %i", handle, pointer, (u32)size); EntryMap::iterator iter = entries.find(handle); if (iter != entries.end()) @@ -986,6 +1001,11 @@ size_t VFSFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) { } size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) { + int ignored; + return WriteFile(handle, pointer, size, ignored); +} + +size_t VFSFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) { // NOT SUPPORTED - READ ONLY return 0; } diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index 78d06d9d72..e13cf5a733 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -96,7 +96,9 @@ public: u32 OpenFile(std::string filename, FileAccess access, const char *devicename=NULL) override; void CloseFile(u32 handle) override; size_t ReadFile(u32 handle, u8 *pointer, s64 size) override; + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) override; size_t WriteFile(u32 handle, const u8 *pointer, s64 size) override; + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) override; size_t SeekFile(u32 handle, s32 position, FileMove type) override; PSPFileInfo GetFileInfo(std::string filename) override; bool OwnsHandle(u32 handle) override; @@ -139,7 +141,9 @@ public: u32 OpenFile(std::string filename, FileAccess access, const char *devicename=NULL) override; void CloseFile(u32 handle) override; size_t ReadFile(u32 handle, u8 *pointer, s64 size) override; + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) override; size_t WriteFile(u32 handle, const u8 *pointer, s64 size) override; + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) override; size_t SeekFile(u32 handle, s32 position, FileMove type) override; PSPFileInfo GetFileInfo(std::string filename) override; bool OwnsHandle(u32 handle) override; diff --git a/Core/FileSystems/FileSystem.h b/Core/FileSystems/FileSystem.h index 4f402b5a6a..c9f6b5290a 100644 --- a/Core/FileSystems/FileSystem.h +++ b/Core/FileSystems/FileSystem.h @@ -111,7 +111,9 @@ public: virtual u32 OpenFile(std::string filename, FileAccess access, const char *devicename=NULL) = 0; virtual void CloseFile(u32 handle) = 0; virtual size_t ReadFile(u32 handle, u8 *pointer, s64 size) = 0; + virtual size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) = 0; virtual size_t WriteFile(u32 handle, const u8 *pointer, s64 size) = 0; + virtual size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) = 0; virtual size_t SeekFile(u32 handle, s32 position, FileMove type) = 0; virtual PSPFileInfo GetFileInfo(std::string filename) = 0; virtual bool OwnsHandle(u32 handle) = 0; @@ -135,7 +137,9 @@ public: u32 OpenFile(std::string filename, FileAccess access, const char *devicename=NULL) {return 0;} void CloseFile(u32 handle) {} size_t ReadFile(u32 handle, u8 *pointer, s64 size) {return 0;} + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) {return 0;} size_t WriteFile(u32 handle, const u8 *pointer, s64 size) {return 0;} + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) {return 0;} size_t SeekFile(u32 handle, s32 position, FileMove type) {return 0;} PSPFileInfo GetFileInfo(std::string filename) {PSPFileInfo f; return f;} bool OwnsHandle(u32 handle) {return false;} diff --git a/Core/FileSystems/ISOFileSystem.cpp b/Core/FileSystems/ISOFileSystem.cpp index f11ff1c7cb..01f57ce681 100644 --- a/Core/FileSystems/ISOFileSystem.cpp +++ b/Core/FileSystems/ISOFileSystem.cpp @@ -519,6 +519,12 @@ int ISOFileSystem::DevType(u32 handle) } size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) +{ + int ignored; + return ReadFile(handle, pointer, size, ignored); +} + +size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) { EntryMap::iterator iter = entries.find(handle); if (iter != entries.end()) @@ -599,7 +605,13 @@ size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) } } -size_t ISOFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) +size_t ISOFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) +{ + ERROR_LOG(FILESYS, "Hey, what are you doing? You can't write to an ISO!"); + return 0; +} + +size_t ISOFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) { ERROR_LOG(FILESYS, "Hey, what are you doing? You can't write to an ISO!"); return 0; diff --git a/Core/FileSystems/ISOFileSystem.h b/Core/FileSystems/ISOFileSystem.h index 084546f1a2..9f074800b2 100644 --- a/Core/FileSystems/ISOFileSystem.h +++ b/Core/FileSystems/ISOFileSystem.h @@ -37,6 +37,7 @@ public: u32 OpenFile(std::string filename, FileAccess access, const char *devicename = NULL) override; void CloseFile(u32 handle) override; size_t ReadFile(u32 handle, u8 *pointer, s64 size) override; + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) override; size_t SeekFile(u32 handle, s32 position, FileMove type) override; PSPFileInfo GetFileInfo(std::string filename) override; bool OwnsHandle(u32 handle) override; @@ -46,6 +47,8 @@ public: u64 FreeSpace(const std::string &path) override { return 0; } size_t WriteFile(u32 handle, const u8 *pointer, s64 size) override; + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) override; + bool GetHostPath(const std::string &inpath, std::string &outpath) {return false;} bool MkDir(const std::string &dirname) override {return false;} bool RmDir(const std::string &dirname) override { return false; } @@ -116,6 +119,9 @@ public: size_t ReadFile(u32 handle, u8 *pointer, s64 size) override { return isoFileSystem_->ReadFile(handle, pointer, size); } + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) override { + return isoFileSystem_->ReadFile(handle, pointer, size, usec); + } size_t SeekFile(u32 handle, s32 position, FileMove type) override { return isoFileSystem_->SeekFile(handle, position, type); } @@ -137,6 +143,9 @@ public: size_t WriteFile(u32 handle, const u8 *pointer, s64 size) override { return isoFileSystem_->WriteFile(handle, pointer, size); } + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) override { + return isoFileSystem_->WriteFile(handle, pointer, size, usec); + } bool GetHostPath(const std::string &inpath, std::string &outpath) { return false; } bool MkDir(const std::string &dirname) override { return false; } bool RmDir(const std::string &dirname) override { return false; } @@ -145,4 +154,4 @@ public: private: ISOFileSystem *isoFileSystem_; -}; \ No newline at end of file +}; diff --git a/Core/FileSystems/MetaFileSystem.cpp b/Core/FileSystems/MetaFileSystem.cpp index cc403b64ef..d2f8df140d 100644 --- a/Core/FileSystems/MetaFileSystem.cpp +++ b/Core/FileSystems/MetaFileSystem.cpp @@ -555,7 +555,7 @@ size_t MetaFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) lock_guard guard(lock); IFileSystem *sys = GetHandleOwner(handle); if (sys) - return sys->ReadFile(handle,pointer,size); + return sys->ReadFile(handle, pointer, size); else return 0; } @@ -565,7 +565,27 @@ size_t MetaFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) lock_guard guard(lock); IFileSystem *sys = GetHandleOwner(handle); if (sys) - return sys->WriteFile(handle,pointer,size); + return sys->WriteFile(handle, pointer, size); + else + return 0; +} + +size_t MetaFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) +{ + lock_guard guard(lock); + IFileSystem *sys = GetHandleOwner(handle); + if (sys) + return sys->ReadFile(handle, pointer, size, usec); + else + return 0; +} + +size_t MetaFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) +{ + lock_guard guard(lock); + IFileSystem *sys = GetHandleOwner(handle); + if (sys) + return sys->WriteFile(handle, pointer, size, usec); else return 0; } diff --git a/Core/FileSystems/MetaFileSystem.h b/Core/FileSystems/MetaFileSystem.h index e1f99e21c0..5990abded4 100644 --- a/Core/FileSystems/MetaFileSystem.h +++ b/Core/FileSystems/MetaFileSystem.h @@ -96,7 +96,9 @@ public: u32 OpenWithError(int &error, std::string filename, FileAccess access, const char *devicename = NULL); void CloseFile(u32 handle) override; size_t ReadFile(u32 handle, u8 *pointer, s64 size) override; + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) override; size_t WriteFile(u32 handle, const u8 *pointer, s64 size) override; + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) override; size_t SeekFile(u32 handle, s32 position, FileMove type) override; PSPFileInfo GetFileInfo(std::string filename) override; bool OwnsHandle(u32 handle) override { return false; } diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index f94f370474..8037a103b2 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -422,6 +422,11 @@ size_t VirtualDiscFileSystem::SeekFile(u32 handle, s32 position, FileMove type) } size_t VirtualDiscFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) { + int ignored; + return ReadFile(handle, pointer, size, ignored); +} + +size_t VirtualDiscFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) { EntryMap::iterator iter = entries.find(handle); if (iter != entries.end()) { @@ -701,6 +706,12 @@ size_t VirtualDiscFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) return 0; } +size_t VirtualDiscFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) +{ + ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot write to file on virtual disc"); + return 0; +} + bool VirtualDiscFileSystem::MkDir(const std::string &dirname) { ERROR_LOG(FILESYS,"VirtualDiscFileSystem: Cannot create directory on virtual disc"); diff --git a/Core/FileSystems/VirtualDiscFileSystem.h b/Core/FileSystems/VirtualDiscFileSystem.h index fca50d73cf..6348d51906 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.h +++ b/Core/FileSystems/VirtualDiscFileSystem.h @@ -33,6 +33,7 @@ public: u32 OpenFile(std::string filename, FileAccess access, const char *devicename=NULL) override; size_t SeekFile(u32 handle, s32 position, FileMove type) override; size_t ReadFile(u32 handle, u8 *pointer, s64 size) override; + size_t ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) override; void CloseFile(u32 handle) override; PSPFileInfo GetFileInfo(std::string filename) override; bool OwnsHandle(u32 handle) override; @@ -45,6 +46,7 @@ public: // unsupported operations size_t WriteFile(u32 handle, const u8 *pointer, s64 size) override; + size_t WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) override; bool MkDir(const std::string &dirname) override; bool RmDir(const std::string &dirname) override; int RenameFile(const std::string &from, const std::string &to) override; diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 811c246279..cb9ebf8706 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -244,7 +244,7 @@ public: /******************************************************************************/ -void __IoCompleteAsyncIO(FileNode *f); +u64 __IoCompleteAsyncIO(FileNode *f); static void TellFsThreadEnded (SceUID threadID) { pspFileSystem.ThreadEnded(threadID); @@ -311,7 +311,7 @@ static void __IoFreeFd(int fd, u32 &error) { // Clearly a buffer is used, it doesn't seem like they are actually kernel objects. // TODO: We don't do any of that yet. -// For now, let's at least delay the callback mnotification. +// For now, let's at least delay the callback notification. static void __IoAsyncNotify(u64 userdata, int cyclesLate) { int fd = (int) userdata; @@ -330,7 +330,12 @@ static void __IoAsyncNotify(u64 userdata, int cyclesLate) { } } - __IoCompleteAsyncIO(f); + u64 finishTicks = __IoCompleteAsyncIO(f); + if (finishTicks > CoreTiming::GetTicks()) { + // Reschedule for later, since we now know how long it ought to take. + CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), asyncNotifyEvent, userdata); + return; + } if (f->waitingThreads.empty()) { return; @@ -376,16 +381,26 @@ static void __IoSyncNotify(u64 userdata, int cyclesLate) { } } + u64 finishTicks = ioManager.ResultFinishTicks(f->handle); + if (finishTicks > CoreTiming::GetTicks()) { + // Reschedule for later when the result should finish. + CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), syncNotifyEvent, userdata); + return; + } + f->pendingAsyncResult = false; f->hasAsyncResult = false; AsyncIOResult managerResult; if (ioManager.WaitResult(f->handle, managerResult)) { - result = managerResult; + result = managerResult.result; } else { ERROR_LOG(SCEIO, "Unable to complete IO operation on %s", f->GetName()); } + f->pendingAsyncResult = false; + f->hasAsyncResult = false; + HLEKernel::ResumeFromWait(threadID, WAITTYPE_IO, fd, result); f->waitingSyncThreads.erase(std::remove(f->waitingSyncThreads.begin(), f->waitingSyncThreads.end(), threadID), f->waitingSyncThreads.end()); } @@ -594,10 +609,14 @@ static u32 sceKernelStderr() { return PSP_STDERR; } -void __IoCompleteAsyncIO(FileNode *f) { +u64 __IoCompleteAsyncIO(FileNode *f) { + u64 finishTicks = ioManager.ResultFinishTicks(f->handle); + if (finishTicks > CoreTiming::GetTicks()) { + return finishTicks; + } AsyncIOResult managerResult; if (ioManager.WaitResult(f->handle, managerResult)) { - f->asyncResult = managerResult; + f->asyncResult = managerResult.result; } else { // It's okay, not all operations are deferred. } @@ -606,6 +625,8 @@ void __IoCompleteAsyncIO(FileNode *f) { } f->pendingAsyncResult = false; f->hasAsyncResult = true; + + return 0; } void __IoCopyDate(ScePspDateTime& date_out, const tm& date_in) @@ -732,7 +753,13 @@ static u32 npdrmRead(FileNode *f, u8 *data, int size) { return size; } -static bool __IoRead(int &result, int id, u32 data_addr, int size) { +static bool __IoRead(int &result, int id, u32 data_addr, int size, int &us) { + // Low estimate, may be improved later from the ReadFile result. + us = size / 100; + if (us < 100) { + us = 100; + } + if (id == PSP_STDIN) { DEBUG_LOG(SCEIO, "sceIoRead STDIN"); result = 0; //stdin @@ -776,7 +803,7 @@ static bool __IoRead(int &result, int id, u32 data_addr, int size) { ioManager.ScheduleOperation(ev); return false; } else { - result = (int) pspFileSystem.ReadFile(f->handle, data, size); + result = (int) pspFileSystem.ReadFile(f->handle, data, size, us); return true; } } else { @@ -809,14 +836,9 @@ static u32 sceIoRead(int id, u32 data_addr, int size) { } } - // TODO: Timing is probably not very accurate, low estimate. - int us = size / 100; - if (us < 100) { - us = 100; - } - int result; - bool complete = __IoRead(result, id, data_addr, size); + int us; + bool complete = __IoRead(result, id, data_addr, size, us); if (!complete) { DEBUG_LOG(SCEIO, "sceIoRead(%d, %08x, %x): deferring result", id, data_addr, size); @@ -834,12 +856,7 @@ static u32 sceIoRead(int id, u32 data_addr, int size) { } static u32 sceIoReadAsync(int id, u32 data_addr, int size) { - // TODO: Not sure what the correct delay is (and technically we shouldn't read into the buffer yet...) - int us = size / 100; - if (us < 100) { - us = 100; - } - + // TODO: Technically we shouldn't read into the buffer yet... u32 error; FileNode *f = __IoGetFd(id, error); if (f) { @@ -848,7 +865,8 @@ static u32 sceIoReadAsync(int id, u32 data_addr, int size) { return SCE_KERNEL_ERROR_ASYNC_BUSY; } int result; - bool complete = __IoRead(result, id, data_addr, size); + int us; + bool complete = __IoRead(result, id, data_addr, size, us); if (complete) { f->asyncResult = result; DEBUG_LOG(SCEIO, "%llx=sceIoReadAsync(%d, %08x, %x)", f->asyncResult, id, data_addr, size); @@ -863,7 +881,13 @@ static u32 sceIoReadAsync(int id, u32 data_addr, int size) { } } -static bool __IoWrite(int &result, int id, u32 data_addr, int size) { +static bool __IoWrite(int &result, int id, u32 data_addr, int size, int &us) { + // Low estimate, may be improved later from the WriteFile result. + us = size / 100; + if (us < 100) { + us = 100; + } + const void *data_ptr = Memory::GetPointer(data_addr); // Let's handle stdout/stderr specially. if (id == PSP_STDOUT || id == PSP_STDERR) { @@ -907,7 +931,7 @@ static bool __IoWrite(int &result, int id, u32 data_addr, int size) { ioManager.ScheduleOperation(ev); return false; } else { - result = (int) pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, size); + result = (int) pspFileSystem.WriteFile(f->handle, (u8 *) data_ptr, size, us); } return true; } else { @@ -931,14 +955,9 @@ static u32 sceIoWrite(int id, u32 data_addr, int size) { } } - // TODO: Timing is probably not very accurate, low estimate. - int us = size / 100; - if (us < 100) { - us = 100; - } - int result; - bool complete = __IoWrite(result, id, data_addr, size); + int us; + bool complete = __IoWrite(result, id, data_addr, size, us); if (!complete) { DEBUG_LOG(SCEIO, "sceIoWrite(%d, %08x, %x): deferring result", id, data_addr, size); @@ -965,12 +984,7 @@ static u32 sceIoWrite(int id, u32 data_addr, int size) { } static u32 sceIoWriteAsync(int id, u32 data_addr, int size) { - // TODO: Not sure what the correct delay is (and technically we shouldn't read from the buffer yet...) - int us = size / 100; - if (us < 100) { - us = 100; - } - + // TODO: Technically we shouldn't read from the buffer yet... u32 error; FileNode *f = __IoGetFd(id, error); if (f) { @@ -979,7 +993,8 @@ static u32 sceIoWriteAsync(int id, u32 data_addr, int size) { return SCE_KERNEL_ERROR_ASYNC_BUSY; } int result; - bool complete = __IoWrite(result, id, data_addr, size); + int us; + bool complete = __IoWrite(result, id, data_addr, size, us); if (complete) { f->asyncResult = result; DEBUG_LOG(SCEIO, "%llx=sceIoWriteAsync(%d, %08x, %x)", f->asyncResult, id, data_addr, size); diff --git a/Core/HW/AsyncIOManager.cpp b/Core/HW/AsyncIOManager.cpp index d3ff681ba5..e4bbde36ca 100644 --- a/Core/HW/AsyncIOManager.cpp +++ b/Core/HW/AsyncIOManager.cpp @@ -64,6 +64,16 @@ bool AsyncIOManager::PopResult(u32 handle, AsyncIOResult &result) { } } +bool AsyncIOManager::ReadResult(u32 handle, AsyncIOResult &result) { + lock_guard guard(resultsLock_); + if (results_.find(handle) != results_.end()) { + result = results_[handle]; + return true; + } else { + return false; + } +} + bool AsyncIOManager::WaitResult(u32 handle, AsyncIOResult &result) { lock_guard guard(resultsLock_); ScheduleEvent(IO_EVENT_SYNC); @@ -80,6 +90,24 @@ bool AsyncIOManager::WaitResult(u32 handle, AsyncIOResult &result) { return false; } +u64 AsyncIOManager::ResultFinishTicks(u32 handle) { + AsyncIOResult result; + + lock_guard guard(resultsLock_); + ScheduleEvent(IO_EVENT_SYNC); + while (HasEvents() && ThreadEnabled() && resultsPending_.find(handle) != resultsPending_.end()) { + if (ReadResult(handle, result)) { + return result.finishTicks; + } + resultsWait_.wait_for(resultsLock_, 16); + } + if (ReadResult(handle, result)) { + return result.finishTicks; + } + + return 0; +} + void AsyncIOManager::ProcessEvent(AsyncIOEvent ev) { switch (ev.type) { case IO_EVENT_READ: @@ -96,14 +124,15 @@ void AsyncIOManager::ProcessEvent(AsyncIOEvent ev) { } void AsyncIOManager::Read(u32 handle, u8 *buf, size_t bytes) { - size_t result = pspFileSystem.ReadFile(handle, buf, bytes); - EventResult(handle, result); + int usec = 0; + s64 result = pspFileSystem.ReadFile(handle, buf, bytes, usec); + EventResult(handle, AsyncIOResult(result, usec)); } void AsyncIOManager::Write(u32 handle, u8 *buf, size_t bytes) { - // We want to sign extend this on 32-bit. - AsyncIOResult result = (ssize_t)pspFileSystem.WriteFile(handle, buf, bytes); - EventResult(handle, result); + int usec = 0; + s64 result = pspFileSystem.WriteFile(handle, buf, bytes, usec); + EventResult(handle, AsyncIOResult(result, usec)); } void AsyncIOManager::EventResult(u32 handle, AsyncIOResult result) { @@ -116,12 +145,20 @@ void AsyncIOManager::EventResult(u32 handle, AsyncIOResult result) { } void AsyncIOManager::DoState(PointerWrap &p) { - auto s = p.Section("AsyncIoManager", 1); + auto s = p.Section("AsyncIoManager", 1, 2); if (!s) return; SyncThread(); lock_guard guard(resultsLock_); p.Do(resultsPending_); - p.Do(results_); + if (s >= 2) { + p.Do(results_); + } else { + std::map oldResults; + p.Do(oldResults); + for (auto it = oldResults.begin(), end = oldResults.end(); it != end; ++it) { + results_[it->first] = AsyncIOResult(it->second); + } + } } diff --git a/Core/HW/AsyncIOManager.h b/Core/HW/AsyncIOManager.h index 643814236a..c2ebb68166 100644 --- a/Core/HW/AsyncIOManager.h +++ b/Core/HW/AsyncIOManager.h @@ -43,8 +43,29 @@ struct AsyncIOEvent { } }; -// TODO: Something better. -typedef s64 AsyncIOResult; +struct AsyncIOResult { + AsyncIOResult() : result(0), finishTicks(0) { + } + + explicit AsyncIOResult(s64 r) : result(r), finishTicks(0) { + } + + AsyncIOResult(s64 r, int usec) : result(r) { + finishTicks = CoreTiming::GetTicks() + usToCycles(usec); + } + + void DoState(PointerWrap &p) { + auto s = p.Section("AsyncIOResult", 1); + if (!s) + return; + + p.Do(result); + p.Do(finishTicks); + } + + s64 result; + u64 finishTicks; +}; typedef ThreadEventQueue IOThreadEventQueue; class AsyncIOManager : public IOThreadEventQueue { @@ -57,7 +78,9 @@ public: bool HasResult(u32 handle); bool PopResult(u32 handle, AsyncIOResult &result); + bool ReadResult(u32 handle, AsyncIOResult &result); bool WaitResult(u32 handle, AsyncIOResult &result); + u64 ResultFinishTicks(u32 handle); protected: virtual void ProcessEvent(AsyncIOEvent ref); From 8f3a8fd233de9ee7e01aad2ff2b544f544468b3b Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 27 Dec 2013 16:49:35 -0800 Subject: [PATCH 5/6] Very rough approximation of UMD seek delays. --- Core/FileSystems/ISOFileSystem.cpp | 19 ++++++++++++++++++- Core/FileSystems/ISOFileSystem.h | 1 + Core/FileSystems/VirtualDiscFileSystem.cpp | 14 +++++++++++++- Core/FileSystems/VirtualDiscFileSystem.h | 1 + 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/Core/FileSystems/ISOFileSystem.cpp b/Core/FileSystems/ISOFileSystem.cpp index 01f57ce681..a5303278a5 100644 --- a/Core/FileSystems/ISOFileSystem.cpp +++ b/Core/FileSystems/ISOFileSystem.cpp @@ -227,6 +227,7 @@ void ISOFileSystem::ReadDirectory(u32 startsector, u32 dirsize, TreeEntry *root, { u8 theSector[2048]; blockDevice->ReadBlock(secnum, theSector); + lastReadBlock_ = secnum; for (int offset = 0; offset < 2048; ) { @@ -535,7 +536,12 @@ size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) { // Whole sectors! Shortcut to this simple code. blockDevice->ReadBlocks(e.seekPos, (int)size, pointer); + if (abs((int)lastReadBlock_ - (int)e.seekPos) > 100) { + // This is an estimate, sometimes it takes 1+ seconds, but it definitely takes time. + usec = 100000; + } e.seekPos += (int)size; + lastReadBlock_ = e.seekPos; return (int)size; } @@ -594,6 +600,11 @@ size_t ISOFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) } size_t totalBytes = pointer - start; + if (abs((int)lastReadBlock_ - (int)secNum) > 100) { + // This is an estimate, sometimes it takes 1+ seconds, but it definitely takes time. + usec = 100000; + } + lastReadBlock_ = secNum; e.seekPos += (unsigned int)totalBytes; return (size_t)totalBytes; } @@ -750,7 +761,7 @@ ISOFileSystem::TreeEntry::~TreeEntry() { void ISOFileSystem::DoState(PointerWrap &p) { - auto s = p.Section("ISOFileSystem", 1); + auto s = p.Section("ISOFileSystem", 1, 2); if (!s) return; @@ -806,4 +817,10 @@ void ISOFileSystem::DoState(PointerWrap &p) } } } + + if (s >= 2) { + p.Do(lastReadBlock_); + } else { + lastReadBlock_ = 0; + } } diff --git a/Core/FileSystems/ISOFileSystem.h b/Core/FileSystems/ISOFileSystem.h index 9f074800b2..5e46436ff1 100644 --- a/Core/FileSystems/ISOFileSystem.h +++ b/Core/FileSystems/ISOFileSystem.h @@ -86,6 +86,7 @@ private: IHandleAllocator *hAlloc; TreeEntry *treeroot; BlockDevice *blockDevice; + u32 lastReadBlock_; TreeEntry entireISO; diff --git a/Core/FileSystems/VirtualDiscFileSystem.cpp b/Core/FileSystems/VirtualDiscFileSystem.cpp index 8037a103b2..5f50b77e82 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.cpp +++ b/Core/FileSystems/VirtualDiscFileSystem.cpp @@ -146,7 +146,7 @@ void VirtualDiscFileSystem::LoadFileListIndex() { void VirtualDiscFileSystem::DoState(PointerWrap &p) { - auto s = p.Section("VirtualDiscFileSystem", 1); + auto s = p.Section("VirtualDiscFileSystem", 1, 2); if (!s) return; @@ -217,6 +217,12 @@ void VirtualDiscFileSystem::DoState(PointerWrap &p) } } + if (s >= 2) { + p.Do(lastReadBlock_); + } else { + lastReadBlock_ = 0; + } + // We don't savestate handlers (loaded on fs load), but if they change, it may not load properly. } @@ -473,6 +479,12 @@ size_t VirtualDiscFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &u temp.Close(); iter->second.curOffset += size; + // TODO: This probably isn't enough... + if (abs((int)lastReadBlock_ - (int)iter->second.curOffset) > 100) { + // This is an estimate, sometimes it takes 1+ seconds, but it definitely takes time. + usec = 100000; + } + lastReadBlock_ = iter->second.curOffset; return size; } diff --git a/Core/FileSystems/VirtualDiscFileSystem.h b/Core/FileSystems/VirtualDiscFileSystem.h index 6348d51906..19840bf127 100644 --- a/Core/FileSystems/VirtualDiscFileSystem.h +++ b/Core/FileSystems/VirtualDiscFileSystem.h @@ -180,6 +180,7 @@ private: std::vector fileList; u32 currentBlockIndex; + u32 lastReadBlock_; std::map handlers; }; From efae9e061205dd6bf6f97c9fc513001f4eaaad5c Mon Sep 17 00:00:00 2001 From: "Unknown W. Brackets" Date: Fri, 26 Dec 2014 01:18:52 -0800 Subject: [PATCH 6/6] Make realistic timing optional. --- Core/HLE/sceIo.cpp | 36 +++++++++++++++++++----------------- UI/GameSettingsScreen.cpp | 2 +- 2 files changed, 20 insertions(+), 18 deletions(-) diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index cb9ebf8706..fbee4f5133 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -328,13 +328,13 @@ static void __IoAsyncNotify(u64 userdata, int cyclesLate) { CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, asyncNotifyEvent, userdata); return; } - } - - u64 finishTicks = __IoCompleteAsyncIO(f); - if (finishTicks > CoreTiming::GetTicks()) { - // Reschedule for later, since we now know how long it ought to take. - CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), asyncNotifyEvent, userdata); - return; + } else if (g_Config.iIOTimingMethod == IOTIMING_REALISTIC) { + u64 finishTicks = __IoCompleteAsyncIO(f); + if (finishTicks > CoreTiming::GetTicks()) { + // Reschedule for later, since we now know how long it ought to take. + CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), asyncNotifyEvent, userdata); + return; + } } if (f->waitingThreads.empty()) { @@ -379,13 +379,13 @@ static void __IoSyncNotify(u64 userdata, int cyclesLate) { CoreTiming::ScheduleEvent(usToCycles(500) - cyclesLate, syncNotifyEvent, userdata); return; } - } - - u64 finishTicks = ioManager.ResultFinishTicks(f->handle); - if (finishTicks > CoreTiming::GetTicks()) { - // Reschedule for later when the result should finish. - CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), syncNotifyEvent, userdata); - return; + } else if (g_Config.iIOTimingMethod == IOTIMING_REALISTIC) { + u64 finishTicks = ioManager.ResultFinishTicks(f->handle); + if (finishTicks > CoreTiming::GetTicks()) { + // Reschedule for later when the result should finish. + CoreTiming::ScheduleEvent(finishTicks - CoreTiming::GetTicks(), syncNotifyEvent, userdata); + return; + } } f->pendingAsyncResult = false; @@ -610,9 +610,11 @@ static u32 sceKernelStderr() { } u64 __IoCompleteAsyncIO(FileNode *f) { - u64 finishTicks = ioManager.ResultFinishTicks(f->handle); - if (finishTicks > CoreTiming::GetTicks()) { - return finishTicks; + if (g_Config.iIOTimingMethod == IOTIMING_REALISTIC) { + u64 finishTicks = ioManager.ResultFinishTicks(f->handle); + if (finishTicks > CoreTiming::GetTicks()) { + return finishTicks; + } } AsyncIOResult managerResult; if (ioManager.WaitResult(f->handle, managerResult)) { diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index b26ba3a0ae..673082d144 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -392,7 +392,7 @@ void GameSettingsScreen::CreateViews() { systemSettings->Add(new CheckBox(&g_Config.bSeparateCPUThread, s->T("Multithreaded (experimental)"))); systemSettings->Add(new CheckBox(&g_Config.bSeparateIOThread, s->T("I/O on thread (experimental)")))->SetEnabled(!PSP_IsInited()); - static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)" }; + static const char *ioTimingMethods[] = { "Fast (lag on slow storage)", "Host (bugs, less lag)", "Simulate UMD delays" }; View *ioTimingMethod = systemSettings->Add(new PopupMultiChoice(&g_Config.iIOTimingMethod, s->T("IO timing method"), ioTimingMethods, 0, ARRAY_SIZE(ioTimingMethods), s, screenManager())); ioTimingMethod->SetEnabledPtr(&g_Config.bSeparateIOThread); systemSettings->Add(new CheckBox(&g_Config.bForceLagSync, s->T("Force real clock sync (slower, less lag)")));