diff --git a/CMakeLists.txt b/CMakeLists.txt index 6021144a5b..6985fa0f12 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1268,6 +1268,8 @@ set(GPU_SOURCES GPU/Common/SplineCommon.h GPU/Debugger/Breakpoints.cpp GPU/Debugger/Breakpoints.h + GPU/Debugger/Record.cpp + GPU/Debugger/Record.h GPU/Debugger/Stepping.cpp GPU/Debugger/Stepping.h GPU/GPUInterface.h @@ -1350,6 +1352,8 @@ add_library(${CoreLibName} ${CoreLinkType} Core/ELF/ParamSFO.cpp Core/ELF/ParamSFO.h Core/FileSystems/tlzrc.cpp + Core/FileSystems/BlobFileSystem.cpp + Core/FileSystems/BlobFileSystem.h Core/FileSystems/BlockDevices.cpp Core/FileSystems/BlockDevices.h Core/FileSystems/DirectoryFileSystem.cpp diff --git a/Core/Core.vcxproj b/Core/Core.vcxproj index ce2cb5033b..e02438f7f9 100644 --- a/Core/Core.vcxproj +++ b/Core/Core.vcxproj @@ -21,7 +21,8 @@ {533F1D30-D04D-47CC-AD71-20F658907E36} Core - + + @@ -183,6 +184,7 @@ + @@ -524,6 +526,7 @@ + diff --git a/Core/Core.vcxproj.filters b/Core/Core.vcxproj.filters index 234ff2172a..79da4d1df0 100644 --- a/Core/Core.vcxproj.filters +++ b/Core/Core.vcxproj.filters @@ -680,6 +680,9 @@ HLE\Libraries + + FileSystems + @@ -1250,10 +1253,13 @@ HLE\Libraries + + FileSystems + - + \ No newline at end of file diff --git a/Core/CoreTiming.cpp b/Core/CoreTiming.cpp index 1c148c86c3..1f41a6f802 100644 --- a/Core/CoreTiming.cpp +++ b/Core/CoreTiming.cpp @@ -573,6 +573,10 @@ void ForceCheck() currentMIPS->downcount = -1; // But let's not eat a bunch more time in Advance() because of this. slicelength = -1; + +#ifdef _DEBUG + _dbg_assert_msg_(CPU, cyclesExecuted >= 0, "Shouldn't have a negative cyclesExecuted"); +#endif } void Advance() @@ -613,7 +617,7 @@ void LogPendingEvents() Event *ptr = first; while (ptr) { - //INFO_LOG(TIMER, "PENDING: Now: %lld Pending: %lld Type: %d", globalTimer, ptr->time, ptr->type); + //INFO_LOG(CPU, "PENDING: Now: %lld Pending: %lld Type: %d", globalTimer, ptr->time, ptr->type); ptr = ptr->next; } } diff --git a/Core/ELF/ParamSFO.h b/Core/ELF/ParamSFO.h index ead49feff8..940280bac3 100644 --- a/Core/ELF/ParamSFO.h +++ b/Core/ELF/ParamSFO.h @@ -37,6 +37,14 @@ public: std::vector GetKeys(); std::string GenerateFakeID(std::string filename = ""); + std::string GetDiscID() { + const std::string discID = GetValueString("DISC_ID"); + if (discID.empty()) { + return GenerateFakeID(); + } + return discID; + } + bool ReadSFO(const u8 *paramsfo, size_t size); bool WriteSFO(u8 **paramsfo, size_t *size); diff --git a/Core/FileSystems/BlobFileSystem.cpp b/Core/FileSystems/BlobFileSystem.cpp new file mode 100644 index 0000000000..059e79199f --- /dev/null +++ b/Core/FileSystems/BlobFileSystem.cpp @@ -0,0 +1,137 @@ +// Copyright (c) 2017- 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 "Core/FileSystems/BlobFileSystem.h" + +BlobFileSystem::BlobFileSystem(IHandleAllocator *hAlloc, FileLoader *fileLoader, std::string alias) +: alloc_(hAlloc), fileLoader_(fileLoader), alias_(alias) { +} + +BlobFileSystem::~BlobFileSystem() { + // TODO: Who deletes fileLoader? +} + +void BlobFileSystem::DoState(PointerWrap &p) { + // Not used in real emulation. +} + +std::vector BlobFileSystem::GetDirListing(std::string path) { + std::vector listing; + listing.push_back(GetFileInfo(alias_)); + return listing; +} + +u32 BlobFileSystem::OpenFile(std::string filename, FileAccess access, const char *devicename) { + u32 newHandle = alloc_->GetNewHandle(); + entries_[newHandle] = 0; + return newHandle; +} + +void BlobFileSystem::CloseFile(u32 handle) { + alloc_->FreeHandle(handle); + entries_.erase(handle); +} + +size_t BlobFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size) { + auto entry = entries_.find(handle); + if (entry != entries_.end()) { + size_t readSize = fileLoader_->ReadAt(entry->second, size, pointer); + entry->second += readSize; + return readSize; + } + return 0; +} + +size_t BlobFileSystem::ReadFile(u32 handle, u8 *pointer, s64 size, int &usec) { + usec = 0; + return ReadFile(handle, pointer, size); +} + +size_t BlobFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size) { + return 0; +} + +size_t BlobFileSystem::WriteFile(u32 handle, const u8 *pointer, s64 size, int &usec) { + return 0; +} + +size_t BlobFileSystem::SeekFile(u32 handle, s32 position, FileMove type) { + auto entry = entries_.find(handle); + if (entry != entries_.end()) { + switch (type) { + case FILEMOVE_BEGIN: + entry->second = position; + break; + case FILEMOVE_CURRENT: + entry->second += position; + break; + case FILEMOVE_END: + entry->second = fileLoader_->FileSize() + position; + break; + } + return (size_t)entry->second; + } + return 0; +} + +PSPFileInfo BlobFileSystem::GetFileInfo(std::string filename) { + PSPFileInfo info{}; + info.name = alias_; + info.size = fileLoader_->FileSize(); + info.access = 0666; + info.exists = true; + info.type = FILETYPE_NORMAL; + return info; +} + +bool BlobFileSystem::OwnsHandle(u32 handle) { + auto entry = entries_.find(handle); + return entry != entries_.end(); +} + +int BlobFileSystem::Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) { + return -1; +} + +int BlobFileSystem::DevType(u32 handle) { + return -1; +} + +bool BlobFileSystem::MkDir(const std::string &dirname) { + return false; +} + +bool BlobFileSystem::RmDir(const std::string &dirname) { + return false; +} + +int BlobFileSystem::RenameFile(const std::string &from, const std::string &to) { + return -1; +} + +bool BlobFileSystem::RemoveFile(const std::string &filename) { + return false; +} + +bool BlobFileSystem::GetHostPath(const std::string &inpath, std::string &outpath) { + outpath = fileLoader_->Path(); + return true; +} + +u64 BlobFileSystem::FreeSpace(const std::string &path) { + return 0; +} diff --git a/Core/FileSystems/BlobFileSystem.h b/Core/FileSystems/BlobFileSystem.h new file mode 100644 index 0000000000..573fdacdd7 --- /dev/null +++ b/Core/FileSystems/BlobFileSystem.h @@ -0,0 +1,63 @@ +// Copyright (c) 2017- 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 + +// This is used for opening a debug file as a blob, and mounting it. +// Importantly, uses a fileLoader for all access, so http:// URLs are supported. +// As of writing, only used by GE dump replay. + +#include +#include +#include "Core/Loaders.h" +#include "Core/FileSystems/FileSystem.h" + +class BlobFileSystem : public IFileSystem { +public: + BlobFileSystem(IHandleAllocator *hAlloc, FileLoader *fileLoader, std::string alias); + ~BlobFileSystem(); + + void DoState(PointerWrap &p) override; + std::vector GetDirListing(std::string path) override; + u32 OpenFile(std::string filename, FileAccess access, const char *devicename = nullptr) 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; + int Ioctl(u32 handle, u32 cmd, u32 indataPtr, u32 inlen, u32 outdataPtr, u32 outlen, int &usec) override; + int DevType(u32 handle) override; + int Flags() override { return 0; } + + 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; + bool RemoveFile(const std::string &filename) override; + bool GetHostPath(const std::string &inpath, std::string &outpath) override; + u64 FreeSpace(const std::string &path) override; + +private: + // File positions. + std::map entries_; + + IHandleAllocator *alloc_; + FileLoader *fileLoader_; + std::string alias_; +}; diff --git a/Core/HLE/HLETables.cpp b/Core/HLE/HLETables.cpp index b3efc7faac..16444c6e7d 100644 --- a/Core/HLE/HLETables.cpp +++ b/Core/HLE/HLETables.cpp @@ -92,6 +92,7 @@ const HLEFunction FakeSysCalls[] = { {NID_EXTENDRETURN, __KernelReturnFromExtendStack, "__KernelReturnFromExtendStack"}, {NID_MODULERETURN, __KernelReturnFromModuleFunc, "__KernelReturnFromModuleFunc"}, {NID_IDLE, __KernelIdle, "_sceKernelIdle"}, + {NID_GPUREPLAY, __KernelGPUReplay, "__KernelGPUReplay"}, }; const HLEFunction UtilsForUser[] = diff --git a/Core/HLE/HLETables.h b/Core/HLE/HLETables.h index 4349747a2c..2bc280f7fe 100644 --- a/Core/HLE/HLETables.h +++ b/Core/HLE/HLETables.h @@ -23,5 +23,6 @@ #define NID_EXTENDRETURN 0xbad0b0c9 #define NID_MODULERETURN 0xbad0d318 #define NID_IDLE 0x1d7e1d7e +#define NID_GPUREPLAY 0x9e45bd95 void RegisterAllModules(); diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 19ccacf1f3..9f271ee2de 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -825,13 +825,29 @@ static u32 sceDisplaySetMode(int displayMode, int displayWidth, int displayHeigh return DisplayWaitForVblanks("display mode", 1); } -// Some games (GTA) never call this during gameplay, so bad place to put a framerate counter. -static u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) { +void __DisplaySetFramebuf(u32 topaddr, int linesize, int pixelFormat, int sync) { FrameBufferState fbstate = {0}; fbstate.topaddr = topaddr; - fbstate.fmt = (GEBufferFormat)pixelformat; + fbstate.fmt = (GEBufferFormat)pixelFormat; fbstate.stride = linesize; + if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { + // Write immediately to the current framebuffer parameters + framebuf = fbstate; + gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt); + } else { + // Delay the write until vblank + latchedFramebuf = fbstate; + framebufIsLatched = true; + + // If we update the format or stride, this affects the current framebuf immediately. + framebuf.fmt = latchedFramebuf.fmt; + framebuf.stride = latchedFramebuf.stride; + } +} + +// Some games (GTA) never call this during gameplay, so bad place to put a framerate counter. +u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) { if (sync != PSP_DISPLAY_SETBUF_IMMEDIATE && sync != PSP_DISPLAY_SETBUF_NEXTFRAME) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_MODE, "invalid sync mode"); } @@ -849,7 +865,7 @@ static u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int } if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { - if (fbstate.fmt != latchedFramebuf.fmt || fbstate.stride != latchedFramebuf.stride) { + if ((GEBufferFormat)pixelformat != latchedFramebuf.fmt || linesize != latchedFramebuf.stride) { return hleReportError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_MODE, "must change latched framebuf first"); } } @@ -882,19 +898,7 @@ static u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int lastFlipCycles = CoreTiming::GetTicks(); } - if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { - // Write immediately to the current framebuffer parameters - framebuf = fbstate; - gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt); - } else { - // Delay the write until vblank - latchedFramebuf = fbstate; - framebufIsLatched = true; - - // If we update the format or stride, this affects the current framebuf immediately. - framebuf.fmt = latchedFramebuf.fmt; - framebuf.stride = latchedFramebuf.stride; - } + __DisplaySetFramebuf(topaddr, linesize, pixelformat, sync); if (delayCycles > 0) { // Okay, the game is going at too high a frame rate. God of War and Fat Princess both do this. @@ -910,10 +914,10 @@ static u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int } } -bool __DisplayGetFramebuf(u8 **topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) { +bool __DisplayGetFramebuf(PSPPointer *topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) { const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf; if (topaddr != nullptr) - *topaddr = Memory::GetPointer(fbState.topaddr); + (*topaddr).ptr = fbState.topaddr; if (linesize != nullptr) *linesize = fbState.stride; if (pixelFormat != nullptr) diff --git a/Core/HLE/sceDisplay.h b/Core/HLE/sceDisplay.h index caaeafa72d..db6cbcd1e1 100644 --- a/Core/HLE/sceDisplay.h +++ b/Core/HLE/sceDisplay.h @@ -17,6 +17,8 @@ #pragma once +#include "Core/MemMap.h" + void __DisplayInit(); void __DisplayDoState(PointerWrap &p); void __DisplayShutdown(); @@ -27,7 +29,8 @@ void Register_sceDisplay(); bool __DisplayFrameDone(); // Get information about the current framebuffer. -bool __DisplayGetFramebuf(u8 **topaddr, u32 *linesize, u32 *pixelFormat, int mode); +bool __DisplayGetFramebuf(PSPPointer *topaddr, u32 *linesize, u32 *pixelFormat, int mode); +void __DisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync); typedef void (*VblankCallback)(); // Listen for vblank events. Only register during init. @@ -44,4 +47,4 @@ int __DisplayGetFlipCount(); // Call this when resuming to avoid a small speedup burst void __DisplaySetWasPaused(); -void Register_sceDisplay_driver(); \ No newline at end of file +void Register_sceDisplay_driver(); diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 26f83d1ac7..ead6f487a5 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -1825,7 +1825,8 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o return 0; case 0x20: // EMULATOR_DEVCTL__EMIT_SCREENSHOT - u8 *topaddr; + { + PSPPointer topaddr; u32 linesize, pixelFormat; __DisplayGetFramebuf(&topaddr, &linesize, &pixelFormat, 0); @@ -1833,6 +1834,7 @@ static u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 o host->SendDebugScreenshot(topaddr, linesize, 272); return 0; } + } ERROR_LOG(SCEIO, "sceIoDevCtl: UNKNOWN PARAMETERS"); diff --git a/Core/HLE/sceKernelModule.cpp b/Core/HLE/sceKernelModule.cpp index 85b8449c76..f3d0005558 100644 --- a/Core/HLE/sceKernelModule.cpp +++ b/Core/HLE/sceKernelModule.cpp @@ -24,6 +24,7 @@ #include "Common/FileUtil.h" #include "Common/StringUtils.h" #include "Core/Config.h" +#include "Core/Core.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/HLE/HLETables.h" @@ -57,6 +58,7 @@ #include "Core/HLE/KernelWaitHelpers.h" #include "Core/ELF/ParamSFO.h" +#include "GPU/Debugger/Record.h" #include "GPU/GPU.h" #include "GPU/GPUInterface.h" #include "GPU/GPUState.h" @@ -1513,8 +1515,36 @@ u32 __KernelGetModuleGP(SceUID uid) } } -bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string) -{ +void __KernelLoadReset() { + // Wipe kernel here, loadexec should reset the entire system + if (__KernelIsRunning()) { + u32 error; + while (!loadedModules.empty()) { + SceUID moduleID = *loadedModules.begin(); + Module *module = kernelObjects.Get(moduleID, error); + if (module) { + module->Cleanup(); + } else { + // An invalid module. We need to remove it or we'll loop forever. + WARN_LOG(LOADER, "Invalid module still marked as loaded on loadexec"); + loadedModules.erase(moduleID); + } + } + + Replacement_Shutdown(); + __KernelShutdown(); + // HLE needs to be reset here + HLEShutdown(); + Replacement_Init(); + HLEInit(); + gpu->Reinitialize(); + } + + __KernelModuleInit(); + __KernelInit(); +} + +bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string) { SceKernelLoadExecParam param; if (paramPtr) @@ -1536,33 +1566,7 @@ bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_str Memory::Memcpy(param_key, keyAddr, (u32)keylen); } - // Wipe kernel here, loadexec should reset the entire system - if (__KernelIsRunning()) - { - u32 error; - while (!loadedModules.empty()) { - SceUID moduleID = *loadedModules.begin(); - Module *module = kernelObjects.Get(moduleID, error); - if (module) { - module->Cleanup(); - } else { - // An invalid module. We need to remove it or we'll loop forever. - WARN_LOG(LOADER, "Invalid module still marked as loaded on loadexec"); - loadedModules.erase(moduleID); - } - } - - Replacement_Shutdown(); - __KernelShutdown(); - //HLE needs to be reset here - HLEShutdown(); - Replacement_Init(); - HLEInit(); - gpu->Reinitialize(); - } - - __KernelModuleInit(); - __KernelInit(); + __KernelLoadReset(); PSPFileInfo info = pspFileSystem.GetFileInfo(filename); if (!info.exists) { @@ -1635,6 +1639,64 @@ bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_str return true; } +bool __KernelLoadGEDump(const std::string &base_filename, std::string *error_string) { + __KernelLoadReset(); + + mipsr4k.pc = PSP_GetUserMemoryBase(); + + const static u32_le runDumpCode[] = { + // Save the filename. + MIPS_MAKE_ORI(MIPS_REG_S0, MIPS_REG_A0, 0), + MIPS_MAKE_ORI(MIPS_REG_S1, MIPS_REG_A1, 0), + // Call the actual render. + MIPS_MAKE_SYSCALL("FakeSysCalls", "__KernelGPUReplay"), + // Make sure we don't get out of sync. + MIPS_MAKE_LUI(MIPS_REG_A0, 0), + MIPS_MAKE_SYSCALL("sceGe_user", "sceGeDrawSync"), + // Set the return address after the entry which saved the filename. + MIPS_MAKE_LUI(MIPS_REG_RA, mipsr4k.pc >> 16), + MIPS_MAKE_ADDIU(MIPS_REG_RA, MIPS_REG_RA, 8), + // Wait for the next vblank to render again. + MIPS_MAKE_JR_RA(), + MIPS_MAKE_SYSCALL("sceDisplay", "sceDisplayWaitVblankStart"), + // This never gets reached, just here to be safe. + MIPS_MAKE_BREAK(0), + }; + + for (size_t i = 0; i < ARRAY_SIZE(runDumpCode); ++i) { + Memory::WriteUnchecked_U32(runDumpCode[i], mipsr4k.pc + (int)i * sizeof(u32_le)); + } + + Module *module = new Module; + kernelObjects.Create(module); + loadedModules.insert(module->GetUID()); + memset(&module->nm, 0, sizeof(module->nm)); + module->isFake = true; + module->nm.entry_addr = mipsr4k.pc; + module->nm.gp_value = -1; + + SceUID threadID = __KernelSetupRootThread(module->GetUID(), (int)base_filename.size(), base_filename.data(), 0x20, 0x1000, 0); + __KernelSetThreadRA(threadID, NID_MODULERETURN); + + __KernelStartIdleThreads(module->GetUID()); + return true; +} + +void __KernelGPUReplay() { + // Special ABI: s0 and s1 are the "args". Not null terminated. + const char *filenamep = Memory::GetCharPointer(currentMIPS->r[MIPS_REG_S1]); + if (!filenamep) { + ERROR_LOG(SYSTEM, "Failed to load dump filename"); + Core_Stop(); + return; + } + + std::string filename(filenamep, currentMIPS->r[MIPS_REG_S0]); + if (!GPURecord::RunMountedReplay(filename)) { + Core_Stop(); + } +} + int sceKernelLoadExec(const char *filename, u32 paramPtr) { std::string exec_filename = filename; diff --git a/Core/HLE/sceKernelModule.h b/Core/HLE/sceKernelModule.h index 70509d9f22..232c71d23f 100644 --- a/Core/HLE/sceKernelModule.h +++ b/Core/HLE/sceKernelModule.h @@ -39,7 +39,9 @@ void __KernelModuleDoState(PointerWrap &p); void __KernelModuleShutdown(); u32 __KernelGetModuleGP(SceUID module); +bool __KernelLoadGEDump(const std::string &base_filename, std::string *error_string); bool __KernelLoadExec(const char *filename, u32 paramPtr, std::string *error_string); +void __KernelGPUReplay(); void __KernelReturnFromModuleFunc(); u32 hleKernelStopUnloadSelfModuleWithOrWithoutStatus(u32 exitCode, u32 argSize, u32 argp, u32 statusAddr, u32 optionAddr, bool WithStatus); diff --git a/Core/Loaders.cpp b/Core/Loaders.cpp index 20b7c5e346..ace145457d 100644 --- a/Core/Loaders.cpp +++ b/Core/Loaders.cpp @@ -69,31 +69,30 @@ IdentifiedFileType Identify_File(FileLoader *fileLoader) { } std::string extension = fileLoader->Extension(); - if (!strcasecmp(extension.c_str(), ".iso")) - { + if (!strcasecmp(extension.c_str(), ".iso")) { // may be a psx iso, they have 2352 byte sectors. You never know what some people try to open - if ((fileLoader->FileSize() % 2352) == 0) - { + if ((fileLoader->FileSize() % 2352) == 0) { unsigned char sync[12]; fileLoader->ReadAt(0, 12, sync); // each sector in a mode2 image starts with these 12 bytes - if (memcmp(sync,"\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00",12) == 0) - { + if (memcmp(sync,"\x00\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF\x00", 12) == 0) { return IdentifiedFileType::ISO_MODE2; } // maybe it also just happened to have that size, } return IdentifiedFileType::PSP_ISO; - } - else if (!strcasecmp(extension.c_str(),".cso")) - { + } else if (!strcasecmp(extension.c_str(), ".cso")) { return IdentifiedFileType::PSP_ISO; - } - else if (!strcasecmp(extension.c_str(),".ppst")) - { + } else if (!strcasecmp(extension.c_str(), ".ppst")) { return IdentifiedFileType::PPSSPP_SAVESTATE; + } else if (!strcasecmp(extension.c_str(), ".ppdmp")) { + char data[8]{}; + fileLoader->ReadAt(0, 8, data); + if (memcmp(data, "PPSSPPGE", 8) == 0) { + return IdentifiedFileType::PPSSPP_GE_DUMP; + } } // First, check if it's a directory with an EBOOT.PBP in it. @@ -337,6 +336,9 @@ bool LoadFile(FileLoader **fileLoaderPtr, std::string *error_string) { *error_string = "This is save data, not a game."; // Actually, we could make it load it... break; + case IdentifiedFileType::PPSSPP_GE_DUMP: + return Load_PSP_GE_Dump(fileLoader, error_string); + case IdentifiedFileType::UNKNOWN_BIN: case IdentifiedFileType::UNKNOWN_ELF: case IdentifiedFileType::UNKNOWN: diff --git a/Core/Loaders.h b/Core/Loaders.h index e6678c0bd0..faf9f78cc1 100644 --- a/Core/Loaders.h +++ b/Core/Loaders.h @@ -49,6 +49,8 @@ enum class IdentifiedFileType { PSP_SAVEDATA_DIRECTORY, PPSSPP_SAVESTATE, + PPSSPP_GE_DUMP, + UNKNOWN, }; diff --git a/Core/MIPS/ARM/ArmCompBranch.cpp b/Core/MIPS/ARM/ArmCompBranch.cpp index 167468ae87..af18735e82 100644 --- a/Core/MIPS/ARM/ArmCompBranch.cpp +++ b/Core/MIPS/ARM/ArmCompBranch.cpp @@ -612,6 +612,11 @@ void ArmJit::Comp_Syscall(MIPSOpcode op) RestoreRoundingMode(); js.downcountAmount = -offset; + if (!js.inDelaySlot) { + gpr.SetRegImm(SCRATCHREG1, GetCompilerPC() + 4); + MovToPC(SCRATCHREG1); + } + FlushAll(); SaveDowncount(); diff --git a/Core/MIPS/ARM64/Arm64CompBranch.cpp b/Core/MIPS/ARM64/Arm64CompBranch.cpp index 89f536dd87..beb9ab7777 100644 --- a/Core/MIPS/ARM64/Arm64CompBranch.cpp +++ b/Core/MIPS/ARM64/Arm64CompBranch.cpp @@ -594,6 +594,11 @@ void Arm64Jit::Comp_Syscall(MIPSOpcode op) RestoreRoundingMode(); js.downcountAmount = -offset; + if (!js.inDelaySlot) { + gpr.SetRegImm(SCRATCH1, GetCompilerPC() + 4); + MovToPC(SCRATCH1); + } + FlushAll(); SaveStaticRegisters(); diff --git a/Core/MIPS/IR/IRCompBranch.cpp b/Core/MIPS/IR/IRCompBranch.cpp index 40810d06f4..f9185b3bdd 100644 --- a/Core/MIPS/IR/IRCompBranch.cpp +++ b/Core/MIPS/IR/IRCompBranch.cpp @@ -380,6 +380,11 @@ void IRFrontend::Comp_Syscall(MIPSOpcode op) { ir.Write(IROp::Downcount, 0, dcAmount & 0xFF, dcAmount >> 8); js.downcountAmount = 0; + // If not in a delay slot, we need to update PC. + if (!js.inDelaySlot) { + ir.Write(IROp::SetPCConst, 0, ir.AddConstant(GetCompilerPC() + 4)); + } + FlushAll(); RestoreRoundingMode(); diff --git a/Core/MIPS/x86/CompBranch.cpp b/Core/MIPS/x86/CompBranch.cpp index 3382bd4ae1..9217cdc96a 100644 --- a/Core/MIPS/x86/CompBranch.cpp +++ b/Core/MIPS/x86/CompBranch.cpp @@ -787,6 +787,10 @@ void Jit::Comp_Syscall(MIPSOpcode op) RestoreRoundingMode(); js.downcountAmount = -offset; + if (!js.inDelaySlot) { + MOV(32, M(&mips_->pc), Imm32(GetCompilerPC() + 4)); + } + #ifdef USE_PROFILER // When profiling, we can't skip CallSyscall, since it times syscalls. ABI_CallFunctionC(&CallSyscall, op.encoding); diff --git a/Core/PSPLoaders.cpp b/Core/PSPLoaders.cpp index 9f8066005d..3dd9b76492 100644 --- a/Core/PSPLoaders.cpp +++ b/Core/PSPLoaders.cpp @@ -27,11 +27,12 @@ #include "Core/ELF/ElfReader.h" #include "Core/ELF/ParamSFO.h" -#include "FileSystems/BlockDevices.h" -#include "FileSystems/DirectoryFileSystem.h" -#include "FileSystems/ISOFileSystem.h" -#include "FileSystems/MetaFileSystem.h" -#include "FileSystems/VirtualDiscFileSystem.h" +#include "Core/FileSystems/BlockDevices.h" +#include "Core/FileSystems/BlobFileSystem.h" +#include "Core/FileSystems/DirectoryFileSystem.h" +#include "Core/FileSystems/ISOFileSystem.h" +#include "Core/FileSystems/MetaFileSystem.h" +#include "Core/FileSystems/VirtualDiscFileSystem.h" #include "Core/Loaders.h" #include "Core/MemMap.h" @@ -355,3 +356,11 @@ bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string) { return __KernelLoadExec(finalName.c_str(), 0, error_string); } + +bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string) { + BlobFileSystem *umd = new BlobFileSystem(&pspFileSystem, fileLoader, "data.ppdmp"); + pspFileSystem.Mount("disc0:", umd); + + __KernelLoadGEDump("disc0:/data.ppdmp", error_string); + return true; +} diff --git a/Core/PSPLoaders.h b/Core/PSPLoaders.h index 400df80d65..34f33ca177 100644 --- a/Core/PSPLoaders.h +++ b/Core/PSPLoaders.h @@ -23,5 +23,6 @@ class FileLoader; bool Load_PSP_ISO(FileLoader *fileLoader, std::string *error_string); bool Load_PSP_ELF_PBP(FileLoader *fileLoader, std::string *error_string); +bool Load_PSP_GE_Dump(FileLoader *fileLoader, std::string *error_string); void InitMemoryForGameISO(FileLoader *fileLoader); void InitMemoryForGamePBP(FileLoader *fileLoader); diff --git a/Core/System.cpp b/Core/System.cpp index 89a5151edc..05e47333eb 100644 --- a/Core/System.cpp +++ b/Core/System.cpp @@ -242,10 +242,7 @@ void CPU_Init() { // Here we have read the PARAM.SFO, let's see if we need any compatibility overrides. // Homebrew usually has an empty discID, and even if they do have a disc id, it's not // likely to collide with any commercial ones. - std::string discID = g_paramSFO.GetValueString("DISC_ID"); - if (discID.empty()) { - discID = g_paramSFO.GenerateFakeID(); - } + std::string discID = g_paramSFO.GetDiscID(); coreParameter.compat.Load(discID); Memory::Init(); diff --git a/Core/TextureReplacer.cpp b/Core/TextureReplacer.cpp index f1957c8c2e..f7256d8f20 100644 --- a/Core/TextureReplacer.cpp +++ b/Core/TextureReplacer.cpp @@ -47,10 +47,7 @@ void TextureReplacer::Init() { } void TextureReplacer::NotifyConfigChanged() { - gameID_ = g_paramSFO.GetValueString("DISC_ID"); - if (gameID_.empty()) { - gameID_ = g_paramSFO.GenerateFakeID(); - } + gameID_ = g_paramSFO.GetDiscID(); enabled_ = g_Config.bReplaceTextures || g_Config.bSaveNewTextures; if (enabled_) { diff --git a/GPU/Common/FramebufferCommon.cpp b/GPU/Common/FramebufferCommon.cpp index 548ffb54d1..d3ffcb3755 100644 --- a/GPU/Common/FramebufferCommon.cpp +++ b/GPU/Common/FramebufferCommon.cpp @@ -119,10 +119,6 @@ FramebufferManagerCommon::~FramebufferManagerCommon() { } void FramebufferManagerCommon::Init() { - std::string gameId = g_paramSFO.GetValueString("DISC_ID"); - if (gameId.empty()) { - gameId = g_paramSFO.GenerateFakeID(); - } BeginFrame(); } diff --git a/GPU/Common/VertexDecoderCommon.h b/GPU/Common/VertexDecoderCommon.h index 194bbc5ed5..0c73504a69 100644 --- a/GPU/Common/VertexDecoderCommon.h +++ b/GPU/Common/VertexDecoderCommon.h @@ -480,8 +480,8 @@ class VertexDecoder { public: VertexDecoder(); - // A jit cache is not mandatory, we don't use it in the sw renderer - void SetVertexType(u32 vtype, const VertexDecoderOptions &options, VertexDecoderJitCache *jitCache = 0); + // A jit cache is not mandatory. + void SetVertexType(u32 vtype, const VertexDecoderOptions &options, VertexDecoderJitCache *jitCache = nullptr); u32 VertexType() const { return fmt_; } diff --git a/GPU/D3D11/GPU_D3D11.cpp b/GPU/D3D11/GPU_D3D11.cpp index d8724867d0..14904495f2 100644 --- a/GPU/D3D11/GPU_D3D11.cpp +++ b/GPU/D3D11/GPU_D3D11.cpp @@ -293,10 +293,6 @@ void GPU_D3D11::InitClearInternal() { } } -void GPU_D3D11::DumpNextFrame() { - dumpNextFrame_ = true; -} - void GPU_D3D11::BeginHostFrame() { GPUCommon::BeginHostFrame(); UpdateCmdInfo(); @@ -309,11 +305,6 @@ void GPU_D3D11::BeginHostFrame() { } } -void GPU_D3D11::BeginFrame() { - ScheduleEvent(GPU_EVENT_BEGIN_FRAME); - gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX); -} - void GPU_D3D11::ReapplyGfxStateInternal() { GPUCommon::ReapplyGfxStateInternal(); @@ -331,16 +322,11 @@ void GPU_D3D11::BeginFrameInternal() { depalShaderCache_->Decimate(); // fragmentTestCache_.Decimate(); - if (dumpNextFrame_) { - NOTICE_LOG(G3D, "DUMPING THIS FRAME"); - dumpThisFrame_ = true; - dumpNextFrame_ = false; - } else if (dumpThisFrame_) { - dumpThisFrame_ = false; - } + GPUCommon::BeginFrameInternal(); shaderManagerD3D11_->DirtyLastShader(); framebufferManagerD3D11_->BeginFrame(); + gstate_c.Dirty(DIRTY_PROJTHROUGHMATRIX); } void GPU_D3D11::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { diff --git a/GPU/D3D11/GPU_D3D11.h b/GPU/D3D11/GPU_D3D11.h index c9cbacd7ee..60a481b5da 100644 --- a/GPU/D3D11/GPU_D3D11.h +++ b/GPU/D3D11/GPU_D3D11.h @@ -42,13 +42,11 @@ public: void ReapplyGfxStateInternal() override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; - void BeginFrame() override; void GetStats(char *buffer, size_t bufsize) override; void ClearCacheNextFrame() override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DumpNextFrame() override; void DoState(PointerWrap &p) override; void ClearShaderCache() override; @@ -122,4 +120,4 @@ private: std::string reportingPrimaryInfo_; std::string reportingFullInfo_; -}; \ No newline at end of file +}; diff --git a/GPU/Debugger/Record.cpp b/GPU/Debugger/Record.cpp new file mode 100644 index 0000000000..e5a85b6a44 --- /dev/null +++ b/GPU/Debugger/Record.cpp @@ -0,0 +1,1037 @@ +// Copyright (c) 2017- 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 +#include "base/stringutil.h" +#include "Common/Common.h" +#include "Common/FileUtil.h" +#include "Common/Log.h" +#include "Core/Core.h" +#include "Core/CoreTiming.h" +#include "Core/ELF/ParamSFO.h" +#include "Core/FileSystems/MetaFileSystem.h" +#include "Core/HLE/sceDisplay.h" +#include "Core/HLE/sceKernelMemory.h" +#include "Core/MemMap.h" +#include "Core/MIPS/MIPS.h" +#include "Core/System.h" +#include "GPU/GPUInterface.h" +#include "GPU/GPUState.h" +#include "GPU/ge_constants.h" +#include "GPU/Common/TextureDecoder.h" +#include "GPU/Common/VertexDecoderCommon.h" +#include "GPU/Debugger/Record.h" + +namespace GPURecord { + +static const char *HEADER = "PPSSPPGE"; +static const int VERSION = 2; + +static bool active = false; +static bool nextFrame = false; +static bool writePending = false; + +enum class CommandType : u8 { + INIT = 0, + REGISTERS = 1, + VERTICES = 2, + INDICES = 3, + CLUT = 4, + TRANSFERSRC = 5, + MEMSET = 6, + MEMCPYDEST = 7, + MEMCPYDATA = 8, + DISPLAY = 9, + + TEXTURE0 = 0x10, + TEXTURE1 = 0x11, + TEXTURE2 = 0x12, + TEXTURE3 = 0x13, + TEXTURE4 = 0x14, + TEXTURE5 = 0x15, + TEXTURE6 = 0x16, + TEXTURE7 = 0x17, +}; + +#pragma pack(push, 1) + +struct Command { + CommandType type; + u32 sz; + u32 ptr; +}; + +#pragma pack(pop) + +static std::vector pushbuf; +static std::vector commands; +static std::vector lastRegisters; +static std::vector lastTextures; + +// TODO: Maybe move execute to another file? +static u32 execMemcpyDest; +static u32 execListBuf; +static u32 execListPos; +static u32 execListID; +static const int LIST_BUF_SIZE = 256 * 1024; +static std::vector execListQueue; + +// This class maps pushbuffer (dump data) sections to PSP memory. +// Dumps can be larger than available PSP memory, because they include generated data too. +// +// If possible, it maps to dynamically allocated "slabs" so nearby access is fast. +// Otherwise it uses "extra" allocations to manage sections that straddle two slabs. +// Slabs are managed with LRU, extra buffers are round-robin. +class BufMapping { +public: + // Returns a pointer to contiguous memory for this access, or else 0 (failure). + u32 Map(u32 bufpos, u32 sz); + + // Clear and reset allocations made. + void Reset() { + slabGeneration_ = 0; + extraOffset_ = 0; + for (int i = 0; i < SLAB_COUNT; ++i) { + slabs_[i].Free(); + } + for (int i = 0; i < EXTRA_COUNT; ++i) { + extra_[i].Free(); + } + } + +protected: + u32 MapSlab(u32 bufpos); + u32 MapExtra(u32 bufpos, u32 sz); + + enum { + // These numbers kept low because we only have 24 MB of user memory to map into. + SLAB_SIZE = 1 * 1024 * 1024, + // 10 is the number of texture units + verts + inds. + // In the worst case, we could concurrently need 10 slabs/extras at the same time. + SLAB_COUNT = 10, + EXTRA_COUNT = 10, + }; + + // The current "generation". Static simply as a convenience for access. + // This increments on every allocation, for a simple LRU. + static int slabGeneration_; + + // An aligned large mapping of the pushbuffer in PSP RAM. + struct SlabInfo { + u32 psp_pointer_; + u32 buf_pointer_; + int last_used_; + + bool Matches(u32 bufpos) { + // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. + return buf_pointer_ == bufpos && psp_pointer_ != 0; + } + + // Automatically marks used for LRU purposes. + u32 Ptr(u32 bufpos) { + last_used_ = slabGeneration_; + return psp_pointer_ + (bufpos - buf_pointer_); + } + + int Age() const { + // If not allocated, it's as expired as it's gonna get. + if (psp_pointer_ == 0) + return std::numeric_limits::max(); + return slabGeneration_ - last_used_; + } + + bool Alloc(); + void Free(); + bool Setup(u32 bufpos); + }; + + // An adhoc mapping of the pushbuffer (either larger than a slab or straddling slabs.) + // Remember: texture data, verts, etc. must be contiguous. + struct ExtraInfo { + u32 psp_pointer_; + u32 buf_pointer_; + u32 size_; + + bool Matches(u32 bufpos, u32 sz) { + // We check psp_pointer_ because bufpos = 0 is valid, and the initial value. + return buf_pointer_ == bufpos && psp_pointer_ != 0 && size_ >= sz; + } + + u32 Ptr() { + return psp_pointer_; + } + + bool Alloc(u32 bufpos, u32 sz); + void Free(); + }; + + SlabInfo slabs_[SLAB_COUNT]; + u32 extraOffset_ = 0; + ExtraInfo extra_[EXTRA_COUNT]; +}; + +static BufMapping execMapping; + +u32 BufMapping::Map(u32 bufpos, u32 sz) { + int slab1 = bufpos / SLAB_SIZE; + int slab2 = (bufpos + sz - 1) / SLAB_SIZE; + + if (slab1 == slab2) { + // Doesn't straddle, so we can just map to a slab. + return MapSlab(bufpos); + } else { + // We need contiguous, so we'll just allocate separately. + return MapExtra(bufpos, sz); + } +} + +u32 BufMapping::MapSlab(u32 bufpos) { + u32 slab_pos = (bufpos / SLAB_SIZE) * SLAB_SIZE; + + int best = 0; + for (int i = 0; i < SLAB_COUNT; ++i) { + if (slabs_[i].Matches(slab_pos)) { + return slabs_[i].Ptr(bufpos); + } + + if (slabs_[i].Age() > slabs_[best].Age()) { + best = i; + } + } + + // Okay, we need to allocate. + if (!slabs_[best].Setup(slab_pos)) { + return 0; + } + return slabs_[best].Ptr(bufpos); +} + +u32 BufMapping::MapExtra(u32 bufpos, u32 sz) { + for (int i = 0; i < EXTRA_COUNT; ++i) { + // Might be likely to reuse larger buffers straddling slabs. + if (extra_[i].Matches(bufpos, sz)) { + return extra_[i].Ptr(); + } + } + + int i = extraOffset_; + extraOffset_ = (extraOffset_ + 1) % EXTRA_COUNT; + + if (!extra_[i].Alloc(bufpos, sz)) { + // Let's try to power on - hopefully none of these are still in use. + for (int i = 0; i < EXTRA_COUNT; ++i) { + extra_[i].Free(); + } + if (!extra_[i].Alloc(bufpos, sz)) { + return 0; + } + } + return extra_[i].Ptr(); +} + +bool BufMapping::SlabInfo::Alloc() { + u32 sz = SLAB_SIZE; + psp_pointer_ = userMemory.Alloc(sz, false, "Slab"); + if (psp_pointer_ == -1) { + psp_pointer_ = 0; + } + return psp_pointer_ != 0; +} + +void BufMapping::SlabInfo::Free() { + if (psp_pointer_) { + userMemory.Free(psp_pointer_); + psp_pointer_ = 0; + buf_pointer_ = 0; + last_used_ = 0; + } +} + +bool BufMapping::ExtraInfo::Alloc(u32 bufpos, u32 sz) { + // Make sure we've freed any previous allocation first. + Free(); + + u32 allocSize = sz; + psp_pointer_ = userMemory.Alloc(allocSize, false, "Straddle extra"); + if (psp_pointer_ == -1) { + psp_pointer_ = 0; + } + if (psp_pointer_ == 0) { + return false; + } + + buf_pointer_ = bufpos; + size_ = sz; + Memory::MemcpyUnchecked(psp_pointer_, pushbuf.data() + bufpos, sz); + return true; +} + +void BufMapping::ExtraInfo::Free() { + if (psp_pointer_) { + userMemory.Free(psp_pointer_); + psp_pointer_ = 0; + buf_pointer_ = 0; + } +} + +bool BufMapping::SlabInfo::Setup(u32 bufpos) { + // If it already has RAM, we're simply taking it over. Slabs come only in one size. + if (psp_pointer_ == 0) { + if (!Alloc()) { + return false; + } + } + + buf_pointer_ = bufpos; + u32 sz = std::min((u32)SLAB_SIZE, (u32)pushbuf.size() - bufpos); + Memory::MemcpyUnchecked(psp_pointer_, pushbuf.data() + bufpos, sz); + + slabGeneration_++; + last_used_ = slabGeneration_; + return true; +} + +int BufMapping::slabGeneration_ = 0; + +static void FlushRegisters() { + if (!lastRegisters.empty()) { + Command last{CommandType::REGISTERS}; + last.ptr = (u32)pushbuf.size(); + last.sz = (u32)(lastRegisters.size() * sizeof(u32)); + pushbuf.resize(pushbuf.size() + last.sz); + memcpy(pushbuf.data() + last.ptr, lastRegisters.data(), last.sz); + lastRegisters.clear(); + + commands.push_back(last); + } +} + +static std::string GenRecordingFilename() { + const std::string dumpDir = GetSysDirectory(DIRECTORY_DUMP); + const std::string prefix = dumpDir + "/" + g_paramSFO.GetDiscID(); + + File::CreateFullPath(dumpDir); + + for (int n = 1; n < 10000; ++n) { + std::string filename = StringFromFormat("%s_%04d.ppdmp", prefix.c_str(), n); + if (!File::Exists(filename)) { + return filename; + } + } + + return StringFromFormat("%s_%04d.ppdmp", prefix.c_str(), 9999); +} + +static void EmitDisplayBuf() { + struct DisplayBufData { + PSPPointer topaddr; + u32 linesize, pixelFormat; + }; + + DisplayBufData disp{}; + __DisplayGetFramebuf(&disp.topaddr, &disp.linesize, &disp.pixelFormat, 0); + + u32 ptr = (u32)pushbuf.size(); + u32 sz = (u32)sizeof(disp); + pushbuf.resize(pushbuf.size() + sz); + memcpy(pushbuf.data() + ptr, &disp, sz); + + commands.push_back({CommandType::DISPLAY, sz, ptr}); +} + +static void BeginRecording() { + u32 ptr = (u32)pushbuf.size(); + u32 sz = 512 * 4; + pushbuf.resize(pushbuf.size() + sz); + gstate.Save((u32_le *)(pushbuf.data() + ptr)); + + commands.push_back({CommandType::INIT, sz, ptr}); +} + +static void WriteCompressed(FILE *fp, const void *p, size_t sz) { + size_t compressed_size = snappy_max_compressed_length(sz); + u8 *compressed = new u8[compressed_size]; + snappy_compress((const char *)p, sz, (char *)compressed, &compressed_size); + + u32 write_size = (u32)compressed_size; + fwrite(&write_size, sizeof(write_size), 1, fp); + fwrite(compressed, compressed_size, 1, fp); + + delete [] compressed; +} + +static void WriteRecording() { + FlushRegisters(); + EmitDisplayBuf(); + + const std::string filename = GenRecordingFilename(); + FILE *fp = File::OpenCFile(filename, "wb"); + fwrite(HEADER, sizeof(HEADER), 1, fp); + fwrite(&VERSION, sizeof(VERSION), 1, fp); + + u32 sz = (u32)commands.size(); + fwrite(&sz, sizeof(sz), 1, fp); + u32 bufsz = (u32)pushbuf.size(); + fwrite(&bufsz, sizeof(bufsz), 1, fp); + + WriteCompressed(fp, commands.data(), commands.size() * sizeof(Command)); + WriteCompressed(fp, pushbuf.data(), bufsz); + + fclose(fp); +} + +static void GetVertDataSizes(int vcount, const void *indices, u32 &vbytes, u32 &ibytes) { + VertexDecoder vdec; + VertexDecoderOptions opts{}; + vdec.SetVertexType(gstate.vertType, opts); + + if (indices) { + u16 lower = 0; + u16 upper = 0; + GetIndexBounds(indices, vcount, gstate.vertType, &lower, &upper); + + vbytes = (upper + 1) * vdec.VertexSize(); + u32 idx = gstate.vertType & GE_VTYPE_IDX_MASK; + if (idx == GE_VTYPE_IDX_8BIT) { + ibytes = vcount * sizeof(u8); + } else if (idx == GE_VTYPE_IDX_16BIT) { + ibytes = vcount * sizeof(u16); + } else if (idx == GE_VTYPE_IDX_32BIT) { + ibytes = vcount * sizeof(u32); + } + } else { + vbytes = vcount * vdec.VertexSize(); + } +} + +static const u8 *mymemmem(const u8 *haystack, size_t hlen, const u8 *needle, size_t nlen) { + if (!nlen) { + return nullptr; + } + + const u8 *last_possible = haystack + hlen - nlen; + int first = *needle; + const u8 *p = haystack; + while (p <= last_possible) { + p = (const u8 *)memchr(p, first, last_possible - p + 1); + if (!p) { + return nullptr; + } + if (!memcmp(p, needle, nlen)) { + return p; + } + + p++; + } + + return nullptr; +} + +static Command EmitCommandWithRAM(CommandType t, const void *p, u32 sz) { + FlushRegisters(); + + Command cmd{t, sz, 0}; + + if (sz) { + // If at all possible, try to find it already in the buffer. + const u8 *prev = nullptr; + const size_t NEAR_WINDOW = std::max((int)sz * 2, 1024 * 10); + // Let's try nearby first... it will often be nearby. + if (pushbuf.size() > NEAR_WINDOW) { + prev = mymemmem(pushbuf.data() + pushbuf.size() - NEAR_WINDOW, NEAR_WINDOW, (const u8 *)p, sz); + } + if (!prev) { + prev = mymemmem(pushbuf.data(), pushbuf.size(), (const u8 *)p, sz); + } + + if (prev) { + cmd.ptr = (u32)(prev - pushbuf.data()); + } else { + cmd.ptr = (u32)pushbuf.size(); + int pad = 0; + if (cmd.ptr & 0xF) { + pad = 0x10 - (cmd.ptr & 0xF); + cmd.ptr += pad; + } + pushbuf.resize(pushbuf.size() + sz + pad); + if (pad) { + memset(pushbuf.data() + cmd.ptr - pad, 0, pad); + } + memcpy(pushbuf.data() + cmd.ptr, p, sz); + } + } + + commands.push_back(cmd); + + return cmd; +} + +static void EmitTextureData(int level, u32 texaddr) { + GETextureFormat format = gstate.getTextureFormat(); + int w = gstate.getTextureWidth(level); + int h = gstate.getTextureHeight(level); + int bufw = GetTextureBufw(level, texaddr, format); + int extraw = w > bufw ? w - bufw : 0; + u32 sizeInRAM = (textureBitsPerPixel[format] * (bufw * h + extraw)) / 8; + + u32 bytes = Memory::ValidSize(texaddr, sizeInRAM); + if (Memory::IsValidAddress(texaddr)) { + FlushRegisters(); + + CommandType type = CommandType((int)CommandType::TEXTURE0 + level); + const u8 *p = Memory::GetPointerUnchecked(texaddr); + + // Dumps are huge - let's try to find this already emitted. + for (u32 prevptr : lastTextures) { + if (pushbuf.size() < prevptr + bytes) { + continue; + } + + if (memcmp(pushbuf.data() + prevptr, p, bytes) == 0) { + commands.push_back({type, bytes, prevptr}); + // Okay, that was easy. Bail out. + return; + } + } + + // Not there, gotta emit anew. + Command cmd = EmitCommandWithRAM(type, p, bytes); + lastTextures.push_back(cmd.ptr); + } +} + +static void FlushPrimState(int vcount) { + // TODO: Eventually, how do we handle texturing from framebuf/zbuf? + // TODO: Do we need to preload color/depth/stencil (in case from last frame)? + + // We re-flush textures always in case the game changed them... kinda expensive. + // TODO: Dirty textures on transfer/stall/etc. somehow? + // TODO: Or maybe de-dup by validating if it has changed? + for (int level = 0; level < 8; ++level) { + u32 texaddr = gstate.getTextureAddress(level); + if (texaddr) { + EmitTextureData(level, texaddr); + } + } + + const void *verts = Memory::GetPointer(gstate_c.vertexAddr); + const void *indices = nullptr; + if ((gstate.vertType & GE_VTYPE_IDX_MASK) != GE_VTYPE_IDX_NONE) { + indices = Memory::GetPointer(gstate_c.indexAddr); + } + + u32 ibytes = 0; + u32 vbytes = 0; + GetVertDataSizes(vcount, indices, vbytes, ibytes); + + if (indices) { + EmitCommandWithRAM(CommandType::INDICES, indices, ibytes); + } + if (verts) { + EmitCommandWithRAM(CommandType::VERTICES, verts, vbytes); + } +} + +static void EmitTransfer(u32 op) { + FlushRegisters(); + + // This may not make a lot of sense right now, unless it's to a framebuf... + if (!Memory::IsVRAMAddress(gstate.getTransferDstAddress())) { + // Skip, not VRAM, so can't affect drawing (we flush textures each prim.) + return; + } + + u32 srcBasePtr = gstate.getTransferSrcAddress(); + u32 srcStride = gstate.getTransferSrcStride(); + int srcX = gstate.getTransferSrcX(); + int srcY = gstate.getTransferSrcY(); + int width = gstate.getTransferWidth(); + int height = gstate.getTransferHeight(); + int bpp = gstate.getTransferBpp(); + + u32 srcBytes = ((srcY + height - 1) * srcStride + (srcX + width)) * bpp; + srcBytes = Memory::ValidSize(srcBasePtr, srcBytes); + + EmitCommandWithRAM(CommandType::TRANSFERSRC, Memory::GetPointerUnchecked(srcBasePtr), srcBytes); + + lastRegisters.push_back(op); +} + +static void EmitClut(u32 op) { + u32 addr = gstate.getClutAddress(); + u32 bytes = (op & 0x3F) * 32; + bytes = Memory::ValidSize(addr, bytes); + + EmitCommandWithRAM(CommandType::CLUT, Memory::GetPointerUnchecked(addr), bytes); + + lastRegisters.push_back(op); +} + +static void EmitPrim(u32 op) { + FlushPrimState(op & 0x0000FFFF); + + lastRegisters.push_back(op); +} + +static void EmitBezierSpline(u32 op) { + int ucount = op & 0xFF; + int vcount = (op >> 8) & 0xFF; + FlushPrimState(ucount * vcount); + + lastRegisters.push_back(op); +} + +bool IsActive() { + return active; +} + +void Activate() { + nextFrame = true; +} + +void NotifyCommand(u32 pc) { + if (!active) { + return; + } + if (writePending) { + WriteRecording(); + commands.clear(); + pushbuf.clear(); + + writePending = false; + // We're done - this was just to write the result out. + NOTICE_LOG(SYSTEM, "Recording finished"); + active = false; + return; + } + + const u32 op = Memory::Read_U32(pc); + const GECommand cmd = GECommand(op >> 24); + + switch (cmd) { + case GE_CMD_VADDR: + case GE_CMD_IADDR: + case GE_CMD_JUMP: + case GE_CMD_CALL: + case GE_CMD_RET: + case GE_CMD_END: + case GE_CMD_SIGNAL: + case GE_CMD_FINISH: + case GE_CMD_BASE: + case GE_CMD_OFFSETADDR: + case GE_CMD_ORIGIN: + // These just prepare future commands, and are flushed with those commands. + // TODO: Maybe add a command just to log that these were hit? + break; + + case GE_CMD_BOUNDINGBOX: + case GE_CMD_BJUMP: + // Since we record each command, this is theoretically not relevant. + // TODO: Output a CommandType to validate this. + break; + + case GE_CMD_PRIM: + EmitPrim(op); + break; + + case GE_CMD_BEZIER: + case GE_CMD_SPLINE: + EmitBezierSpline(op); + break; + + case GE_CMD_LOADCLUT: + EmitClut(op); + break; + + case GE_CMD_TRANSFERSTART: + EmitTransfer(op); + break; + + default: + lastRegisters.push_back(op); + break; + } +} + +void NotifyMemcpy(u32 dest, u32 src, u32 sz) { + if (!active) { + return; + } + if (Memory::IsVRAMAddress(dest)) { + FlushRegisters(); + Command cmd{CommandType::MEMCPYDEST, sizeof(dest), (u32)pushbuf.size()}; + pushbuf.resize(pushbuf.size() + sizeof(dest)); + memcpy(pushbuf.data() + cmd.ptr, &dest, sizeof(dest)); + + sz = Memory::ValidSize(dest, sz); + EmitCommandWithRAM(CommandType::MEMCPYDATA, Memory::GetPointer(dest), sz); + } +} + +void NotifyMemset(u32 dest, int v, u32 sz) { + if (!active) { + return; + } + struct MemsetCommand { + u32 dest; + int value; + u32 sz; + }; + + if (Memory::IsVRAMAddress(dest)) { + sz = Memory::ValidSize(dest, sz); + MemsetCommand data{dest, v, sz}; + + FlushRegisters(); + Command cmd{CommandType::MEMSET, sizeof(data), (u32)pushbuf.size()}; + pushbuf.resize(pushbuf.size() + sizeof(data)); + memcpy(pushbuf.data() + cmd.ptr, &data, sizeof(data)); + } +} + +void NotifyUpload(u32 dest, u32 sz) { + if (!active) { + return; + } + NotifyMemcpy(dest, dest, sz); +} + +void NotifyFrame() { + if (active && !writePending) { + // Delay write until the first command of the next frame, so we get the right display buf. + NOTICE_LOG(SYSTEM, "Recording complete - waiting to get display buffer"); + writePending = true; + } + if (nextFrame) { + NOTICE_LOG(SYSTEM, "Recording starting..."); + active = true; + nextFrame = false; + lastTextures.clear(); + BeginRecording(); + } +} + +static bool ExecuteSubmitCmds(void *p, u32 sz) { + if (execListBuf == 0) { + u32 allocSize = LIST_BUF_SIZE; + execListBuf = userMemory.Alloc(allocSize, "List buf"); + if (execListBuf == -1) { + execListBuf = 0; + } + if (execListBuf == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for display list"); + return false; + } + + execListPos = execListBuf; + Memory::Write_U32(GE_CMD_NOP << 24, execListPos); + execListPos += 4; + + gpu->EnableInterrupts(false); + auto optParam = PSPPointer::Create(0); + execListID = gpu->EnqueueList(execListBuf, execListPos, -1, optParam, false); + gpu->EnableInterrupts(true); + } + + u32 pendingSize = (int)execListQueue.size() * sizeof(u32); + // Validate space for jump. + u32 allocSize = pendingSize + sz + 8; + if (execListPos + allocSize >= execListBuf + LIST_BUF_SIZE) { + Memory::Write_U32((GE_CMD_BASE << 24) | ((execListBuf >> 8) & 0x00FF0000), execListPos); + Memory::Write_U32((GE_CMD_JUMP << 24) | (execListBuf & 0x00FFFFFF), execListPos + 4); + + execListPos = execListBuf; + } + + Memory::MemcpyUnchecked(execListPos, execListQueue.data(), pendingSize); + execListPos += pendingSize; + Memory::MemcpyUnchecked(execListPos, p, sz); + execListPos += sz; + + execListQueue.clear(); + gpu->UpdateStall(execListID, execListPos); + s64 listTicks = gpu->GetListTicks(execListID); + if (listTicks != -1) { + currentMIPS->downcount -= listTicks - CoreTiming::GetTicks(); + } + + // Make sure downcount doesn't overflow. + CoreTiming::ForceCheck(); + + return true; +} + +static void ExecuteSubmitListEnd() { + if (execListPos == 0) { + return; + } + + // There's always space for the end, same size as a jump. + Memory::Write_U32(GE_CMD_FINISH << 24, execListPos); + Memory::Write_U32(GE_CMD_END << 24, execListPos + 4); + execListPos += 8; + + gpu->UpdateStall(execListID, execListPos); + currentMIPS->downcount -= gpu->GetListTicks(execListID) - CoreTiming::GetTicks(); + + gpu->ListSync(execListID, 0); + + // Make sure downcount doesn't overflow. + CoreTiming::ForceCheck(); +} + +static void ExecuteInit(u32 ptr, u32 sz) { + gstate.Restore((u32_le *)(pushbuf.data() + ptr)); + gpu->ReapplyGfxState(); +} + +static void ExecuteRegisters(u32 ptr, u32 sz) { + ExecuteSubmitCmds(pushbuf.data() + ptr, sz); +} + +static void ExecuteVertices(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for vertices"); + return; + } + + execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_VADDR << 24) | (psp & 0x00FFFFFF)); +} + +static void ExecuteIndices(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for indices"); + return; + } + + execListQueue.push_back((GE_CMD_BASE << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_IADDR << 24) | (psp & 0x00FFFFFF)); +} + +static void ExecuteClut(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for clut"); + return; + } + + execListQueue.push_back((GE_CMD_CLUTADDRUPPER << 24) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back((GE_CMD_CLUTADDR << 24) | (psp & 0x00FFFFFF)); +} + +static void ExecuteTransferSrc(u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for transfer"); + return; + } + + execListQueue.push_back((gstate.transfersrcw & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back(((GE_CMD_TRANSFERSRC) << 24) | (psp & 0x00FFFFFF)); +} + +static void ExecuteMemset(u32 ptr, u32 sz) { + struct MemsetCommand { + u32 dest; + int value; + u32 sz; + }; + + const MemsetCommand *data = (const MemsetCommand *)(pushbuf.data() + ptr); + + if (Memory::IsVRAMAddress(data->dest)) { + gpu->PerformMemorySet(data->dest, (u8)data->value, data->sz); + } +} + +static void ExecuteMemcpyDest(u32 ptr, u32 sz) { + execMemcpyDest = *(const u32 *)(pushbuf.data() + ptr); +} + +static void ExecuteMemcpy(u32 ptr, u32 sz) { + if (Memory::IsVRAMAddress(execMemcpyDest)) { + Memory::MemcpyUnchecked(execMemcpyDest, pushbuf.data() + ptr, sz); + gpu->PerformMemoryUpload(execMemcpyDest, sz); + } +} + +static void ExecuteTexture(int level, u32 ptr, u32 sz) { + u32 psp = execMapping.Map(ptr, sz); + if (psp == 0) { + ERROR_LOG(SYSTEM, "Unable to allocate for texture"); + return; + } + + execListQueue.push_back((gstate.texbufwidth[level] & 0xFF00FFFF) | ((psp >> 8) & 0x00FF0000)); + execListQueue.push_back(((GE_CMD_TEXADDR0 + level) << 24) | (psp & 0x00FFFFFF)); +} + +static void ExecuteDisplay(u32 ptr, u32 sz) { + struct DisplayBufData { + PSPPointer topaddr; + u32 linesize, pixelFormat; + }; + + DisplayBufData *disp = (DisplayBufData *)(pushbuf.data() + ptr); + + __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 1); + __DisplaySetFramebuf(disp->topaddr.ptr, disp->linesize, disp->pixelFormat, 0); +} + +static void ExecuteFree() { + execMemcpyDest = 0; + if (execListBuf) { + userMemory.Free(execListBuf); + execListBuf = 0; + } + execListPos = 0; + execMapping.Reset(); + + commands.clear(); + pushbuf.clear(); +} + +static bool ExecuteCommands() { + for (const Command &cmd : commands) { + switch (cmd.type) { + case CommandType::INIT: + ExecuteInit(cmd.ptr, cmd.sz); + break; + + case CommandType::REGISTERS: + ExecuteRegisters(cmd.ptr, cmd.sz); + break; + + case CommandType::VERTICES: + ExecuteVertices(cmd.ptr, cmd.sz); + break; + + case CommandType::INDICES: + ExecuteIndices(cmd.ptr, cmd.sz); + break; + + case CommandType::CLUT: + ExecuteClut(cmd.ptr, cmd.sz); + break; + + case CommandType::TRANSFERSRC: + ExecuteTransferSrc(cmd.ptr, cmd.sz); + break; + + case CommandType::MEMSET: + ExecuteMemset(cmd.ptr, cmd.sz); + break; + + case CommandType::MEMCPYDEST: + ExecuteMemcpyDest(cmd.ptr, cmd.sz); + break; + + case CommandType::MEMCPYDATA: + ExecuteMemcpy(cmd.ptr, cmd.sz); + break; + + case CommandType::TEXTURE0: + case CommandType::TEXTURE1: + case CommandType::TEXTURE2: + case CommandType::TEXTURE3: + case CommandType::TEXTURE4: + case CommandType::TEXTURE5: + case CommandType::TEXTURE6: + case CommandType::TEXTURE7: + ExecuteTexture((int)cmd.type - (int)CommandType::TEXTURE0, cmd.ptr, cmd.sz); + break; + + case CommandType::DISPLAY: + ExecuteDisplay(cmd.ptr, cmd.sz); + break; + + default: + ERROR_LOG(SYSTEM, "Unsupported GE dump command: %d", cmd.type); + return false; + } + } + + ExecuteSubmitListEnd(); + return true; +} + +static bool ReadCompressed(u32 fp, void *dest, size_t sz) { + u32 compressed_size = 0; + if (pspFileSystem.ReadFile(fp, (u8 *)&compressed_size, sizeof(compressed_size)) != sizeof(compressed_size)) { + return false; + } + + u8 *compressed = new u8[compressed_size]; + if (pspFileSystem.ReadFile(fp, compressed, compressed_size) != compressed_size) { + delete [] compressed; + return false; + } + + size_t real_size = sz; + snappy_uncompress((const char *)compressed, compressed_size, (char *)dest, &real_size); + delete [] compressed; + + return real_size == sz; +} + +bool RunMountedReplay(const std::string &filename) { + _assert_msg_(SYSTEM, !active && !nextFrame, "Cannot run replay while recording."); + + u32 fp = pspFileSystem.OpenFile(filename, FILEACCESS_READ); + u8 header[8]{}; + int version = 0; + pspFileSystem.ReadFile(fp, header, sizeof(header)); + pspFileSystem.ReadFile(fp, (u8 *)&version, sizeof(version)); + + if (memcmp(header, HEADER, sizeof(HEADER)) != 0 || version != VERSION) { + ERROR_LOG(SYSTEM, "Invalid GE dump or unsupported version"); + pspFileSystem.CloseFile(fp); + return false; + } + + u32 sz = 0; + pspFileSystem.ReadFile(fp, (u8 *)&sz, sizeof(sz)); + u32 bufsz = 0; + pspFileSystem.ReadFile(fp, (u8 *)&bufsz, sizeof(bufsz)); + + commands.resize(sz); + pushbuf.resize(bufsz); + + bool truncated = false; + truncated = truncated || !ReadCompressed(fp, commands.data(), sizeof(Command) * sz); + truncated = truncated || !ReadCompressed(fp, pushbuf.data(), bufsz); + + pspFileSystem.CloseFile(fp); + + if (truncated) { + ERROR_LOG(SYSTEM, "Truncated GE dump"); + ExecuteFree(); + return false; + } + + bool success = ExecuteCommands(); + ExecuteFree(); + return success; +} + +}; diff --git a/GPU/Debugger/Record.h b/GPU/Debugger/Record.h new file mode 100644 index 0000000000..3cfc995a80 --- /dev/null +++ b/GPU/Debugger/Record.h @@ -0,0 +1,36 @@ +// Copyright (c) 2017- 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 "Common/CommonTypes.h" + +namespace GPURecord { + +bool IsActive(); +void Activate(); + +void NotifyCommand(u32 pc); +void NotifyMemcpy(u32 dest, u32 src, u32 sz); +void NotifyMemset(u32 dest, int v, u32 sz); +void NotifyUpload(u32 dest, u32 sz); +void NotifyFrame(); + +bool RunMountedReplay(const std::string &filename); + +}; diff --git a/GPU/Directx9/GPU_DX9.cpp b/GPU/Directx9/GPU_DX9.cpp index a6453d4c4d..4e1da0aff3 100644 --- a/GPU/Directx9/GPU_DX9.cpp +++ b/GPU/Directx9/GPU_DX9.cpp @@ -260,10 +260,6 @@ void GPU_DX9::InitClearInternal() { } } -void GPU_DX9::DumpNextFrame() { - dumpNextFrame_ = true; -} - void GPU_DX9::BeginHostFrame() { GPUCommon::BeginHostFrame(); UpdateCmdInfo(); @@ -276,10 +272,6 @@ void GPU_DX9::BeginHostFrame() { } } -void GPU_DX9::BeginFrame() { - ScheduleEvent(GPU_EVENT_BEGIN_FRAME); -} - void GPU_DX9::ReapplyGfxStateInternal() { dxstate.Restore(); GPUCommon::ReapplyGfxStateInternal(); @@ -300,13 +292,7 @@ void GPU_DX9::BeginFrameInternal() { depalShaderCache_.Decimate(); // fragmentTestCache_.Decimate(); - if (dumpNextFrame_) { - NOTICE_LOG(G3D, "DUMPING THIS FRAME"); - dumpThisFrame_ = true; - dumpNextFrame_ = false; - } else if (dumpThisFrame_) { - dumpThisFrame_ = false; - } + GPUCommon::BeginFrameInternal(); shaderManagerDX9_->DirtyShader(); framebufferManagerDX9_->BeginFrame(); diff --git a/GPU/Directx9/GPU_DX9.h b/GPU/Directx9/GPU_DX9.h index 0cfad05701..bee3fba0ac 100644 --- a/GPU/Directx9/GPU_DX9.h +++ b/GPU/Directx9/GPU_DX9.h @@ -43,13 +43,11 @@ public: void ReapplyGfxStateInternal() override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; - void BeginFrame() override; void GetStats(char *buffer, size_t bufsize) override; void ClearCacheNextFrame() override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DumpNextFrame() override; void DoState(PointerWrap &p) override; void ClearShaderCache() override; diff --git a/GPU/GLES/GPU_GLES.cpp b/GPU/GLES/GPU_GLES.cpp index 1f40a0299b..8247b41118 100644 --- a/GPU/GLES/GPU_GLES.cpp +++ b/GPU/GLES/GPU_GLES.cpp @@ -168,10 +168,7 @@ GPU_GLES::GPU_GLES(GraphicsContext *gfxCtx, Draw::DrawContext *draw) textureCacheGL_->NotifyConfigChanged(); // Load shader cache. - std::string discID = g_paramSFO.GetValueString("DISC_ID"); - if (discID.empty()) { - discID = g_paramSFO.GenerateFakeID(); - } + std::string discID = g_paramSFO.GetDiscID(); if (discID.size()) { File::CreateFullPath(GetSysDirectory(DIRECTORY_APP_CACHE)); shaderCachePath_ = GetSysDirectory(DIRECTORY_APP_CACHE) + "/" + discID + ".glshadercache"; @@ -422,10 +419,6 @@ void GPU_GLES::InitClearInternal() { glstate.viewport.set(0, 0, PSP_CoreParameter().pixelWidth, PSP_CoreParameter().pixelHeight); } -void GPU_GLES::DumpNextFrame() { - dumpNextFrame_ = true; -} - void GPU_GLES::BeginHostFrame() { GPUCommon::BeginHostFrame(); UpdateCmdInfo(); @@ -438,10 +431,6 @@ void GPU_GLES::BeginHostFrame() { } } -void GPU_GLES::BeginFrame() { - ScheduleEvent(GPU_EVENT_BEGIN_FRAME); -} - inline void GPU_GLES::UpdateVsyncInterval(bool force) { #ifdef _WIN32 int desiredVSyncInterval = g_Config.bVSync ? 1 : 0; @@ -495,13 +484,7 @@ void GPU_GLES::BeginFrameInternal() { depalShaderCache_.Decimate(); fragmentTestCache_.Decimate(); - if (dumpNextFrame_) { - NOTICE_LOG(G3D, "DUMPING THIS FRAME"); - dumpThisFrame_ = true; - dumpNextFrame_ = false; - } else if (dumpThisFrame_) { - dumpThisFrame_ = false; - } + GPUCommon::BeginFrameInternal(); // Save the cache from time to time. TODO: How often? if (!shaderCachePath_.empty() && (gpuStats.numFlips & 1023) == 0) { diff --git a/GPU/GLES/GPU_GLES.h b/GPU/GLES/GPU_GLES.h index dad9997910..b94bd8ecec 100644 --- a/GPU/GLES/GPU_GLES.h +++ b/GPU/GLES/GPU_GLES.h @@ -43,14 +43,12 @@ public: void ReapplyGfxStateInternal() override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; - void BeginFrame() override; void GetStats(char *buffer, size_t bufsize) override; void ClearCacheNextFrame() override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DumpNextFrame() override; void DoState(PointerWrap &p) override; void ClearShaderCache() override; diff --git a/GPU/GPU.vcxproj b/GPU/GPU.vcxproj index dcf471a173..b53ade9739 100644 --- a/GPU/GPU.vcxproj +++ b/GPU/GPU.vcxproj @@ -218,6 +218,7 @@ + @@ -318,6 +319,7 @@ + diff --git a/GPU/GPU.vcxproj.filters b/GPU/GPU.vcxproj.filters index 2499cfab12..cbbd189fe9 100644 --- a/GPU/GPU.vcxproj.filters +++ b/GPU/GPU.vcxproj.filters @@ -262,6 +262,9 @@ Software + + Debugger + @@ -519,5 +522,8 @@ Software + + Debugger + \ No newline at end of file diff --git a/GPU/GPUCommon.cpp b/GPU/GPUCommon.cpp index d3cea915f9..8dacea143d 100644 --- a/GPU/GPUCommon.cpp +++ b/GPU/GPUCommon.cpp @@ -25,6 +25,7 @@ #include "GPU/Common/FramebufferCommon.h" #include "GPU/Common/TextureCacheCommon.h" #include "GPU/Common/DrawEngineCommon.h" +#include "GPU/Debugger/Record.h" const CommonCommandTableEntry commonCommandTable[] = { // From Common. No flushing but definitely need execute. @@ -461,6 +462,10 @@ void GPUCommon::Resized() { resized_ = true; } +void GPUCommon::DumpNextFrame() { + dumpNextFrame_ = true; +} + u32 GPUCommon::DrawSync(int mode) { if (ThreadEnabled()) { // Sync first, because the CPU is usually faster than the emulated GPU. @@ -898,7 +903,8 @@ bool GPUCommon::InterpretList(DisplayList &list) { gpuState = list.pc == list.stall ? GPUSTATE_STALL : GPUSTATE_RUNNING; guard.unlock(); - const bool useDebugger = host->GPUDebuggingActive(); + debugRecording_ = GPURecord::IsActive(); + const bool useDebugger = host->GPUDebuggingActive() || debugRecording_; const bool useFastRunLoop = !dumpThisFrame_ && !useDebugger; while (gpuState == GPUSTATE_RUNNING) { { @@ -945,12 +951,28 @@ bool GPUCommon::InterpretList(DisplayList &list) { return gpuState == GPUSTATE_DONE || gpuState == GPUSTATE_ERROR; } +void GPUCommon::BeginFrame() { + ScheduleEvent(GPU_EVENT_BEGIN_FRAME); +} + +void GPUCommon::BeginFrameInternal() { + if (dumpNextFrame_) { + NOTICE_LOG(G3D, "DUMPING THIS FRAME"); + dumpThisFrame_ = true; + dumpNextFrame_ = false; + } else if (dumpThisFrame_) { + dumpThisFrame_ = false; + } + GPURecord::NotifyFrame(); +} + void GPUCommon::SlowRunLoop(DisplayList &list) { const bool dumpThisFrame = dumpThisFrame_; while (downcount > 0) { host->GPUNotifyCommand(list.pc); + GPURecord::NotifyCommand(list.pc); u32 op = Memory::ReadUnchecked_U32(list.pc); u32 cmd = op >> 24; @@ -1197,13 +1219,16 @@ void GPUCommon::Execute_Call(u32 op, u32 diff) { #endif // Bone matrix optimization - many games will CALL a bone matrix (!). - if ((Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA) { + // We don't optimize during recording - so the matrix data gets recorded. + if (!debugRecording_ && (Memory::ReadUnchecked_U32(target) >> 24) == GE_CMD_BONEMATRIXDATA) { // Check for the end if ((Memory::ReadUnchecked_U32(target + 11 * 4) >> 24) == GE_CMD_BONEMATRIXDATA && (Memory::ReadUnchecked_U32(target + 12 * 4) >> 24) == GE_CMD_RET) { - // Yep, pretty sure this is a bone matrix call. - FastLoadBoneMatrix(target); - return; + // Yep, pretty sure this is a bone matrix call. Double check stall first. + if (target > currentList->stall || target + 12 * 4 < currentList->stall) { + FastLoadBoneMatrix(target); + return; + } } } @@ -1553,15 +1578,23 @@ void GPUCommon::Execute_WorldMtxNum(u32 op, u32 diff) { const int end = 12 - (op & 0xF); int i = 0; - while ((src[i] >> 24) == GE_CMD_WORLDMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_WORLDMATRIX); - } - if (++i >= end) { - break; + // We must record the individual data commands while debugRecording_. + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_WORLDMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_WORLDMATRIX); + } + if (++i >= end) { + break; + } } } @@ -1594,15 +1627,22 @@ void GPUCommon::Execute_ViewMtxNum(u32 op, u32 diff) { const int end = 12 - (op & 0xF); int i = 0; - while ((src[i] >> 24) == GE_CMD_VIEWMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_VIEWMATRIX); - } - if (++i >= end) { - break; + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_VIEWMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_VIEWMATRIX); + } + if (++i >= end) { + break; + } } } @@ -1635,15 +1675,22 @@ void GPUCommon::Execute_ProjMtxNum(u32 op, u32 diff) { const int end = 16 - (op & 0xF); int i = 0; - while ((src[i] >> 24) == GE_CMD_PROJMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_PROJMATRIX); - } - if (++i >= end) { - break; + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_PROJMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_PROJMATRIX); + } + if (++i >= end) { + break; + } } } @@ -1677,15 +1724,22 @@ void GPUCommon::Execute_TgenMtxNum(u32 op, u32 diff) { const int end = 12 - (op & 0xF); int i = 0; - while ((src[i] >> 24) == GE_CMD_TGENMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - gstate_c.Dirty(DIRTY_TEXMATRIX); - } - if (++i >= end) { - break; + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } + + if (fastLoad) { + while ((src[i] >> 24) == GE_CMD_TGENMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + gstate_c.Dirty(DIRTY_TEXMATRIX); + } + if (++i >= end) { + break; + } } } @@ -1718,34 +1772,41 @@ void GPUCommon::Execute_BoneMtxNum(u32 op, u32 diff) { const int end = 12 * 8 - (op & 0x7F); int i = 0; - // If we can't use software skinning, we have to flush and dirty. - if (!g_Config.bSoftwareSkinning || (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) != 0) { - while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { - const u32 newVal = src[i] << 8; - if (dst[i] != newVal) { - Flush(); - dst[i] = newVal; - } - if (++i >= end) { - break; - } - } + bool fastLoad = !debugRecording_; + if (currentList->pc < currentList->stall && currentList->pc + end * 4 >= currentList->stall) { + fastLoad = false; + } - const int numPlusCount = (op & 0x7F) + i; - for (int num = op & 0x7F; num < numPlusCount; num += 12) { - gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12)); - } - } else { - while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { - dst[i] = src[i] << 8; - if (++i >= end) { - break; + if (fastLoad) { + // If we can't use software skinning, we have to flush and dirty. + if (!g_Config.bSoftwareSkinning || (gstate.vertType & GE_VTYPE_MORPHCOUNT_MASK) != 0) { + while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { + const u32 newVal = src[i] << 8; + if (dst[i] != newVal) { + Flush(); + dst[i] = newVal; + } + if (++i >= end) { + break; + } } - } - const int numPlusCount = (op & 0x7F) + i; - for (int num = op & 0x7F; num < numPlusCount; num += 12) { - gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12); + const int numPlusCount = (op & 0x7F) + i; + for (int num = op & 0x7F; num < numPlusCount; num += 12) { + gstate_c.Dirty(DIRTY_BONEMATRIX0 << (num / 12)); + } + } else { + while ((src[i] >> 24) == GE_CMD_BONEMATRIXDATA) { + dst[i] = src[i] << 8; + if (++i >= end) { + break; + } + } + + const int numPlusCount = (op & 0x7F) + i; + for (int num = op & 0x7F; num < numPlusCount; num += 12) { + gstate_c.deferredVertTypeDirty |= DIRTY_BONEMATRIX0 << (num / 12); + } } } @@ -2330,6 +2391,7 @@ bool GPUCommon::PerformMemoryCopy(u32 dest, u32 src, int size) { } InvalidateCache(dest, size, GPU_INVALIDATE_HINT); + GPURecord::NotifyMemcpy(dest, src, size); return false; } @@ -2354,6 +2416,7 @@ bool GPUCommon::PerformMemorySet(u32 dest, u8 v, int size) { // Or perhaps a texture, let's invalidate. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); + GPURecord::NotifyMemset(dest, v, size); return false; } @@ -2370,6 +2433,7 @@ bool GPUCommon::PerformMemoryUpload(u32 dest, int size) { // Cheat a bit to force an upload of the framebuffer. // VRAM + 0x00400000 is simply a VRAM mirror. if (Memory::IsVRAMAddress(dest)) { + GPURecord::NotifyUpload(dest, size); return PerformMemoryCopy(dest, dest ^ 0x00400000, size); } return false; diff --git a/GPU/GPUCommon.h b/GPU/GPUCommon.h index 528e89129c..2a953ccce9 100644 --- a/GPU/GPUCommon.h +++ b/GPU/GPUCommon.h @@ -61,6 +61,7 @@ public: } void Resized() override; + void DumpNextFrame() override; void ExecuteOp(u32 op, u32 diff) override; void PreExecuteOp(u32 op, u32 diff) override; @@ -202,7 +203,7 @@ public: const std::list& GetDisplayLists() override { return dlQueue; } - virtual bool DecodeTexture(u8* dest, const GPUgstate &state) override { + bool DecodeTexture(u8* dest, const GPUgstate &state) override { return false; } std::vector GetFramebufferList() override { @@ -211,6 +212,13 @@ public: void ClearShaderCache() override {} void CleanupBeforeUI() override {} + s64 GetListTicks(int listid) override { + if (listid >= 0 && listid < DisplayListMaxCount) { + return dls[listid].waitTicks; + } + return -1; + } + std::vector DebugGetShaderIDs(DebugShaderType shader) override { return std::vector(); }; std::string DebugGetShaderString(std::string id, DebugShaderType shader, DebugShaderStringType stringType) override { return "N/A"; @@ -227,7 +235,8 @@ protected: } virtual void InitClearInternal() {} - virtual void BeginFrameInternal() {} + void BeginFrame() override; + virtual void BeginFrameInternal(); virtual void CopyDisplayToOutputInternal() {} virtual void ReinitializeInternal() {} @@ -329,6 +338,7 @@ protected: bool dumpNextFrame_; bool dumpThisFrame_; + bool debugRecording_; bool interruptsEnabled_; bool resized_; DrawType lastDraw_; diff --git a/GPU/GPUInterface.h b/GPU/GPUInterface.h index 9d7b44bd6e..ceed93eb15 100644 --- a/GPU/GPUInterface.h +++ b/GPU/GPUInterface.h @@ -299,6 +299,7 @@ public: virtual const std::list& GetDisplayLists() = 0; virtual bool DecodeTexture(u8* dest, const GPUgstate &state) = 0; virtual std::vector GetFramebufferList() = 0; + virtual s64 GetListTicks(int listid) = 0; // For debugging. The IDs returned are opaque, do not poke in them or display them in any way. virtual std::vector DebugGetShaderIDs(DebugShaderType type) = 0; diff --git a/GPU/Null/NullGpu.h b/GPU/Null/NullGpu.h index 7e00213f98..ae31ce3024 100644 --- a/GPU/Null/NullGpu.h +++ b/GPU/Null/NullGpu.h @@ -29,7 +29,6 @@ public: void InitClear() override {} void ExecuteOp(u32 op, u32 diff) override; - void BeginFrame() override {} void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override {} void CopyDisplayToOutput() override {} void GetStats(char *buffer, size_t bufsize) override; diff --git a/GPU/Software/SoftGpu.cpp b/GPU/Software/SoftGpu.cpp index 28a5a03e96..0a08068a2b 100644 --- a/GPU/Software/SoftGpu.cpp +++ b/GPU/Software/SoftGpu.cpp @@ -37,6 +37,7 @@ #include "GPU/Software/TransformUnit.h" #include "GPU/Common/DrawEngineCommon.h" #include "GPU/Common/FramebufferCommon.h" +#include "GPU/Debugger/Record.h" const int FB_WIDTH = 480; const int FB_HEIGHT = 272; @@ -876,6 +877,7 @@ bool SoftGPU::PerformMemoryCopy(u32 dest, u32 src, int size) { // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); + GPURecord::NotifyMemcpy(dest, src, size); // Let's just be safe. framebufferDirty_ = true; return false; @@ -885,6 +887,7 @@ bool SoftGPU::PerformMemorySet(u32 dest, u8 v, int size) { // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); + GPURecord::NotifyMemset(dest, v, size); // Let's just be safe. framebufferDirty_ = true; return false; @@ -901,6 +904,7 @@ bool SoftGPU::PerformMemoryUpload(u32 dest, int size) { // Nothing to update. InvalidateCache(dest, size, GPU_INVALIDATE_HINT); + GPURecord::NotifyUpload(dest, size); return false; } diff --git a/GPU/Software/SoftGpu.h b/GPU/Software/SoftGpu.h index c25e68751c..25c21da0ec 100644 --- a/GPU/Software/SoftGpu.h +++ b/GPU/Software/SoftGpu.h @@ -55,7 +55,6 @@ public: void InitClear() override {} void ExecuteOp(u32 op, u32 diff) override; - void BeginFrame() override {} void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; void CopyDisplayToOutput() override; void GetStats(char *buffer, size_t bufsize) override; @@ -70,7 +69,6 @@ public: void DeviceLost() override; void DeviceRestore() override; - void DumpNextFrame() override {} void Resized() override {} void GetReportingInfo(std::string &primaryInfo, std::string &fullInfo) override { diff --git a/GPU/Vulkan/GPU_Vulkan.cpp b/GPU/Vulkan/GPU_Vulkan.cpp index bada57a1e8..694f9c1a8f 100644 --- a/GPU/Vulkan/GPU_Vulkan.cpp +++ b/GPU/Vulkan/GPU_Vulkan.cpp @@ -329,14 +329,6 @@ void GPU_Vulkan::InitClearInternal() { } } -void GPU_Vulkan::DumpNextFrame() { - dumpNextFrame_ = true; -} - -void GPU_Vulkan::BeginFrame() { - ScheduleEvent(GPU_EVENT_BEGIN_FRAME); -} - void GPU_Vulkan::UpdateVsyncInterval(bool force) { // TODO } @@ -351,9 +343,6 @@ void GPU_Vulkan::UpdateCmdInfo() { } } -void GPU_Vulkan::BeginFrameInternal() { -} - void GPU_Vulkan::SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) { host->GPUNotifyDisplay(framebuf, stride, format); framebufferManager_->SetDisplayFramebuffer(framebuf, stride, format); diff --git a/GPU/Vulkan/GPU_Vulkan.h b/GPU/Vulkan/GPU_Vulkan.h index b056cb02e2..b6d8d4e54c 100644 --- a/GPU/Vulkan/GPU_Vulkan.h +++ b/GPU/Vulkan/GPU_Vulkan.h @@ -46,13 +46,11 @@ public: void ExecuteOp(u32 op, u32 diff) override; void SetDisplayFramebuffer(u32 framebuf, u32 stride, GEBufferFormat format) override; - void BeginFrame() override; void GetStats(char *buffer, size_t bufsize) override; void ClearCacheNextFrame() override; void DeviceLost() override; // Only happens on Android. Drop all textures and shaders. void DeviceRestore() override; - void DumpNextFrame() override; void DoState(PointerWrap &p) override; void ClearShaderCache() override; @@ -102,7 +100,6 @@ private: void CheckFlushOp(int cmd, u32 diff); void BuildReportingInfo(); void InitClearInternal() override; - void BeginFrameInternal() override; void CopyDisplayToOutputInternal() override; void ReinitializeInternal() override; inline void UpdateVsyncInterval(bool force); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index 1f8deb6119..cce054af0f 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -1336,10 +1336,7 @@ UI::EventReturn DeveloperToolsScreen::OnLoadLanguageIni(UI::EventParams &e) { } UI::EventReturn DeveloperToolsScreen::OnOpenTexturesIniFile(UI::EventParams &e) { - std::string gameID = g_paramSFO.GetValueString("DISC_ID"); - if (gameID.empty()) { - gameID = g_paramSFO.GenerateFakeID(); - } + std::string gameID = g_paramSFO.GetDiscID(); std::string texturesDirectory = GetSysDirectory(DIRECTORY_TEXTURES) + gameID + "/"; bool enabled_ = !gameID.empty(); if (enabled_) { diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index d1e21c9b05..0f293b8831 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -713,10 +713,7 @@ void TakeScreenshot() { // First, find a free filename. int i = 0; - std::string gameId = g_paramSFO.GetValueString("DISC_ID"); - if (gameId.empty()) { - gameId = g_paramSFO.GenerateFakeID(); - } + std::string gameId = g_paramSFO.GetDiscID(); char filename[2048]; while (i < 10000){ diff --git a/UI/PauseScreen.cpp b/UI/PauseScreen.cpp index 60b7f5a94f..5e45f1e5eb 100644 --- a/UI/PauseScreen.cpp +++ b/UI/PauseScreen.cpp @@ -320,10 +320,7 @@ void GamePauseScreen::CreateViews() { root_->SetDefaultFocusView(continueChoice); continueChoice->OnClick.Handle(this, &UIScreen::OnBack); - std::string gameId = g_paramSFO.GetValueString("DISC_ID"); - if (gameId.empty()) { - gameId = g_paramSFO.GenerateFakeID(); - } + std::string gameId = g_paramSFO.GetDiscID(); if (g_Config.hasGameConfig(gameId)) { rightColumnItems->Add(new Choice(pa->T("Game Settings")))->OnClick.Handle(this, &GamePauseScreen::OnGameSettings); rightColumnItems->Add(new Choice(pa->T("Delete Game Config")))->OnClick.Handle(this, &GamePauseScreen::OnDeleteConfig); @@ -424,10 +421,7 @@ void GamePauseScreen::CallbackDeleteConfig(bool yes) UI::EventReturn GamePauseScreen::OnCreateConfig(UI::EventParams &e) { - std::string gameId = g_paramSFO.GetValueString("DISC_ID"); - if (gameId.empty()) { - gameId = g_paramSFO.GenerateFakeID(); - } + std::string gameId = g_paramSFO.GetDiscID(); g_Config.createGameConfig(gameId); g_Config.changeGameSpecific(gameId); g_Config.saveGameConfig(gameId); diff --git a/UWP/CoreUWP/CoreUWP.vcxproj b/UWP/CoreUWP/CoreUWP.vcxproj index 17d35ebbdc..9bb7322606 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj +++ b/UWP/CoreUWP/CoreUWP.vcxproj @@ -330,6 +330,7 @@ + @@ -521,6 +522,7 @@ + diff --git a/UWP/CoreUWP/CoreUWP.vcxproj.filters b/UWP/CoreUWP/CoreUWP.vcxproj.filters index 2b20fc1219..0d7268912a 100644 --- a/UWP/CoreUWP/CoreUWP.vcxproj.filters +++ b/UWP/CoreUWP/CoreUWP.vcxproj.filters @@ -166,6 +166,9 @@ MIPS\x86 + + FileSystems + FileSystems @@ -674,6 +677,9 @@ MIPS\x86 + + FileSystems + FileSystems diff --git a/Windows/GEDebugger/GEDebugger.cpp b/Windows/GEDebugger/GEDebugger.cpp index c8ca5a5ed7..3e0a2cd5af 100644 --- a/Windows/GEDebugger/GEDebugger.cpp +++ b/Windows/GEDebugger/GEDebugger.cpp @@ -36,6 +36,7 @@ #include "GPU/Common/GPUStateUtils.h" #include "GPU/GPUState.h" #include "GPU/Debugger/Breakpoints.h" +#include "GPU/Debugger/Record.h" #include "GPU/Debugger/Stepping.h" #include "Core/Config.h" #include @@ -706,6 +707,10 @@ BOOL CGEDebugger::DlgProc(UINT message, WPARAM wParam, LPARAM lParam) { breakNext = BREAK_NONE; break; + case IDC_GEDBG_RECORD: + GPURecord::Activate(); + break; + case IDC_GEDBG_FORCEOPAQUE: if (attached && gpuDebug != nullptr) { forceOpaque_ = SendMessage(GetDlgItem(m_hDlg, IDC_GEDBG_FORCEOPAQUE), BM_GETCHECK, 0, 0) != 0; diff --git a/Windows/ppsspp.rc b/Windows/ppsspp.rc index 320346a5a4..a324f42896 100644 --- a/Windows/ppsspp.rc +++ b/Windows/ppsspp.rc @@ -176,6 +176,7 @@ BEGIN PUSHBUTTON "Step &Prim",IDC_GEDBG_STEPPRIM,166,2,48,14 PUSHBUTTON "Step &Into",IDC_GEDBG_STEP,218,2,48,14 PUSHBUTTON "&Resume",IDC_GEDBG_RESUME,270,2,48,14 + PUSHBUTTON "Rec&ord",IDC_GEDBG_RECORD,440,2,48,14 CONTROL "",IDC_GEDBG_TEX,"SimpleGLWindow",WS_CHILD | WS_VISIBLE,10,20,128,128 CONTROL "",IDC_GEDBG_FRAME,"SimpleGLWindow",WS_CHILD | WS_VISIBLE,148,20,256,136 CONTROL "",IDC_GEDBG_MAINTAB,"SysTabControl32",TCS_TABS | TCS_FOCUSNEVER,10,216,480,180 diff --git a/Windows/resource.h b/Windows/resource.h index b0c0dcd160..f105eb9739 100644 --- a/Windows/resource.h +++ b/Windows/resource.h @@ -333,6 +333,7 @@ #define ID_FILE_USEFFV1 40166 #define ID_FILE_DUMPAUDIO 40167 #define ID_HELP_GITHUB 40168 +#define IDC_GEDBG_RECORD 40169 // Dummy option to let the buffered rendering hotkey cycle through all the options. #define ID_OPTIONS_BUFFEREDRENDERINGDUMMY 40500 diff --git a/android/jni/Android.mk b/android/jni/Android.mk index 7b3dd28a06..58242a4527 100644 --- a/android/jni/Android.mk +++ b/android/jni/Android.mk @@ -229,6 +229,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/GPU/Common/PostShader.cpp \ $(SRC)/GPU/Common/ShaderUniforms.cpp \ $(SRC)/GPU/Debugger/Breakpoints.cpp \ + $(SRC)/GPU/Debugger/Record.cpp \ $(SRC)/GPU/Debugger/Stepping.cpp \ $(SRC)/GPU/GLES/FramebufferManagerGLES.cpp \ $(SRC)/GPU/GLES/DepalettizeShaderGLES.cpp \ @@ -363,6 +364,7 @@ EXEC_AND_LIB_FILES := \ $(SRC)/Core/HLE/sceGameUpdate.cpp \ $(SRC)/Core/HLE/sceNp.cpp \ $(SRC)/Core/HLE/scePauth.cpp \ + $(SRC)/Core/FileSystems/BlobFileSystem.cpp \ $(SRC)/Core/FileSystems/BlockDevices.cpp \ $(SRC)/Core/FileSystems/ISOFileSystem.cpp \ $(SRC)/Core/FileSystems/FileSystem.cpp \