diff --git a/.gitignore b/.gitignore index 5e6b96b51c..a99238ff41 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,9 @@ Windows/ipch # For ppsspp.ini, etc. *.ini +# Qt Linguist files +*.qm + Logs Memstick diff --git a/CMakeLists.txt b/CMakeLists.txt index c567215010..37509ed442 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -918,7 +918,11 @@ if(WIN32) endif() if(HEADLESS) - add_executable(PPSSPPHeadless headless/Headless.cpp headless/StubHost.h) + add_executable(PPSSPPHeadless + headless/Headless.cpp + headless/StubHost.h + headless/Compare.cpp + headless/Compare.h) target_link_libraries(PPSSPPHeadless ${CoreLibName} ${COCOA_LIBRARY} ${CMAKE_THREAD_LIBS_INIT}) setup_target_project(PPSSPPHeadless headless) diff --git a/Core/FileSystems/BlockDevices.cpp b/Core/FileSystems/BlockDevices.cpp index c413209c0c..4b74a422e1 100644 --- a/Core/FileSystems/BlockDevices.cpp +++ b/Core/FileSystems/BlockDevices.cpp @@ -123,6 +123,12 @@ CISOFileBlockDevice::~CISOFileBlockDevice() bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) { + if ((u32)blockNumber >= numBlocks) + { + memset(outPtr, 0, 2048); + return false; + } + u32 idx = index[blockNumber]; u32 idx2 = index[blockNumber+1]; u8 inbuffer[4096]; //too big @@ -153,7 +159,7 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) if(inflateInit2(&z, -15) != Z_OK) { ERROR_LOG(LOADER, "deflateInit ERROR : %s\n", (z.msg) ? z.msg : "???"); - return 1; + return false; } z.avail_in = readSize; z.next_out = outPtr; @@ -165,14 +171,17 @@ bool CISOFileBlockDevice::ReadBlock(int blockNumber, u8 *outPtr) //if (status != Z_OK) { ERROR_LOG(LOADER, "block %d:inflate : %s[%d]\n", blockNumber, (z.msg) ? z.msg : "error", status); + inflateEnd(&z); return 1; } int cmp_size = blockSize - z.avail_out; if (cmp_size != (int)blockSize) { ERROR_LOG(LOADER, "block %d : block size error %d != %d\n", blockNumber, cmp_size, blockSize); - return 1; + inflateEnd(&z); + return false; } + inflateEnd(&z); } return true; } diff --git a/Core/FileSystems/DirectoryFileSystem.cpp b/Core/FileSystems/DirectoryFileSystem.cpp index b5ea2004bc..00a163fc5c 100644 --- a/Core/FileSystems/DirectoryFileSystem.cpp +++ b/Core/FileSystems/DirectoryFileSystem.cpp @@ -31,9 +31,6 @@ #endif -#undef DeleteFile - - #if HOST_IS_CASE_SENSITIVE static bool FixFilenameCase(const std::string &path, std::string &filename) @@ -248,7 +245,7 @@ bool DirectoryFileSystem::RenameFile(const std::string &from, const std::string return retValue; } -bool DirectoryFileSystem::DeleteFile(const std::string &filename) { +bool DirectoryFileSystem::RemoveFile(const std::string &filename) { std::string fullName = GetLocalPath(filename); #ifdef _WIN32 bool retValue = (::DeleteFileA(fullName.c_str()) == TRUE); @@ -266,7 +263,7 @@ bool DirectoryFileSystem::DeleteFile(const std::string &filename) { fullName = GetLocalPath(fullName); #ifdef _WIN32 - retValue = (::DeleteFile(fullName.c_str()) == TRUE); + retValue = (::DeleteFileA(fullName.c_str()) == TRUE); #else retValue = (0 == unlink(fullName.c_str())); #endif diff --git a/Core/FileSystems/DirectoryFileSystem.h b/Core/FileSystems/DirectoryFileSystem.h index 739a8427ad..61ee605406 100644 --- a/Core/FileSystems/DirectoryFileSystem.h +++ b/Core/FileSystems/DirectoryFileSystem.h @@ -66,7 +66,7 @@ public: bool MkDir(const std::string &dirname); bool RmDir(const std::string &dirname); bool RenameFile(const std::string &from, const std::string &to); - bool DeleteFile(const std::string &filename); + bool RemoveFile(const std::string &filename); bool GetHostPath(const std::string &inpath, std::string &outpath); private: diff --git a/Core/FileSystems/FileSystem.h b/Core/FileSystems/FileSystem.h index f055811ebc..d24efafeff 100644 --- a/Core/FileSystems/FileSystem.h +++ b/Core/FileSystems/FileSystem.h @@ -110,7 +110,7 @@ public: virtual bool MkDir(const std::string &dirname) = 0; virtual bool RmDir(const std::string &dirname) = 0; virtual bool RenameFile(const std::string &from, const std::string &to) = 0; - virtual bool DeleteFile(const std::string &filename) = 0; + virtual bool RemoveFile(const std::string &filename) = 0; virtual bool GetHostPath(const std::string &inpath, std::string &outpath) = 0; }; @@ -130,7 +130,7 @@ public: virtual bool MkDir(const std::string &dirname) {return false;} virtual bool RmDir(const std::string &dirname) {return false;} virtual bool RenameFile(const std::string &from, const std::string &to) {return false;} - virtual bool DeleteFile(const std::string &filename) {return false;} + virtual bool RemoveFile(const std::string &filename) {return false;} virtual bool GetHostPath(const std::string &inpath, std::string &outpath) {return false;} }; diff --git a/Core/FileSystems/ISOFileSystem.cpp b/Core/FileSystems/ISOFileSystem.cpp index 2a2403cff5..7e81411d4a 100644 --- a/Core/FileSystems/ISOFileSystem.cpp +++ b/Core/FileSystems/ISOFileSystem.cpp @@ -178,80 +178,74 @@ ISOFileSystem::~ISOFileSystem() void ISOFileSystem::ReadDirectory(u32 startsector, u32 dirsize, TreeEntry *root) { - u8 buffer[2048]; - int offset = 0; - u32 secnum = startsector; - - u8 theSector[2048]; - blockDevice->ReadBlock(secnum, theSector); - - while (secnum < (dirsize/2048 + startsector)) + for (u32 secnum = startsector, endsector = dirsize/2048 + startsector; secnum < endsector; ++secnum) { - DirectoryEntry &dir = *((DirectoryEntry *)buffer); - u8 sz = theSector[offset]; - if (sz == 0) // NOT the correct way - goto nextblock; //done + u8 theSector[2048]; + blockDevice->ReadBlock(secnum, theSector); - memcpy(&dir, theSector + offset, sz); - - buffer[2047]=0; - offset += dir.size; - if (offset >= 2048) + for (int offset = 0; offset < 2048; ) { -nextblock: - offset=0; - secnum++; - blockDevice->ReadBlock(secnum, theSector); - memcpy(&dir, theSector + offset, sz); - } - bool isFile = (dir.flags & 2) ? false : true; + DirectoryEntry &dir = *(DirectoryEntry *)&theSector[offset]; + u8 sz = theSector[offset]; - int fnLength = dir.identifierLength; + // Nothing left in this sector. There might be more in the next one. + if (sz == 0) + break; - char name[256]; - for (int i = 0; i < fnLength; i++) - name[i] = buffer[33+i] ? buffer[33+i] : '.'; - name[fnLength] = '\0'; - - bool relative = false; - - if (!strcmp(name, ".")) // "." record - { - relative = true; - } - - if (strlen(name) == 1 && name[0] == '\x01') // ".." record - { - strcpy(name,".."); - relative = true; - } - - TreeEntry *e = new TreeEntry; - e->name = name; - e->size = dir.dataLengthLE; - e->startingPosition = dir.firstDataSectorLE * 2048; - e->isDirectory = !isFile; - e->flags = dir.flags; - e->isBlockSectorMode = false; - e->parent = root; - - // Let's not excessively spam the log - I commented this line out. - //DEBUG_LOG(FILESYS, "%s: %s %08x %08x %i", e->isDirectory?"D":"F", name, dir.firstDataSectorLE, e->startingPosition, e->startingPosition); - - if (e->isDirectory && !relative) - { - if (dir.firstDataSectorLE == startsector) + const int IDENTIFIER_OFFSET = 33; + if (offset + IDENTIFIER_OFFSET + dir.identifierLength > 2048) { - ERROR_LOG(FILESYS, "WARNING: Appear to have a recursive file system, breaking recursion"); + ERROR_LOG(FILESYS, "Directory entry crosses sectors, corrupt iso?"); + break; + } + + offset += dir.size; + + bool isFile = (dir.flags & 2) ? false : true; + bool relative; + int fnLength = dir.identifierLength; + + TreeEntry *e = new TreeEntry(); + if (dir.identifierLength == 1 && (dir.firstIdChar == '\x00' || dir.firstIdChar == '.')) + { + e->name = "."; + relative = true; + } + else if (dir.identifierLength == 1 && dir.firstIdChar == '\x01') + { + e->name = ".."; + relative = true; } else { - ReadDirectory(dir.firstDataSectorLE, dir.dataLengthLE, e); + e->name = std::string((char *)&dir.firstIdChar, dir.identifierLength); + relative = false; } - } - root->children.push_back(e); - } + e->size = dir.dataLengthLE; + e->startingPosition = dir.firstDataSectorLE * 2048; + e->isDirectory = !isFile; + e->flags = dir.flags; + e->isBlockSectorMode = false; + e->parent = root; + + // Let's not excessively spam the log - I commented this line out. + //DEBUG_LOG(FILESYS, "%s: %s %08x %08x %i", e->isDirectory?"D":"F", e->name.c_str(), dir.firstDataSectorLE, e->startingPosition, e->startingPosition); + + if (e->isDirectory && !relative) + { + if (dir.firstDataSectorLE == startsector) + { + ERROR_LOG(FILESYS, "WARNING: Appear to have a recursive file system, breaking recursion"); + } + else + { + ReadDirectory(dir.firstDataSectorLE, dir.dataLengthLE, e); + } + } + root->children.push_back(e); + } + } } ISOFileSystem::TreeEntry *ISOFileSystem::GetFromPath(std::string path, bool catchError) diff --git a/Core/FileSystems/ISOFileSystem.h b/Core/FileSystems/ISOFileSystem.h index ab90080829..96f8b3f39e 100644 --- a/Core/FileSystems/ISOFileSystem.h +++ b/Core/FileSystems/ISOFileSystem.h @@ -44,7 +44,7 @@ public: virtual bool MkDir(const std::string &dirname) {return false;} virtual bool RmDir(const std::string &dirname) {return false;} virtual bool RenameFile(const std::string &from, const std::string &to) {return false;} - virtual bool DeleteFile(const std::string &filename) {return false;} + virtual bool RemoveFile(const std::string &filename) {return false;} private: struct TreeEntry diff --git a/Core/FileSystems/MetaFileSystem.cpp b/Core/FileSystems/MetaFileSystem.cpp index b72ec7f098..7c971264ed 100644 --- a/Core/FileSystems/MetaFileSystem.cpp +++ b/Core/FileSystems/MetaFileSystem.cpp @@ -363,13 +363,13 @@ bool MetaFileSystem::RenameFile(const std::string &from, const std::string &to) } } -bool MetaFileSystem::DeleteFile(const std::string &filename) +bool MetaFileSystem::RemoveFile(const std::string &filename) { std::string of; IFileSystem *system; if (MapFilePath(filename, of, &system)) { - return system->DeleteFile(of); + return system->RemoveFile(of); } else { diff --git a/Core/FileSystems/MetaFileSystem.h b/Core/FileSystems/MetaFileSystem.h index b54e153613..b6bd5d9954 100644 --- a/Core/FileSystems/MetaFileSystem.h +++ b/Core/FileSystems/MetaFileSystem.h @@ -89,7 +89,7 @@ public: virtual bool MkDir(const std::string &dirname); virtual bool RmDir(const std::string &dirname); virtual bool RenameFile(const std::string &from, const std::string &to); - virtual bool DeleteFile(const std::string &filename); + virtual bool RemoveFile(const std::string &filename); // TODO: void IoCtl(...) diff --git a/Core/HLE/sceDisplay.cpp b/Core/HLE/sceDisplay.cpp index 8e61522cee..f6e4c4f583 100644 --- a/Core/HLE/sceDisplay.cpp +++ b/Core/HLE/sceDisplay.cpp @@ -25,6 +25,7 @@ #include "Thread.h" #include "../Core/CoreTiming.h" +#include "../Core/CoreParameter.h" #include "../MIPS/MIPS.h" #include "../HLE/HLE.h" #include "sceAudio.h" @@ -78,7 +79,7 @@ static int hCountTotal; //unused static int vCount; static int isVblank; static bool hasSetMode; -double lastFrameTime; +static double lastFrameTime; std::vector vblankWaitingThreads; @@ -269,12 +270,11 @@ void hleEnterVblank(u64 userdata, int cyclesLate) { host->EndFrame(); #ifdef _WIN32 - static double lastFrameTime = 0.0; // Best place to throttle the frame rate on non vsynced platforms is probably here. Let's try it. time_update(); if (lastFrameTime == 0.0) lastFrameTime = time_now_d(); - if (!GetAsyncKeyState(VK_TAB)) { + if (!GetAsyncKeyState(VK_TAB) && !PSP_CoreParameter().headLess) { while (time_now_d() < lastFrameTime + 1.0 / 60.0) { Common::SleepCurrentThread(1); time_update(); @@ -357,6 +357,18 @@ u32 sceDisplaySetFramebuf() { return 0; } +bool __DisplayGetFramebuf(u8 **topaddr, u32 *linesize, u32 *pixelFormat, int mode) { + const FrameBufferState &fbState = mode == 1 ? latchedFramebuf : framebuf; + if (topaddr != NULL) + *topaddr = Memory::GetPointer(fbState.topaddr); + if (linesize != NULL) + *linesize = fbState.pspFramebufLinesize; + if (pixelFormat != NULL) + *pixelFormat = fbState.pspFramebufFormat; + + return true; +} + u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int mode) { const FrameBufferState &fbState = mode == 1 ? latchedFramebuf : framebuf; DEBUG_LOG(HLE,"sceDisplayGetFramebuf(*%08x = %08x, *%08x = %08x, *%08x = %08x, %i)", diff --git a/Core/HLE/sceDisplay.h b/Core/HLE/sceDisplay.h index c75faf93f7..fd6de956e3 100644 --- a/Core/HLE/sceDisplay.h +++ b/Core/HLE/sceDisplay.h @@ -26,6 +26,9 @@ void Register_sceDisplay(); // will return true once after every end-of-frame. bool __DisplayFrameDone(); +// Get information about the current framebuffer. +bool __DisplayGetFramebuf(u8 **topaddr, u32 *linesize, u32 *pixelFormat, int mode); + typedef void (*VblankCallback)(); // Listen for vblank events. Only register during init. void __DisplayListenVblank(VblankCallback callback); diff --git a/Core/HLE/sceGe.cpp b/Core/HLE/sceGe.cpp index 91c687d65d..a8de87bf08 100644 --- a/Core/HLE/sceGe.cpp +++ b/Core/HLE/sceGe.cpp @@ -33,6 +33,8 @@ struct GeInterruptData { int listid; u32 pc; + u32 subIntrBase; + u16 subIntrToken; }; static std::list ge_pending_cb; @@ -50,20 +52,21 @@ public: if (dl == NULL) { WARN_LOG(HLE, "Unable to run GE interrupt: list doesn't exist: %d", intrdata.listid); - return false; + // TODO: Use dl instead of just saving everything instead? + //return false; } gpu->InterruptStart(); u32 cmd = Memory::ReadUnchecked_U32(intrdata.pc) >> 24; - int subintr = dl->subIntrBase | (cmd == GE_CMD_FINISH ? PSP_GE_SUBINTR_FINISH : PSP_GE_SUBINTR_SIGNAL); + int subintr = intrdata.subIntrBase | (cmd == GE_CMD_FINISH ? PSP_GE_SUBINTR_FINISH : PSP_GE_SUBINTR_SIGNAL); SubIntrHandler* handler = get(subintr); if(handler != NULL) { DEBUG_LOG(CPU, "Entering interrupt handler %08x", handler->handlerAddress); currentMIPS->pc = handler->handlerAddress; - u32 data = dl->subIntrToken; + u32 data = intrdata.subIntrToken; currentMIPS->r[MIPS_REG_A0] = data & 0xFFFF; currentMIPS->r[MIPS_REG_A1] = handler->handlerArg; currentMIPS->r[MIPS_REG_A2] = sceKernelGetCompiledSdkVersion() <= 0x02000010 ? 0 : intrdata.pc + 4; @@ -109,11 +112,13 @@ void __GeShutdown() } -void __GeTriggerInterrupt(int listid, u32 pc) +void __GeTriggerInterrupt(int listid, u32 pc, u32 subIntrBase, u16 subIntrToken) { GeInterruptData intrdata; intrdata.listid = listid; intrdata.pc = pc; + intrdata.subIntrBase = subIntrBase; + intrdata.subIntrToken = subIntrToken; ge_pending_cb.push_back(intrdata); __TriggerInterrupt(PSP_INTR_HLE, PSP_GE_INTR, PSP_INTR_SUB_NONE); } diff --git a/Core/HLE/sceGe.h b/Core/HLE/sceGe.h index ce6661fe28..c2621a492f 100644 --- a/Core/HLE/sceGe.h +++ b/Core/HLE/sceGe.h @@ -39,7 +39,7 @@ void Register_sceGe_user(); void __GeInit(); void __GeDoState(PointerWrap &p); void __GeShutdown(); -void __GeTriggerInterrupt(int listid, u32 pc); +void __GeTriggerInterrupt(int listid, u32 pc, u32 subIntrBase, u16 subIntrToken); bool __GeHasPendingInterrupt(); diff --git a/Core/HLE/sceIo.cpp b/Core/HLE/sceIo.cpp index 15c535cf4c..25341ea1db 100644 --- a/Core/HLE/sceIo.cpp +++ b/Core/HLE/sceIo.cpp @@ -17,7 +17,6 @@ #ifdef _WIN32 #include -#undef DeleteFile #endif #include "../Config.h" @@ -38,6 +37,9 @@ #include "sceKernelMemory.h" #include "sceKernelThread.h" +// For headless screenshots. +#include "sceDisplay.h" + #define ERROR_ERRNO_FILE_NOT_FOUND 0x80010002 #define ERROR_MEMSTICK_DEVCTL_BAD_PARAMS 0x80220081 @@ -144,9 +146,10 @@ public: p.Do(callbackID); p.Do(callbackArg); p.Do(asyncResult); - p.Do(closePending); p.Do(pendingAsyncResult); p.Do(sectorBlockMode); + p.Do(closePending); + p.Do(info); p.Do(openMode); p.DoMarker("File"); } @@ -549,7 +552,7 @@ u32 sceIoRemove(const char *filename) { if(!pspFileSystem.GetFileInfo(filename).exists) return ERROR_ERRNO_FILE_NOT_FOUND; - pspFileSystem.DeleteFile(filename); + pspFileSystem.RemoveFile(filename); return 0; } @@ -782,6 +785,15 @@ u32 sceIoDevctl(const char *name, int cmd, u32 argAddr, int argLen, u32 outPtr, SaveState::Verify(); // TODO: Maybe save/load to a file just to be sure? return 0; + + case 0x20: // EMULATOR_DEVCTL__EMIT_SCREENSHOT + u8 *topaddr; + u32 linesize, pixelFormat; + + __DisplayGetFramebuf(&topaddr, &linesize, &pixelFormat, 0); + // TODO: Convert based on pixel format / mode / something? + host->SendDebugScreenshot(topaddr, linesize, 272); + return 0; } ERROR_LOG(HLE, "sceIoDevCtl: UNKNOWN PARAMETERS"); @@ -1019,26 +1031,27 @@ u32 sceIoDread(int id, u32 dirent_addr) { u32 error; DirListing *dir = kernelObjects.Get(id, error); if (dir) { + SceIoDirEnt *entry = (SceIoDirEnt*) Memory::GetPointer(dirent_addr); + if (dir->index == (int) dir->listing.size()) { DEBUG_LOG(HLE, "sceIoDread( %d %08x ) - end of the line", id, dirent_addr); + entry->d_name[0] = '\0'; return 0; } PSPFileInfo &info = dir->listing[dir->index]; - - SceIoDirEnt *entry = (SceIoDirEnt*) Memory::GetPointer(dirent_addr); - __IoGetStat(&entry->d_stat, info); strncpy(entry->d_name, info.name.c_str(), 256); + entry->d_name[255] = '\0'; entry->d_private = 0xC0DEBABE; DEBUG_LOG(HLE, "sceIoDread( %d %08x ) = %s", id, dirent_addr, entry->d_name); dir->index++; - return (u32)(dir->listing.size() - dir->index + 1); + return 1; } else { DEBUG_LOG(HLE, "sceIoDread - invalid listing %i, error %08x", id, error); - return -1; // TODO + return SCE_KERNEL_ERROR_BADF; } } diff --git a/Core/HLE/sceKernel.cpp b/Core/HLE/sceKernel.cpp index e74464b129..04e5a8368e 100644 --- a/Core/HLE/sceKernel.cpp +++ b/Core/HLE/sceKernel.cpp @@ -157,6 +157,7 @@ void __KernelDoState(PointerWrap &p) p.DoMarker("KernelObjects"); __InterruptsDoState(p); + // Memory needs to be after kernel objects, which may free kernel memory. __KernelMemoryDoState(p); __KernelThreadingDoState(p); __KernelAlarmDoState(p); @@ -589,7 +590,7 @@ const HLEFunction ThreadManForUser[] = {0x75156e8f,sceKernelResumeThread,"sceKernelResumeThread"}, {0x3ad58b8c,&WrapU_V,"sceKernelSuspendDispatchThread"}, {0x27e22ec2,&WrapU_U,"sceKernelResumeDispatchThread"}, - {0x912354a7,sceKernelRotateThreadReadyQueue,"sceKernelRotateThreadReadyQueue"}, + {0x912354a7,&WrapI_I,"sceKernelRotateThreadReadyQueue"}, {0x9ACE131E,sceKernelSleepThread,"sceKernelSleepThread"}, {0x82826f70,sceKernelSleepThreadCB,"sceKernelSleepThreadCB"}, {0xF475845D,&WrapI_IUU,"sceKernelStartThread"}, diff --git a/Core/HLE/sceKernelThread.cpp b/Core/HLE/sceKernelThread.cpp index 293bab900e..bda308d7a9 100644 --- a/Core/HLE/sceKernelThread.cpp +++ b/Core/HLE/sceKernelThread.cpp @@ -446,16 +446,17 @@ int g_inCbCount = 0; // Normally, the same as currentThread. In an interrupt, remembers the callback's thread id. SceUID currentCallbackThreadID = 0; int readyCallbacksCount = 0; -SceUID currentThread; +SceUID currentThread = 0; u32 idleThreadHackAddr; u32 threadReturnHackAddr; u32 cbReturnHackAddr; u32 intReturnHackAddr; std::vector threadEndListeners; -typedef std::vector ThreadList; // Lists all thread ids that aren't deleted/etc. -ThreadList threadqueue; +std::vector threadqueue; + +typedef std::list ThreadList; // Lists only ready thread ids. std::map threadReadyQueue; @@ -657,30 +658,23 @@ void __KernelFireThreadEnd(SceUID threadID) } // TODO: Use __KernelChangeThreadState instead? It has other affects... -void __KernelChangeReadyState(Thread *thread, SceUID threadID, bool ready, bool atStart = false) +void __KernelChangeReadyState(Thread *thread, SceUID threadID, bool ready) { int prio = thread->nt.currentPriority; if (thread->isReady()) { if (!ready) - threadReadyQueue[prio].erase(std::remove(threadReadyQueue[prio].begin(), threadReadyQueue[prio].end(), threadID), threadReadyQueue[prio].end()); + threadReadyQueue[prio].remove(threadID); } else if (ready) { - if (atStart) - { - size_t oldSize = threadReadyQueue[prio].size(); - threadReadyQueue[prio].resize(oldSize + 1); - if (oldSize > 0) - memmove(&threadReadyQueue[prio][1], &threadReadyQueue[prio][0], oldSize * sizeof(SceUID)); - threadReadyQueue[prio][0] = threadID; - } + if (thread->isRunning()) + threadReadyQueue[prio].push_front(threadID); else threadReadyQueue[prio].push_back(threadID); + thread->nt.status = THREADSTATUS_READY; } - - thread->nt.status = THREADSTATUS_READY; } void __KernelChangeReadyState(SceUID threadID, bool ready) @@ -1130,7 +1124,7 @@ void __KernelRemoveFromThreadQueue(SceUID threadID) { int prio = __KernelGetThreadPrio(threadID); if (prio != 0) - threadReadyQueue[prio].erase(std::remove(threadReadyQueue[prio].begin(), threadReadyQueue[prio].end(), threadID), threadReadyQueue[prio].end()); + threadReadyQueue[prio].remove(threadID); threadqueue.erase(std::remove(threadqueue.begin(), threadqueue.end(), threadID), threadqueue.end()); } @@ -1168,7 +1162,7 @@ Thread *__KernelNextThread() { { if (!it->second.empty()) { - bestThread = it->second[0]; + bestThread = it->second.front(); break; } } @@ -1182,10 +1176,6 @@ Thread *__KernelNextThread() { void __KernelReSchedule(const char *reason) { - // TODO: Not sure if this is correct? - if (__GetCurrentThread() && __GetCurrentThread()->isRunning()) - __KernelChangeReadyState(currentThread, true); - // cancel rescheduling when in interrupt or callback, otherwise everything will be fucked up if (__IsInInterrupt() || __KernelInCallback()) { @@ -1208,6 +1198,10 @@ void __KernelReSchedule(const char *reason) return; } + // TODO: Not sure if this is correct? Probably should remove. + if (__GetCurrentThread() && __GetCurrentThread()->isRunning()) + __KernelChangeReadyState(currentThread, true); + retry: Thread *nextThread = __KernelNextThread(); @@ -1361,8 +1355,11 @@ void __KernelSetupRootThread(SceUID moduleID, int args, const char *argp, int pr Thread *thread = __KernelCreateThread(id, moduleID, "root", currentMIPS->pc, prio, stacksize, attr); __KernelResetThread(thread); + Thread *prevThread = __GetCurrentThread(); + if (prevThread && prevThread->isRunning()) + __KernelChangeReadyState(currentThread, true); currentThread = id; - __KernelChangeReadyState(thread, id, true); // do not schedule + thread->nt.status = THREADSTATUS_RUNNING; // do not schedule strcpy(thread->nt.name, "root"); @@ -1439,7 +1436,6 @@ int sceKernelStartThread(SceUID threadToStartID, u32 argSize, u32 argBlockPtr) threadToStartID,argSize,argBlockPtr); __KernelResetThread(startThread); - __KernelChangeReadyState(startThread, threadToStartID, true, true); u32 sp = startThread->context.r[MIPS_REG_SP]; if (argBlockPtr && argSize > 0) @@ -1462,7 +1458,14 @@ int sceKernelStartThread(SceUID threadToStartID, u32 argSize, u32 argBlockPtr) WARN_LOG(HLE,"sceKernelStartThread : had NULL arg"); } - hleReSchedule("thread started"); + Thread *cur = __GetCurrentThread(); + // Smaller is better for priority. Only switch if the new thread is better. + if (cur && cur->nt.currentPriority > startThread->nt.currentPriority) + { + __KernelChangeReadyState(currentThread, true); + hleReSchedule("thread started"); + } + __KernelChangeReadyState(startThread, threadToStartID, true); return 0; } else @@ -1600,10 +1603,39 @@ u32 sceKernelResumeDispatchThread(u32 suspended) return oldDispatchSuspended; } -void sceKernelRotateThreadReadyQueue() +int sceKernelRotateThreadReadyQueue(int priority) { - DEBUG_LOG(HLE,"sceKernelRotateThreadReadyQueue : rescheduling"); - hleReSchedule("rotatethreadreadyqueue"); + DEBUG_LOG(HLE, "sceKernelRotateThreadReadyQueue(%x)", priority); + + Thread *cur = __GetCurrentThread(); + + // 0 is special, it means "my current priority." + if (priority == 0) + priority = cur->nt.currentPriority; + + if (priority <= 0x07 || priority > 0x77) + return SCE_KERNEL_ERROR_ILLEGAL_PRIORITY; + + if (!threadReadyQueue[priority].empty()) + { + // In other words, yield to everyone else. + if (cur->nt.currentPriority == priority) + { + threadReadyQueue[priority].push_back(currentThread); + cur->nt.status = THREADSTATUS_READY; + } + // Yield the next thread of this priority to all other threads of same priority. + else if (threadReadyQueue[priority].size() > 1) + { + SceUID first = threadReadyQueue[priority].front(); + threadReadyQueue[priority].pop_front(); + threadReadyQueue[priority].push_back(first); + } + + hleReSchedule("rotatethreadreadyqueue"); + } + + return 0; } int sceKernelDeleteThread(int threadHandle) @@ -2257,15 +2289,15 @@ void __KernelSwitchContext(Thread *target, const char *reason) oldName = cur->GetName(); if (cur->isRunning()) - { - __KernelChangeReadyState(cur, oldUID, false); - cur->nt.status = (cur->nt.status | THREADSTATUS_READY) & ~THREADSTATUS_RUNNING; - } + __KernelChangeReadyState(cur, oldUID, true); } currentThread = target->GetUID(); - if (target && target->isRunning()) - __KernelChangeReadyState(target, currentThread, true); + if (target) + { + __KernelChangeReadyState(target, currentThread, false); + target->nt.status = (target->nt.status | THREADSTATUS_RUNNING) & ~THREADSTATUS_READY; + } __KernelLoadContext(&target->context); diff --git a/Core/HLE/sceKernelThread.h b/Core/HLE/sceKernelThread.h index 86a158b5a4..1000f9597a 100644 --- a/Core/HLE/sceKernelThread.h +++ b/Core/HLE/sceKernelThread.h @@ -46,7 +46,7 @@ u32 sceKernelReferThreadStatus(u32 uid, u32 statusPtr); u32 sceKernelReferThreadRunStatus(u32 uid, u32 statusPtr); int sceKernelReleaseWaitThread(SceUID threadID); void sceKernelChangeCurrentThreadAttr(); -void sceKernelRotateThreadReadyQueue(); +int sceKernelRotateThreadReadyQueue(int priority); void sceKernelCheckThreadStack(); void sceKernelSuspendThread(); void sceKernelResumeThread(); diff --git a/Core/Host.h b/Core/Host.h index 29cc5b2fc4..d426289d14 100644 --- a/Core/Host.h +++ b/Core/Host.h @@ -63,6 +63,7 @@ public: // Used for headless. virtual void SendDebugOutput(const std::string &output) {} + virtual void SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) {} }; extern Host *host; diff --git a/Core/MIPS/ARM/ArmCompALU.cpp b/Core/MIPS/ARM/ArmCompALU.cpp index 2673fcada6..9fdc796c2d 100644 --- a/Core/MIPS/ARM/ArmCompALU.cpp +++ b/Core/MIPS/ARM/ArmCompALU.cpp @@ -277,6 +277,12 @@ namespace MIPSComp } } + void Jit::Comp_Special3(u32 op) + { + // ext, ins + DISABLE; + } + void Jit::Comp_Allegrex(u32 op) { DISABLE diff --git a/Core/MIPS/ARM/ArmCompFPU.cpp b/Core/MIPS/ARM/ArmCompFPU.cpp index a1aa60bcf3..127cd705a3 100644 --- a/Core/MIPS/ARM/ArmCompFPU.cpp +++ b/Core/MIPS/ARM/ArmCompFPU.cpp @@ -83,6 +83,10 @@ void Jit::Comp_FPULS(u32 op) } } +void Jit::Comp_FPUComp(u32 op) { + DISABLE; +} + void Jit::Comp_FPU2op(u32 op) { DISABLE diff --git a/Core/MIPS/ARM/ArmCompLoadStore.cpp b/Core/MIPS/ARM/ArmCompLoadStore.cpp index 0a3be15da3..fff481a295 100644 --- a/Core/MIPS/ARM/ArmCompLoadStore.cpp +++ b/Core/MIPS/ARM/ArmCompLoadStore.cpp @@ -52,7 +52,7 @@ #define _POS ((op>>6 ) & 0x1F) #define _SIZE ((op>>11 ) & 0x1F) -#define OLDD Comp_Generic(op); return; +#define DISABLE Comp_Generic(op); return; namespace MIPSComp { diff --git a/Core/MIPS/ARM/ArmCompVFPU.cpp b/Core/MIPS/ARM/ArmCompVFPU.cpp index edb679a7c7..218da28757 100644 --- a/Core/MIPS/ARM/ArmCompVFPU.cpp +++ b/Core/MIPS/ARM/ArmCompVFPU.cpp @@ -29,4 +29,14 @@ namespace MIPSComp { DISABLE; } + + void Jit::Comp_Mftv(u32 op) + { + DISABLE; + } + + void Jit::Comp_SV(u32 op) { + DISABLE; + } + } diff --git a/Core/MIPS/ARM/ArmJit.cpp b/Core/MIPS/ARM/ArmJit.cpp index a1760d94bf..db8a391231 100644 --- a/Core/MIPS/ARM/ArmJit.cpp +++ b/Core/MIPS/ARM/ArmJit.cpp @@ -291,6 +291,7 @@ void Jit::LogBlockNumber() INFO_LOG(CPU, "Block number: %i", blocks.GetNumBlocks() - 1); } +void Jit::Comp_DoNothing(u32 op) { } #define _RS ((op>>21) & 0x1F) #define _RT ((op>>16) & 0x1F) diff --git a/Core/MIPS/ARM/ArmJit.h b/Core/MIPS/ARM/ArmJit.h index b9b2c1752c..e9cd7cc540 100644 --- a/Core/MIPS/ARM/ArmJit.h +++ b/Core/MIPS/ARM/ArmJit.h @@ -87,6 +87,7 @@ public: void Comp_RelBranchRI(u32 op); void Comp_FPUBranch(u32 op); void Comp_FPULS(u32 op); + void Comp_FPUComp(u32 op); void Comp_Jump(u32 op); void Comp_JumpReg(u32 op); void Comp_Syscall(u32 op); @@ -97,13 +98,18 @@ public: void Comp_ShiftType(u32 op); void Comp_Allegrex(u32 op); void Comp_VBranch(u32 op); - void Comp_VDot(u32 op); void Comp_MulDivType(u32 op); + void Comp_Special3(u32 op); void Comp_FPU3op(u32 op); void Comp_FPU2op(u32 op); void Comp_mxc1(u32 op); + void Comp_Mftv(u32 op); + void Comp_VDot(u32 op); + void Comp_DoNothing(u32 op); + + void Comp_SV(u32 op); void Comp_SVQ(u32 op); ArmJitBlockCache *GetBlockCache() { return &blocks; } diff --git a/Core/MIPS/MIPSTables.cpp b/Core/MIPS/MIPSTables.cpp index 580ce90510..145e236f46 100644 --- a/Core/MIPS/MIPSTables.cpp +++ b/Core/MIPS/MIPSTables.cpp @@ -147,7 +147,7 @@ const MIPSInstruction tableImmediate[64] = //xxxxxx ..... //48 INSTR("ll", &Jit::Comp_Generic, Dis_Generic, Int_StoreSync, 0), INSTR("lwc1", &Jit::Comp_FPULS, Dis_FPULS, Int_FPULS, IN_RT|IN_RS_ADDR), - INSTR("lv.s", &Jit::Comp_Generic, Dis_SV, Int_SV, IS_VFPU), + INSTR("lv.s", &Jit::Comp_SV, Dis_SV, Int_SV, IS_VFPU), {-2}, // HIT THIS IN WIPEOUT {VFPU4Jump}, INSTR("lv", &Jit::Comp_SVQ, Dis_SVLRQ, Int_SVQ, IS_VFPU), @@ -156,7 +156,7 @@ const MIPSInstruction tableImmediate[64] = //xxxxxx ..... //56 INSTR("sc", &Jit::Comp_Generic, Dis_Generic, Int_StoreSync, 0), INSTR("swc1", &Jit::Comp_FPULS, Dis_FPULS, Int_FPULS, 0), //copU - INSTR("sv.s", &Jit::Comp_Generic, Dis_SV, Int_SV,IS_VFPU), + INSTR("sv.s", &Jit::Comp_SV, Dis_SV, Int_SV,IS_VFPU), {-2}, //60 {VFPU6}, @@ -185,7 +185,7 @@ const MIPSInstruction tableSpecial[64] = /// 000000 ...... ...... .......... xxx INSTR("syscall", &Jit::Comp_Syscall, Dis_Syscall, Int_Syscall,0), INSTR("break", &Jit::Comp_Break, Dis_Generic, Int_Break, 0), {-2}, - INSTR("sync", &Jit::Comp_Generic, Dis_Generic, Int_Sync, 0), + INSTR("sync", &Jit::Comp_DoNothing, Dis_Generic, Int_Sync, 0), //16 INSTR("mfhi", &Jit::Comp_MulDivType, Dis_FromHiloTransfer, Int_MulDivType, OUT_RD|IN_OTHER), @@ -224,8 +224,8 @@ const MIPSInstruction tableSpecial[64] = /// 000000 ...... ...... .......... xxx INSTR("sltu", &Jit::Comp_RType3, Dis_RType3, Int_RType3,IN_RS|IN_RT|OUT_RD), INSTR("max", &Jit::Comp_RType3, Dis_RType3, Int_RType3,IN_RS|IN_RT|OUT_RD), INSTR("min", &Jit::Comp_RType3, Dis_RType3, Int_RType3,IN_RS|IN_RT|OUT_RD), - INSTR("msub", &Jit::Comp_Generic, Dis_MulDivType, Int_MulDivType, IN_RS|IN_RT|OUT_OTHER), - INSTR("msubu", &Jit::Comp_Generic, Dis_MulDivType, Int_MulDivType, IN_RS|IN_RT|OUT_OTHER), + INSTR("msub", &Jit::Comp_RType3, Dis_MulDivType, Int_MulDivType, IN_RS|IN_RT|OUT_OTHER), + INSTR("msubu", &Jit::Comp_RType3, Dis_MulDivType, Int_MulDivType, IN_RS|IN_RT|OUT_OTHER), //48 INSTR("tge", &Jit::Comp_Generic, Dis_RType3, 0, 0), @@ -276,32 +276,32 @@ const MIPSInstruction tableSpecial2[64] = //40 {-2}, {-2}, {-2}, {-2}, {-2}, {-2}, {-2}, {-2}, //48 - INSTR("c.f", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.un", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.eq", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ueq", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.olt", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ult", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ole", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ule", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.sf", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ngle",&Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.seq", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ngl", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.lt", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.nge", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.le", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), - INSTR("c.ngt", &Jit::Comp_Generic, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.f", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.un", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.eq", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ueq", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.olt", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ult", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ole", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ule", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.sf", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ngle",&Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.seq", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ngl", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.lt", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.nge", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.le", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), + INSTR("c.ngt", &Jit::Comp_FPUComp, Dis_FPUComp, Int_FPUComp, OUT_FPUFLAG), }; const MIPSInstruction tableSpecial3[64] = { - INSTR("ext", &Jit::Comp_Generic, Dis_Special3, Int_Special3, IN_RS|OUT_RT), + INSTR("ext", &Jit::Comp_Special3, Dis_Special3, Int_Special3, IN_RS|OUT_RT), {-2}, {-2}, {-2}, - INSTR("ins", &Jit::Comp_Generic, Dis_Special3, Int_Special3, IN_RS|OUT_RT), + INSTR("ins", &Jit::Comp_Special3, Dis_Special3, Int_Special3, IN_RS|OUT_RT), {-2}, {-2}, {-2}, @@ -363,11 +363,11 @@ const MIPSInstruction tableCop2[32] = INSTR("mfc2", &Jit::Comp_Generic, Dis_Generic, 0, OUT_RT), {-2}, INSTR("cfc2", &Jit::Comp_Generic, Dis_Generic, 0, 0), - INSTR("mfv", &Jit::Comp_Generic, Dis_Mftv, Int_Mftv, 0), + INSTR("mfv", &Jit::Comp_Mftv, Dis_Mftv, Int_Mftv, 0), INSTR("mtc2", &Jit::Comp_Generic, Dis_Generic, 0, IN_RT), {-2}, INSTR("ctc2", &Jit::Comp_Generic, Dis_Generic, 0, 0), - INSTR("mtv", &Jit::Comp_Generic, Dis_Mftv, Int_Mftv, 0), + INSTR("mtv", &Jit::Comp_Mftv, Dis_Mftv, Int_Mftv, 0), {Cop2BC2}, INSTR("??", &Jit::Comp_Generic, Dis_Generic, 0, 0), diff --git a/Core/MIPS/x86/CompALU.cpp b/Core/MIPS/x86/CompALU.cpp index 1f2cf0943f..2ffcd94d4b 100644 --- a/Core/MIPS/x86/CompALU.cpp +++ b/Core/MIPS/x86/CompALU.cpp @@ -335,6 +335,13 @@ namespace MIPSComp } } + void Jit::Comp_Special3(u32 op) + { + // ext, ins + DISABLE; + } + + void Jit::Comp_Allegrex(u32 op) { CONDITIONAL_DISABLE diff --git a/Core/MIPS/x86/CompFPU.cpp b/Core/MIPS/x86/CompFPU.cpp index 7ba16508a5..43170f0bf7 100644 --- a/Core/MIPS/x86/CompFPU.cpp +++ b/Core/MIPS/x86/CompFPU.cpp @@ -148,10 +148,6 @@ static const u64 GC_ALIGNED16(ssOneBits[2]) = {0x0000000100000001ULL, 0x00000001 static const u64 GC_ALIGNED16(ssSignBits2[2]) = {0x8000000080000000ULL, 0x8000000080000000ULL}; static const u64 GC_ALIGNED16(ssNoSignMask[2]) = {0x7FFFFFFF7FFFFFFFULL, 0x7FFFFFFF7FFFFFFFULL}; -<<<<<<< Updated upstream -void Jit::Comp_FPU2op(u32 op) -{ -======= void Jit::Comp_FPUComp(u32 op) { // TODO: Doesn't work yet. DISABLE; @@ -207,7 +203,6 @@ void Jit::Comp_FPUComp(u32 op) { } void Jit::Comp_FPU2op(u32 op) { ->>>>>>> Stashed changes CONDITIONAL_DISABLE; int fs = _FS; diff --git a/Core/MIPS/x86/CompVFPU.cpp b/Core/MIPS/x86/CompVFPU.cpp index 32a859d19b..bee71be757 100644 --- a/Core/MIPS/x86/CompVFPU.cpp +++ b/Core/MIPS/x86/CompVFPU.cpp @@ -141,11 +141,77 @@ void Jit::ApplyPrefixD(const u8 *vregs, u32 prefix, VectorSize sz, bool onlyWrit static u32 GC_ALIGNED16(ssLoadStoreTemp[1]); +void Jit::Comp_SV(u32 op) { + // DISABLE; + + s32 imm = (signed short)(op&0xFFFC); + int vt = ((op >> 16) & 0x1f) | ((op & 3) << 5); + int rs = _RS; + + switch (op >> 26) + { + case 50: //lv.s // VI(vt) = Memory::Read_U32(addr); + { + gpr.BindToRegister(rs, true, false); + fpr.MapRegV(vt, MAP_NOINIT); + + JitSafeMem safe(this, rs, imm); + safe.SetFar(); + OpArg src; + if (safe.PrepareRead(src)) + { + MOVSS(fpr.VX(vt), safe.NextFastAddress(0)); + } + if (safe.PrepareSlowRead((void *) &Memory::Read_U32)) + { + safe.NextSlowRead((void *) &Memory::Read_U32, 0); + MOV(32, M((void *)&ssLoadStoreTemp), R(EAX)); + MOVSS(fpr.VX(vt), M((void *)&ssLoadStoreTemp)); + } + safe.Finish(); + + gpr.UnlockAll(); + fpr.ReleaseSpillLocks(); + } + break; + + case 58: //sv.s // Memory::Write_U32(VI(vt), addr); + { + gpr.BindToRegister(rs, true, true); + + // Even if we don't use real SIMD there's still 8 or 16 scalar float registers. + fpr.MapRegV(vt, 0); + + JitSafeMem safe(this, rs, imm); + safe.SetFar(); + OpArg dest; + if (safe.PrepareWrite(dest)) + { + MOVSS(safe.NextFastAddress(0), fpr.VX(vt)); + } + if (safe.PrepareSlowWrite()) + { + MOVSS(M((void *)&ssLoadStoreTemp), fpr.VX(vt)); + safe.DoSlowWrite((void *) &Memory::Write_U32, M((void *)&ssLoadStoreTemp), 0); + } + safe.Finish(); + + fpr.ReleaseSpillLocks(); + gpr.UnlockAll(); + } + break; + + default: + _dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted"); + break; + } +} + void Jit::Comp_SVQ(u32 op) { int imm = (signed short)(op&0xFFFC); - int rs = _RS; int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5); + int rs = _RS; switch (op >> 26) { @@ -263,5 +329,47 @@ void Jit::Comp_VDot(u32 op) { js.EatPrefix(); } +void Jit::Comp_Mftv(u32 op) { + int imm = op & 0xFF; + int rt = _RT; + switch ((op >> 21) & 0x1f) + { + case 3: //mfv / mfvc + if (imm < 128) { //R(rt) = VI(imm); + fpr.StoreFromRegisterV(imm); + gpr.BindToRegister(rt, false, true); + MOV(32, gpr.R(rt), fpr.V(imm)); + } else if (imm < 128 + VFPU_CTRL_MAX) { //mtvc + gpr.BindToRegister(rt, false, true); + MOV(32, gpr.R(rt), M(¤tMIPS->vfpuCtrl[imm - 128])); + } else if (rt == 0 && imm == 255) { + // This appears to be used as a CPU interlock by some games. Do nothing. + } else { + //ERROR - maybe need to make this value too an "interlock" value? + _dbg_assert_msg_(CPU,0,"mfv - invalid register"); + } + break; + + case 7: //mtv + if (imm < 128) { + fpr.StoreFromRegisterV(imm); + gpr.BindToRegister(rt, true, false); + MOV(32, fpr.V(imm), gpr.R(rt)); + // VI(imm) = R(rt); + } else if (imm < 128 + VFPU_CTRL_MAX) { //mtvc //currentMIPS->vfpuCtrl[imm - 128] = R(rt); + gpr.BindToRegister(rt, true, false); + MOV(32, M(¤tMIPS->vfpuCtrl[imm - 128]), gpr.R(rt)); + } else { + //ERROR + _dbg_assert_msg_(CPU,0,"mtv - invalid register"); + } + break; + + default: + DISABLE; + _dbg_assert_msg_(CPU,0,"Trying to interpret instruction that can't be interpreted"); + break; + } +} } \ No newline at end of file diff --git a/Core/MIPS/x86/Jit.cpp b/Core/MIPS/x86/Jit.cpp index 5efaa0c7b8..dc6a789830 100644 --- a/Core/MIPS/x86/Jit.cpp +++ b/Core/MIPS/x86/Jit.cpp @@ -288,7 +288,31 @@ void Jit::WriteExitDestInEAX() // TODO: Some wasted potential, dispatcher will always read this back into EAX. MOV(32, M(&mips_->pc), R(EAX)); WriteDowncount(); - JMP(asm_.dispatcher, true); + + // Validate the jump to avoid a crash? + if (!g_Config.bFastMemory) + { + CMP(32, R(EAX), Imm32(PSP_GetKernelMemoryBase())); + FixupBranch tooLow = J_CC(CC_L); + CMP(32, R(EAX), Imm32(PSP_GetUserMemoryEnd())); + FixupBranch tooHigh = J_CC(CC_GE); + + JMP(asm_.dispatcher, true); + + SetJumpTarget(tooLow); + SetJumpTarget(tooHigh); + + ABI_CallFunctionA(thunks.ProtectFunction((void *) Memory::GetPointer, 1), R(EAX)); + CMP(32, R(EAX), Imm32(0)); + J_CC(CC_NE, asm_.dispatcher, true); + + // TODO: "Ignore" this so other threads can continue? + if (g_Config.bIgnoreBadMemAccess) + MOV(32, M((void*)&coreState), Imm32(CORE_ERROR)); + JMP(asm_.dispatcherCheckCoreState, true); + } + else + JMP(asm_.dispatcher, true); } void Jit::WriteSyscallExit() @@ -413,9 +437,9 @@ OpArg Jit::JitSafeMem::PrepareMemoryOpArg() if (!g_Config.bFastMemory) { // Is it in physical ram? - jit_->CMP(32, R(xaddr_), Imm32(PSP_GetKernelMemoryBase())); + jit_->CMP(32, R(xaddr_), Imm32(PSP_GetKernelMemoryBase() - offset_)); tooLow_ = jit_->J_CC(CC_L); - jit_->CMP(32, R(xaddr_), Imm32(PSP_GetUserMemoryEnd())); + jit_->CMP(32, R(xaddr_), Imm32(PSP_GetUserMemoryEnd() - offset_)); tooHigh_ = jit_->J_CC(CC_GE); // We may need to jump back up here. @@ -448,9 +472,9 @@ void Jit::JitSafeMem::PrepareSlowAccess() jit_->SetJumpTarget(tooHigh_); // Might also be the scratchpad. - jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryBase())); + jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryBase() - offset_)); FixupBranch tooLow = jit_->J_CC(CC_L); - jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryEnd())); + jit_->CMP(32, R(xaddr_), Imm32(PSP_GetScratchpadMemoryEnd() - offset_)); jit_->J_CC(CC_L, safe_); jit_->SetJumpTarget(tooLow); } @@ -545,4 +569,6 @@ void Jit::JitSafeMem::Finish() jit_->SetJumpTarget(skip_); } +void Jit::Comp_DoNothing(u32 op) { } + } // namespace diff --git a/Core/MIPS/x86/Jit.h b/Core/MIPS/x86/Jit.h index 234c0698ce..ff23c4d348 100644 --- a/Core/MIPS/x86/Jit.h +++ b/Core/MIPS/x86/Jit.h @@ -118,6 +118,7 @@ public: void Comp_RelBranchRI(u32 op); void Comp_FPUBranch(u32 op); void Comp_FPULS(u32 op); + void Comp_FPUComp(u32 op); void Comp_Jump(u32 op); void Comp_JumpReg(u32 op); void Comp_Syscall(u32 op); @@ -129,15 +130,20 @@ public: void Comp_Allegrex(u32 op); void Comp_VBranch(u32 op); void Comp_MulDivType(u32 op); + void Comp_Special3(u32 op); void Comp_FPU3op(u32 op); void Comp_FPU2op(u32 op); void Comp_mxc1(u32 op); + void Comp_SV(u32 op); void Comp_SVQ(u32 op); void Comp_VPFX(u32 op); void Comp_VDot(u32 op); - + void Comp_Mftv(u32 op); + + void Comp_DoNothing(u32 op); + void ApplyPrefixST(u8 *vregs, u32 prefix, VectorSize sz); void ApplyPrefixD(const u8 *vregs, u32 prefix, VectorSize sz, bool onlyWriteMask = false); diff --git a/Core/MIPS/x86/RegCacheFPU.cpp b/Core/MIPS/x86/RegCacheFPU.cpp index 9a2027e1e3..9c2cb9c837 100644 --- a/Core/MIPS/x86/RegCacheFPU.cpp +++ b/Core/MIPS/x86/RegCacheFPU.cpp @@ -59,6 +59,10 @@ void FPURegCache::SpillLockV(int vec, VectorSize sz) { SpillLockV(v, sz); } +void FPURegCache::MapRegV(int vreg, int flags) { + BindToRegister(vreg + 32, (flags & MAP_NOINIT) == 0, (flags & MAP_DIRTY) != 0); +} + void FPURegCache::MapRegsV(int vec, VectorSize sz, int flags) { u8 v[4]; GetVectorRegs(v, sz, vec); diff --git a/Core/MIPS/x86/RegCacheFPU.h b/Core/MIPS/x86/RegCacheFPU.h index 4102ef5ee6..cb4db210df 100644 --- a/Core/MIPS/x86/RegCacheFPU.h +++ b/Core/MIPS/x86/RegCacheFPU.h @@ -64,6 +64,9 @@ public: void Start(MIPSState *mips, MIPSAnalyst::AnalysisResults &stats); void BindToRegister(int preg, bool doLoad = true, bool makeDirty = true); void StoreFromRegister(int preg); + void StoreFromRegisterV(int preg) { + StoreFromRegister(preg + 32); + } OpArg GetDefaultLocation(int reg) const; void SetEmitter(XEmitter *emitter) {emit = emitter;} @@ -94,6 +97,7 @@ public: void SpillLock(int p1, int p2=0xff, int p3=0xff, int p4=0xff); void ReleaseSpillLocks(); + void MapRegV(int vreg, int flags); void MapRegsV(int vec, VectorSize vsz, int flags); void MapRegsV(const u8 *v, VectorSize vsz, int flags); void SpillLockV(const u8 *v, VectorSize vsz); diff --git a/Core/SaveState.cpp b/Core/SaveState.cpp index bf4a2a8fde..8a450081c3 100644 --- a/Core/SaveState.cpp +++ b/Core/SaveState.cpp @@ -77,9 +77,10 @@ namespace SaveState Memory::DoState(p); MemoryStick_DoState(p); currentMIPS->DoState(p); - pspFileSystem.DoState(p); HLEDoState(p); __KernelDoState(p); + // Kernel object destructors might close open files, so do the filesystem last. + pspFileSystem.DoState(p); } void Enqueue(SaveState::Operation op) diff --git a/GPU/GLES/DisplayListInterpreter.cpp b/GPU/GLES/DisplayListInterpreter.cpp index efaa5f5240..3d38b7d7ed 100644 --- a/GPU/GLES/DisplayListInterpreter.cpp +++ b/GPU/GLES/DisplayListInterpreter.cpp @@ -110,6 +110,7 @@ static const int flushOnChangedBeforeCommandList[] = { GE_CMD_TEXFORMAT, GE_CMD_TEXWRAP, GE_CMD_ZTESTENABLE, + GE_CMD_ZWRITEDISABLE, GE_CMD_STENCILTESTENABLE, GE_CMD_STENCILOP, GE_CMD_STENCILTEST, @@ -166,6 +167,8 @@ GLES_GPU::GLES_GPU() shaderManager_ = new ShaderManager(); transformDraw_.SetShaderManager(shaderManager_); transformDraw_.SetTextureCache(&textureCache_); + transformDraw_.SetFramebufferManager(&framebufferManager_); + framebufferManager_.SetTextureCache(&textureCache_); // Sanity check gstate if ((int *)&gstate.transferstart - (int *)&gstate != 0xEA) { @@ -448,7 +451,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { currentList->subIntrToken = data & 0xFFFF; // TODO: Should this run while interrupts are suspended? if (interruptsEnabled_) - __GeTriggerInterrupt(currentList->id, currentList->pc); + __GeTriggerInterrupt(currentList->id, currentList->pc, currentList->subIntrBase, currentList->subIntrToken); break; case GE_CMD_END: @@ -486,7 +489,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { } // TODO: Should this run while interrupts are suspended? if (interruptsEnabled_) - __GeTriggerInterrupt(currentList->id, currentList->pc); + __GeTriggerInterrupt(currentList->id, currentList->pc, currentList->subIntrBase, currentList->subIntrToken); } break; case GE_CMD_FINISH: @@ -677,6 +680,7 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { case GE_CMD_TEXSIZE0: gstate_c.curTextureWidth = 1 << (gstate.texsize[0] & 0xf); gstate_c.curTextureHeight = 1 << ((gstate.texsize[0]>>8) & 0xf); + shaderManager_->DirtyUniform(DIRTY_UVSCALEOFFSET); //fall thru - ignoring the mipmap sizes for now case GE_CMD_TEXSIZE1: case GE_CMD_TEXSIZE2: @@ -797,16 +801,8 @@ void GLES_GPU::ExecuteOp(u32 op, u32 diff) { case GE_CMD_VIEWPORTY1: case GE_CMD_VIEWPORTX2: case GE_CMD_VIEWPORTY2: - break; - case GE_CMD_VIEWPORTZ1: - gstate_c.zScale = getFloat24(data) / 65535.f; - break; - case GE_CMD_VIEWPORTZ2: - gstate_c.zOff = getFloat24(data) / 65535.f; - break; - case GE_CMD_LIGHTENABLE0: case GE_CMD_LIGHTENABLE1: case GE_CMD_LIGHTENABLE2: diff --git a/GPU/GLES/Framebuffer.cpp b/GPU/GLES/Framebuffer.cpp index ffdb8b2707..7090c2a784 100644 --- a/GPU/GLES/Framebuffer.cpp +++ b/GPU/GLES/Framebuffer.cpp @@ -29,6 +29,7 @@ #include "GPU/GPUState.h" #include "GPU/GLES/Framebuffer.h" +#include "GPU/GLES/TextureCache.h" static const char tex_fs[] = "#ifdef GL_ES\n" @@ -66,7 +67,8 @@ static bool MaskedEqual(u32 addr1, u32 addr2) { FramebufferManager::FramebufferManager() : displayFramebufPtr_(0), prevDisplayFramebuf_(0), - prevPrevDisplayFramebuf_(0) + prevPrevDisplayFramebuf_(0), + currentRenderVfb_(0) { glGenTextures(1, &backbufTex); @@ -216,6 +218,13 @@ FramebufferManager::VirtualFramebuffer *FramebufferManager::GetDisplayFBO() { return 0; } +void GetViewportDimensions(int *w, int *h) { + float vpXa = getFloat24(gstate.viewportx1); + float vpYa = getFloat24(gstate.viewporty1); + *w = (int)fabsf(vpXa * 2); + *h = (int)fabsf(vpYa * 2); +} + void FramebufferManager::SetRenderFrameBuffer() { if (!g_Config.bBufferedRendering) return; @@ -226,12 +235,23 @@ void FramebufferManager::SetRenderFrameBuffer() { u32 z_address = (gstate.zbptr & 0xFFE000) | ((gstate.zbwidth & 0xFF0000) << 8); int z_stride = gstate.zbwidth & 0x3C0; + // We guess that the viewport size during the first draw call is an appropriate + // size for a render target. + //UpdateViewportAndProjection(); + // Yeah this is not completely right. but it'll do for now. - int drawing_width = ((gstate.region2) & 0x3FF) + 1; - int drawing_height = ((gstate.region2 >> 10) & 0x3FF) + 1; + //int drawing_width = ((gstate.region2) & 0x3FF) + 1; + //int drawing_height = ((gstate.region2 >> 10) & 0x3FF) + 1; + + // As there are no clear "framebuffer width" and "framebuffer height" registers, + // we need to infer the size of the current framebuffer somehow. Let's try the viewport. + + int drawing_width, drawing_height; + GetViewportDimensions(&drawing_width, &drawing_height); // HACK for first frame where some games don't init things right - if (drawing_width == 1 && drawing_height == 1) { + + if (drawing_width <= 1 && drawing_height <= 1) { drawing_width = 480; drawing_height = 272; } @@ -251,6 +271,9 @@ void FramebufferManager::SetRenderFrameBuffer() { } } + float renderWidthFactor = (float)PSP_CoreParameter().renderWidth / 480.0f; + float renderHeightFactor = (float)PSP_CoreParameter().renderHeight / 272.0f; + // None found? Create one. if (!vfb) { gstate_c.textureChanged = true; @@ -261,6 +284,8 @@ void FramebufferManager::SetRenderFrameBuffer() { vfb->z_stride = z_stride; vfb->width = drawing_width; vfb->height = drawing_height; + vfb->renderWidth = (u16)(drawing_width * renderWidthFactor); + vfb->renderHeight = (u16)(drawing_height * renderHeightFactor); vfb->format = fmt; vfb->colorDepth = FBO_8888; @@ -273,15 +298,16 @@ void FramebufferManager::SetRenderFrameBuffer() { //#ifdef ANDROID // vfb->colorDepth = FBO_8888; //#endif - float renderWidthFactor = (float)PSP_CoreParameter().renderWidth / 480.0f; - float renderHeightFactor = (float)PSP_CoreParameter().renderHeight / 272.0f; - vfb->fbo = fbo_create((int)(vfb->width * renderWidthFactor), (int)(vfb->height * renderHeightFactor), 1, true, vfb->colorDepth); + + vfb->fbo = fbo_create(vfb->renderWidth, vfb->renderHeight, 1, true, vfb->colorDepth); + textureCache_->NotifyFramebuffer(vfb->fb_address, vfb->fbo); vfb->last_frame_used = gpuStats.numFrames; vfbs_.push_back(vfb); + fbo_bind_as_render_target(vfb->fbo); glEnable(GL_DITHER); - glstate.viewport.set(0, 0, PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); + glstate.viewport.set(0, 0, vfb->renderWidth, vfb->renderHeight); currentRenderVfb_ = vfb; glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); INFO_LOG(HLE, "Creating FBO for %08x : %i x %i x %i", vfb->fb_address, vfb->width, vfb->height, vfb->format); @@ -292,8 +318,15 @@ void FramebufferManager::SetRenderFrameBuffer() { // Use it as a render target. DEBUG_LOG(HLE, "Switching render target to FBO for %08x", vfb->fb_address); gstate_c.textureChanged = true; + if (vfb->last_frame_used != gpuStats.numFrames) { + // Android optimization + //glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + vfb->last_frame_used = gpuStats.numFrames; + fbo_bind_as_render_target(vfb->fbo); + textureCache_->NotifyFramebuffer(vfb->fb_address, vfb->fbo); #ifdef USING_GLES2 // Some tiled mobile GPUs benefit IMMENSELY from clearing an FBO before rendering // to it. This broke stuff before, so now it only clears on the first use of an @@ -302,9 +335,8 @@ void FramebufferManager::SetRenderFrameBuffer() { if (vfb->last_frame_used != gpuStats.numFrames) glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); #endif - glstate.viewport.set(0, 0, PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight); + glstate.viewport.set(0, 0, vfb->renderWidth, vfb->renderHeight); currentRenderVfb_ = vfb; - vfb->last_frame_used = gpuStats.numFrames; } } @@ -375,15 +407,16 @@ void FramebufferManager::SetDisplayFramebuffer(u32 framebuf, u32 stride, int for void FramebufferManager::DecimateFBOs() { for (auto iter = vfbs_.begin(); iter != vfbs_.end();) { - VirtualFramebuffer *v = *iter; - if (v == displayFramebuf_ || v == prevDisplayFramebuf_ || v == prevPrevDisplayFramebuf_) { + VirtualFramebuffer *vfb = *iter; + if (vfb == displayFramebuf_ || vfb == prevDisplayFramebuf_ || vfb == prevPrevDisplayFramebuf_) { ++iter; continue; } if ((*iter)->last_frame_used + FBO_OLD_AGE < gpuStats.numFrames) { - INFO_LOG(HLE, "Destroying FBO %i (%i x %i x %i)", v->fb_address, v->width, v->height, v->format) - fbo_destroy(v->fbo); - delete v; + INFO_LOG(HLE, "Destroying FBO for %08x (%i x %i x %i)", vfb->fb_address, vfb->width, vfb->height, vfb->format) + textureCache_->NotifyFramebufferDestroyed(vfb->fb_address, vfb->fbo); + fbo_destroy(vfb->fbo); + delete vfb; vfbs_.erase(iter++); } else @@ -393,9 +426,10 @@ void FramebufferManager::DecimateFBOs() { void FramebufferManager::DestroyAllFBOs() { for (auto iter = vfbs_.begin(); iter != vfbs_.end(); ++iter) { - VirtualFramebuffer *v = *iter; - fbo_destroy(v->fbo); - delete v; + VirtualFramebuffer *vfb = *iter; + textureCache_->NotifyFramebufferDestroyed(vfb->fb_address, vfb->fbo); + fbo_destroy(vfb->fbo); + delete vfb; } vfbs_.clear(); } diff --git a/GPU/GLES/Framebuffer.h b/GPU/GLES/Framebuffer.h index 9e5573dacf..84486413df 100644 --- a/GPU/GLES/Framebuffer.h +++ b/GPU/GLES/Framebuffer.h @@ -28,6 +28,7 @@ #include "../Globals.h" struct GLSLProgram; +class TextureCache; enum PspDisplayPixelFormat { PSP_DISPLAY_PIXEL_FORMAT_565 = 0, @@ -41,6 +42,10 @@ public: FramebufferManager(); ~FramebufferManager(); + void SetTextureCache(TextureCache *tc) { + textureCache_ = tc; + } + struct VirtualFramebuffer { int last_frame_used; @@ -50,8 +55,10 @@ public: int z_stride; // There's also a top left of the drawing region, but meh... - int width; - int height; + u16 width; + u16 height; + u16 renderWidth; + u16 renderHeight; int format; // virtual, right now they are all RGBA8888 FBOColorDepth colorDepth; @@ -73,6 +80,11 @@ public: void SetDisplayFramebuffer(u32 framebuf, u32 stride, int format); size_t NumVFBs() const { return vfbs_.size(); } + int GetRenderWidth() const { return currentRenderVfb_ ? currentRenderVfb_->renderWidth : 480; } + int GetRenderHeight() const { return currentRenderVfb_ ? currentRenderVfb_->renderHeight : 272; } + int GetTargetWidth() const { return currentRenderVfb_ ? currentRenderVfb_->width : 480; } + int GetTargetHeight() const { return currentRenderVfb_ ? currentRenderVfb_->height : 272; } + private: // Deletes old FBOs. @@ -93,5 +105,9 @@ private: u8 *convBuf; GLSLProgram *draw2dprogram; + + + TextureCache *textureCache_; + bool resized_; }; diff --git a/GPU/GLES/StateMapping.cpp b/GPU/GLES/StateMapping.cpp index 5d4a952404..9d9deca257 100644 --- a/GPU/GLES/StateMapping.cpp +++ b/GPU/GLES/StateMapping.cpp @@ -208,25 +208,20 @@ void TransformDrawEngine::ApplyDrawState(int prim) { bool wantDepthWrite = gstate.isModeClear() || gstate.isDepthWriteEnabled(); glstate.depthWrite.set(wantDepthWrite ? GL_TRUE : GL_FALSE); - - float depthRangeMin = gstate_c.zOff - gstate_c.zScale; - float depthRangeMax = gstate_c.zOff + gstate_c.zScale; - glstate.depthRange.set(depthRangeMin, depthRangeMax); } -void UpdateViewportAndProjection() { +void TransformDrawEngine::UpdateViewportAndProjection() { int renderWidth, renderHeight; - if (g_Config.bBufferedRendering) { - renderWidth = PSP_CoreParameter().renderWidth; - renderHeight = PSP_CoreParameter().renderHeight; + renderWidth = framebufferManager_->GetRenderWidth(); + renderHeight = framebufferManager_->GetRenderHeight(); } else { // TODO: Aspect-ratio aware and centered renderWidth = PSP_CoreParameter().pixelWidth; renderHeight = PSP_CoreParameter().pixelHeight; } - float renderWidthFactor = (float)renderWidth / 480.0f; - float renderHeightFactor = (float)renderHeight / 272.0f; + float renderWidthFactor = (float)renderWidth / framebufferManager_->GetTargetWidth(); + float renderHeightFactor = (float)renderHeight / framebufferManager_->GetTargetHeight(); bool throughmode = (gstate.vertType & GE_VTYPE_THROUGH_MASK) != 0; // We can probably use these to simply set scissors? Maybe we need to offset by regionX1/Y1 @@ -241,14 +236,13 @@ void UpdateViewportAndProjection() { if (throughmode) { // No viewport transform here. Let's experiment with using region. glstate.viewport.set((0 + regionX1) * renderWidthFactor, (0 - regionY1) * renderHeightFactor, (regionX2 - regionX1) * renderWidthFactor, (regionY2 - regionY1) * renderHeightFactor); + glstate.depthRange.set(1.0, 0.0); } else { // These we can turn into a glViewport call, offset by offsetX and offsetY. Math after. float vpXa = getFloat24(gstate.viewportx1); float vpXb = getFloat24(gstate.viewportx2); float vpYa = getFloat24(gstate.viewporty1); float vpYb = getFloat24(gstate.viewporty2); - float vpZa = getFloat24(gstate.viewportz1); // / 65536.0f should map it to OpenGL's 0.0-1.0 Z range - float vpZb = getFloat24(gstate.viewportz2); // / 65536.0f // The viewport transform appears to go like this: // Xscreen = -offsetX + vpXb + vpXa * Xview @@ -264,10 +258,6 @@ void UpdateViewportAndProjection() { float vpWidth = fabsf(gstate_c.vpWidth); float vpHeight = fabsf(gstate_c.vpHeight); - // TODO: These two should feed into glDepthRange somehow. - float vpZ0 = (vpZb - vpZa) / 65536.0f; - float vpZ1 = (vpZa * 2) / 65536.0f; - vpX0 *= renderWidthFactor; vpY0 *= renderHeightFactor; vpWidth *= renderWidthFactor; @@ -278,5 +268,11 @@ void UpdateViewportAndProjection() { glstate.viewport.set(vpX0, vpY0, vpWidth, vpHeight); // Sadly, as glViewport takes integers, we will not be able to support sub pixel offsets this way. But meh. // shaderManager_->DirtyUniform(DIRTY_PROJMATRIX); + + float zScale = getFloat24(gstate.viewportz1) / 65535.f; + float zOff = getFloat24(gstate.viewportz2) / 65535.f; + float depthRangeMin = zOff - zScale; + float depthRangeMax = zOff + zScale; + glstate.depthRange.set(depthRangeMin, depthRangeMax); } } diff --git a/GPU/GLES/StateMapping.h b/GPU/GLES/StateMapping.h index 0bff9bd723..dc720e8eee 100644 --- a/GPU/GLES/StateMapping.h +++ b/GPU/GLES/StateMapping.h @@ -6,5 +6,4 @@ extern const GLint eqLookup[]; extern const GLint cullingMode[]; extern const GLuint ztests[]; -void UpdateViewportAndProjection(); diff --git a/GPU/GLES/TextureCache.cpp b/GPU/GLES/TextureCache.cpp index 8edd260ddf..023e30c181 100644 --- a/GPU/GLES/TextureCache.cpp +++ b/GPU/GLES/TextureCache.cpp @@ -111,6 +111,33 @@ void TextureCache::InvalidateAll(bool force) { Invalidate(0, 0xFFFFFFFF, force); } +TextureCache::TexCacheEntry *TextureCache::GetEntryAt(u32 texaddr) { + // If no CLUT, as in framebuffer textures, cache key is simply texaddr. + auto iter = cache.find(texaddr); + if (iter != cache.end() && iter->second.addr == texaddr) + return &iter->second; + else + return 0; +} + +void TextureCache::NotifyFramebuffer(u32 address, FBO *fbo) { + // Must be in VRAM so | 0x04000000 it is. + TexCacheEntry *entry = GetEntryAt(address | 0x04000000); + if (entry) { + // INFO_LOG(HLE, "Render to texture detected at %08x!", address); + if (!entry->fbo) + entry->fbo = fbo; + // TODO: Delete the original non-fbo texture too. + } +} + +void TextureCache::NotifyFramebufferDestroyed(u32 address, FBO *fbo) { + TexCacheEntry *entry = GetEntryAt(address | 0x04000000); + if (entry && entry->fbo) { + entry->fbo = 0; + } +} + static u32 GetClutAddr(u32 clutEntrySize) { return ((gstate.clutaddr & 0xFFFFFF) | ((gstate.clutaddrupper << 8) & 0x0F000000)) + ((gstate.clutformat >> 16) & 0x1f) * clutEntrySize; } @@ -363,7 +390,8 @@ static const GLuint MagFiltGL[2] = { GL_LINEAR }; -// OpenGL ES 2.0 workaround. Let's see if this hackery works. +// OpenGL ES 2.0 workaround. This SHOULD be available but is NOT in the headers in Android. +// Let's see if this hackery works. #ifndef GL_TEXTURE_LOD_BIAS #define GL_TEXTURE_LOD_BIAS 0x8501 #endif @@ -372,8 +400,6 @@ static const GLuint MagFiltGL[2] = { #define GL_TEXTURE_MAX_LOD 0x813B #endif - - // This should not have to be done per texture! OpenGL is silly yo // TODO: Dirty-check this against the current texture. void TextureCache::UpdateSamplingParams(TexCacheEntry &entry, bool force) { @@ -641,23 +667,52 @@ void TextureCache::SetTexture() { ERROR_LOG(G3D, "Unknown texture format %i", format); format = 0; } + bool hasClut = formatUsesClut[format]; + + const u8 *texptr = Memory::GetPointer(texaddr); u32 clutformat = gstate.clutformat & 3; u32 clutaddr = GetClutAddr(clutformat == GE_CMODE_32BIT_ABGR8888 ? 4 : 2); + u64 cachekey = texaddr; + if (formatUsesClut[format]) + cachekey |= (u64)clutaddr << 32; + int maxLevel = ((gstate.texmode >> 16) & 0x7); - const u8 *texptr = Memory::GetPointer(texaddr); - u32 texhash = texptr ? MiniHash((const u32*)texptr) : 0; + // Adjust maxLevel to actually present levels.. + for (int i = 0; i <= maxLevel; i++) { + // If encountering levels pointing to nothing, adjust max level. + u32 levelTexaddr = (gstate.texaddr[i] & 0xFFFFF0) | ((gstate.texbufwidth[i] << 8) & 0x0F000000); + if (!Memory::IsValidAddress(levelTexaddr)) { + maxLevel = i - 1; + break; + } + } - u64 cachekey = texaddr ^ texhash; - if (formatUsesClut[format]) - cachekey |= (u64) clutaddr << 32; + u32 texhash = MiniHash((const u32 *)Memory::GetPointer(texaddr)); + + int w = 1 << (gstate.texsize[0] & 0xf); + int h = 1 << ((gstate.texsize[0] >> 8) & 0xf); TexCache::iterator iter = cache.find(cachekey); if (iter != cache.end()) { - //Validate the texture here (width, height etc) TexCacheEntry &entry = iter->second; + // Check for FBO - slow! + if (entry.fbo) { + fbo_bind_color_as_texture(entry.fbo, 0); + UpdateSamplingParams(entry, false); + + int fbow, fboh; + fbo_get_dimensions(entry.fbo, &fbow, &fboh); + + // Almost certain this isn't right. + gstate_c.curTextureWidth = fbow; + gstate_c.curTextureHeight = fboh; + return; + } + + //Validate the texture here (width, height etc) int dim = gstate.texsize[0] & 0xF0F; bool match = true; @@ -716,6 +771,7 @@ void TextureCache::SetTexture() { entry.hash = texhash; entry.format = format; entry.frameCounter = gpuStats.numFrames; + entry.fbo = 0; entry.maxLevel = maxLevel; entry.lodBias = 0.0f; @@ -731,9 +787,6 @@ void TextureCache::SetTexture() { entry.dim = gstate.texsize[0] & 0xF0F; - int w = 1 << (gstate.texsize[0] & 0xf); - int h = 1 << ((gstate.texsize[0] >> 8) & 0xf); - // This would overestimate the size in many case so we underestimate instead // to avoid excessive clearing caused by cache invalidations. entry.sizeInRAM = (bitsPerPixel[format < 11 ? format : 0] * bufw * h / 2) / 8; @@ -748,21 +801,26 @@ void TextureCache::SetTexture() { glGenTextures(1, &entry.texture); glBindTexture(GL_TEXTURE_2D, entry.texture); +#ifdef USING_GLES2 + // GLES2 doesn't have support for a "Max lod" which is critical as PSP games often + // don't specify mips all the way down. As a result, we either need to manually generate + // the bottom few levels or rely on OpenGL's autogen mipmaps instead, which might not + // be as good quality as the game's own (might even be better in some cases though). + + // For now, I choose to use autogen mips on GLES2 and the game's own on other platforms. + // As is usual, GLES3 will solve this problem nicely but wide distribution of that is + // years away. + LoadTextureLevel(entry, 0); + if (entry.maxLevel > 0) + glGenerateMipmap(GL_TEXTURE_2D); +#else for (int i = 0; i <= entry.maxLevel; i++) { - // If encountering levels pointing to nothing, adjust max level. - u32 levelTexaddr = (gstate.texaddr[i] & 0xFFFFF0) | ((gstate.texbufwidth[i] << 8) & 0x0F000000); - if (!Memory::IsValidAddress(levelTexaddr)) { - entry.maxLevel = i - 1; - break; - } LoadTextureLevel(entry, i); } - -#ifndef USING_GLES2 - // See horrifying hack at the bottom of LoadTextureLevel! glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LEVEL, entry.maxLevel); #endif - glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, entry.maxLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_LOD, (float)entry.maxLevel); + glTexParameterf(GL_TEXTURE_2D, GL_TEXTURE_MAX_ANISOTROPY_EXT, 4.0); UpdateSamplingParams(entry, true); @@ -799,7 +857,7 @@ void TextureCache::LoadTextureLevel(TexCacheEntry &entry, int level) switch (entry.format) { case GE_TFMT_CLUT4: - dstFmt = getClutDestFormat((GEPaletteFormat)(gstate.clutformat & 3)); + dstFmt = getClutDestFormat((GEPaletteFormat)(entry.clutformat)); switch (entry.clutformat) { case GE_CMODE_16BIT_BGR5650: @@ -808,15 +866,15 @@ void TextureCache::LoadTextureLevel(TexCacheEntry &entry, int level) { ReadClut16(clutBuf16); const u16 *clut = clutBuf16; - u32 clutSharingOff = 0;//gstate.mipmapShareClut ? 0 : level * 16; + u32 clutSharingOffset = 0; //(gstate.mipmapShareClut & 1) ? 0 : level * 16; texByteAlign = 2; if (!(gstate.texmode & 1)) { const u8 *addr = Memory::GetPointer(texaddr); for (int i = 0; i < bufw * h; i += 2) { u8 index = *addr++; - tmpTexBuf16[i + 0] = clut[GetClutIndex((index >> 0) & 0xf) + clutSharingOff]; - tmpTexBuf16[i + 1] = clut[GetClutIndex((index >> 4) & 0xf) + clutSharingOff]; + tmpTexBuf16[i + 0] = clut[GetClutIndex((index >> 0) & 0xf) + clutSharingOffset]; + tmpTexBuf16[i + 1] = clut[GetClutIndex((index >> 4) & 0xf) + clutSharingOffset]; } } else { UnswizzleFromMem(texaddr, 0, level); @@ -826,7 +884,7 @@ void TextureCache::LoadTextureLevel(TexCacheEntry &entry, int level) u32 k, index; for (k = 0; k < 8; k++) { index = (n >> (k * 4)) & 0xf; - tmpTexBuf16[i + k] = clut[GetClutIndex(index) + clutSharingOff]; + tmpTexBuf16[i + k] = clut[GetClutIndex(index) + clutSharingOffset]; } } } @@ -1025,27 +1083,8 @@ void TextureCache::LoadTextureLevel(TexCacheEntry &entry, int level) //glPixelStorei(GL_PACK_ROW_LENGTH, bufw); glPixelStorei(GL_PACK_ALIGNMENT, texByteAlign); - INFO_LOG(HLE, "Creating texture level %i/%i from %08x: %i x %i (stride: %i). fmt: %i", level, entry.maxLevel, texaddr, w, h, bufw, entry.format); + // INFO_LOG(G3D, "Creating texture level %i/%i from %08x: %i x %i (stride: %i). fmt: %i", level, entry.maxLevel, texaddr, w, h, bufw, entry.format); GLuint components = dstFmt == GL_UNSIGNED_SHORT_5_6_5 ? GL_RGB : GL_RGBA; glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components, dstFmt, finalBuf); - -#ifdef USING_GLES2 - // ARGH! OpenGL ES does not support max texture level! - // Let's do a HORRIBLE hack for now and re-specify the last level, but with changed dimensions. - // Will at least give us sort of the right colors and hopefully it'll be too blurry anyway. - // Later I will add proper downsampling of the bottom level. - // TEXTURE_MAX_LOD should ensure that we never get to see these anyway. - if (level == entry.maxLevel) { - while (w >= 2 || h >= 2) { - w /= 2; - h /= 2; - if (w == 0) w = 1; - if (h == 0) h = 1; - ++level; - INFO_LOG(HLE, "Specifying extra texture level %i : %ix%i", level, w, h); - glTexImage2D(GL_TEXTURE_2D, level, components, w, h, 0, components, dstFmt, finalBuf); - } - } -#endif -} \ No newline at end of file +} diff --git a/GPU/GLES/TextureCache.h b/GPU/GLES/TextureCache.h index 891ceb1e84..7f8e4ad9a7 100644 --- a/GPU/GLES/TextureCache.h +++ b/GPU/GLES/TextureCache.h @@ -18,6 +18,7 @@ #pragma once #include "../Globals.h" +#include "gfx_es2/fbo.h" class TextureCache { @@ -32,21 +33,28 @@ public: void Invalidate(u32 addr, int size, bool force); void InvalidateAll(bool force); + // FramebufferManager keeps TextureCache updated about what regions of memory + // are being rendered to. This is barebones so far. + void NotifyFramebuffer(u32 address, FBO *fbo); + void NotifyFramebufferDestroyed(u32 address, FBO *fbo); + size_t NumLoadedTextures() const { return cache.size(); } private: + struct TexCacheEntry { u32 addr; u32 hash; + FBO *fbo; // if null, not sourced from an FBO. u32 sizeInRAM; int frameCounter; - u32 format; + u8 format; + u8 clutformat; + u16 dim; u32 clutaddr; - u32 clutformat; u32 cluthash; - int dim; u32 texture; //GLuint int invalidHint; u32 fullhash; @@ -66,6 +74,8 @@ private: void UpdateSamplingParams(TexCacheEntry &entry, bool force); void LoadTextureLevel(TexCacheEntry &entry, int level); + TexCacheEntry *GetEntryAt(u32 texaddr); + typedef std::map TexCache; // TODO: Speed up by switching to ReadUnchecked*. diff --git a/GPU/GLES/TransformPipeline.h b/GPU/GLES/TransformPipeline.h index adbbc78f7c..91e0219bb5 100644 --- a/GPU/GLES/TransformPipeline.h +++ b/GPU/GLES/TransformPipeline.h @@ -24,6 +24,8 @@ class LinkedShader; class ShaderManager; class TextureCache; +class FramebufferManager; + struct DecVtxFormat; // States transitions: @@ -103,7 +105,9 @@ public: void SetTextureCache(TextureCache *textureCache) { textureCache_ = textureCache; } - + void SetFramebufferManager(FramebufferManager *fbManager) { + framebufferManager_ = fbManager; + } void InitDeviceObjects(); void DestroyDeviceObjects(); void GLLost(); @@ -114,6 +118,7 @@ public: private: void SoftwareTransformAndDraw(int prim, u8 *decoded, LinkedShader *program, int vertexCount, u32 vertexType, void *inds, int indexType, const DecVtxFormat &decVtxFormat, int maxIndex); void ApplyDrawState(int prim); + void UpdateViewportAndProjection(); // drawcall ID u32 ComputeFastDCID(); @@ -158,6 +163,7 @@ private: // Other ShaderManager *shaderManager_; TextureCache *textureCache_; + FramebufferManager *framebufferManager_; enum { MAX_DEFERRED_DRAW_CALLS = 128 }; DeferredDrawCall drawCalls[MAX_DEFERRED_DRAW_CALLS]; diff --git a/GPU/GLES/VertexShaderGenerator.cpp b/GPU/GLES/VertexShaderGenerator.cpp index d6cc3770c9..f9da12ed74 100644 --- a/GPU/GLES/VertexShaderGenerator.cpp +++ b/GPU/GLES/VertexShaderGenerator.cpp @@ -375,7 +375,7 @@ void GenerateVertexShader(int prim, char *buffer) { WRITE(p, " vec3 temp_tc = a_position.xyz;\n"); break; case 1: // Use unscaled UV as source - WRITE(p, " vec3 temp_tc = vec3(a_texcoord.xy * 2.0f, 0.0);\n"); + WRITE(p, " vec3 temp_tc = vec3(a_texcoord.xy * 2.0, 0.0);\n"); break; case 2: // Use normalized transformed normal as source WRITE(p, " vec3 temp_tc = normalize(a_normal);\n"); diff --git a/GPU/GPUState.h b/GPU/GPUState.h index 4710856152..84d9d8dcea 100644 --- a/GPU/GPUState.h +++ b/GPU/GPUState.h @@ -236,8 +236,8 @@ struct GPUStateCache bool textureChanged; - float uScale,vScale,zScale; - float uOff,vOff,zOff; + float uScale,vScale; + float uOff,vOff; float zMin, zMax; float lightpos[4][3]; float lightdir[4][3]; diff --git a/GPU/GeDisasm.cpp b/GPU/GeDisasm.cpp index 49d0c735f4..c75dc1ba93 100644 --- a/GPU/GeDisasm.cpp +++ b/GPU/GeDisasm.cpp @@ -521,13 +521,13 @@ void GeDisassembleOp(u32 pc, u32 op, u32 prev, char *buffer) { case GE_CMD_VIEWPORTZ1: { float zScale = getFloat24(data) / 65535.f; - sprintf(buffer, "Z scale: %f", zScale); + sprintf(buffer, "Viewport Z scale: %f", zScale); } break; case GE_CMD_VIEWPORTZ2: { float zOff = getFloat24(data) / 65535.f; - sprintf(buffer, "Z pos: %f", zOff); + sprintf(buffer, "Viewport Z pos: %f", zOff); } break; diff --git a/GPU/Null/NullGpu.cpp b/GPU/Null/NullGpu.cpp index 73517092ac..f9153be506 100644 --- a/GPU/Null/NullGpu.cpp +++ b/GPU/Null/NullGpu.cpp @@ -154,7 +154,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) // TODO: Should this run while interrupts are suspended? if (interruptsEnabled_) - __GeTriggerInterrupt(currentList->id, currentList->pc); + __GeTriggerInterrupt(currentList->id, currentList->pc, currentList->subIntrBase, currentList->subIntrToken); } break; @@ -188,7 +188,7 @@ void NullGPU::ExecuteOp(u32 op, u32 diff) currentList->subIntrToken = data & 0xFFFF; // TODO: Should this run while interrupts are suspended? if (interruptsEnabled_) - __GeTriggerInterrupt(currentList->id, currentList->pc); + __GeTriggerInterrupt(currentList->id, currentList->pc, currentList->subIntrBase, currentList->subIntrToken); break; case GE_CMD_END: diff --git a/Qt/PPSSPP.pro b/Qt/PPSSPP.pro index 0fd569e699..3446744f32 100755 --- a/Qt/PPSSPP.pro +++ b/Qt/PPSSPP.pro @@ -21,6 +21,9 @@ desktop_ui { } } +TRANSLATIONS = languages/ppsspp_en.ts \ + languages/ppsspp_pl.ts + # Main SOURCES += ../native/base/QtMain.cpp HEADERS += ../native/base/QtMain.h diff --git a/Qt/gamepaddialog.cpp b/Qt/gamepaddialog.cpp index 858a17f9db..5094fc032c 100644 --- a/Qt/gamepaddialog.cpp +++ b/Qt/gamepaddialog.cpp @@ -16,26 +16,26 @@ struct GamePadInfo // Initial values are PS3 controller GamePadInfo GamepadPadMapping[] = { - {0, 14, 0, "Prev_X", "Cross"}, //A - {0, 13, 0, "Prev_O", "Circle"}, //B - {0, 15, 0, "Prev_S", "Square"}, //X - {0, 12, 0, "Prev_T", "Triangle"}, //Y - {0, 10, 0, "Prev_LT", "Left Trigger"}, //LBUMPER - {0, 11, 0, "Prev_RT", "Right Trigger"}, //RBUMPER - {0, 3, 0, "Prev_Start", "Start"}, //START - {0, 0, 0, "Prev_Select", "Select"}, //SELECT - {0, 4, 0, "Prev_Up", "Up"}, //UP - {0, 6, 0, "Prev_Down", "Down"}, //DOWN - {0, 7, 0, "Prev_Left", "Left"}, //LEFT - {0, 5, 0, "Prev_Right", "Right"}, //RIGHT - {0, 0, 0, ""}, //MENU (event) - {0, 16, 0, "Prev_Home", "Home"}, //BACK + {0, 14, 0, "Prev_X", QT_TRANSLATE_NOOP("gamepadMapping", "Cross")}, //A + {0, 13, 0, "Prev_O", QT_TRANSLATE_NOOP("gamepadMapping", "Circle")}, //B + {0, 15, 0, "Prev_S", QT_TRANSLATE_NOOP("gamepadMapping", "Square")}, //X + {0, 12, 0, "Prev_T", QT_TRANSLATE_NOOP("gamepadMapping", "Triangle")}, //Y + {0, 10, 0, "Prev_LT", QT_TRANSLATE_NOOP("gamepadMapping", "Left Trigger")}, //LBUMPER + {0, 11, 0, "Prev_RT", QT_TRANSLATE_NOOP("gamepadMapping", "Right Trigger")}, //RBUMPER + {0, 3, 0, "Prev_Start", QT_TRANSLATE_NOOP("gamepadMapping", "Start")}, //START + {0, 0, 0, "Prev_Select", QT_TRANSLATE_NOOP("gamepadMapping", "Select")}, //SELECT + {0, 4, 0, "Prev_Up", QT_TRANSLATE_NOOP("gamepadMapping", "Up")}, //UP + {0, 6, 0, "Prev_Down", QT_TRANSLATE_NOOP("gamepadMapping", "Down")}, //DOWN + {0, 7, 0, "Prev_Left", QT_TRANSLATE_NOOP("gamepadMapping", "Left")}, //LEFT + {0, 5, 0, "Prev_Right", QT_TRANSLATE_NOOP("gamepadMapping", "Right")}, //RIGHT + {0, 0, 0, ""}, //MENU (event) + {0, 16, 0, "Prev_Home", QT_TRANSLATE_NOOP("gamepadMapping", "Home")}, //BACK // Special case for analog stick - {1, 0, -1, "Prev_ALeft", "Stick left"}, - {1, 0, 1, "Prev_ARight", "Stick right"}, - {1, 1, -1, "Prev_AUp", "Stick up"}, - {1, 1, 1, "Prev_ADown", "Stick bottom"} + {1, 0, -1, "Prev_ALeft", QT_TRANSLATE_NOOP("gamepadMapping", "Stick left")}, + {1, 0, 1, "Prev_ARight", QT_TRANSLATE_NOOP("gamepadMapping", "Stick right")}, + {1, 1, -1, "Prev_AUp", QT_TRANSLATE_NOOP("gamepadMapping", "Stick up")}, + {1, 1, 1, "Prev_ADown", QT_TRANSLATE_NOOP("gamepadMapping", "Stick bottom")} }; // id for mapping in config start at offset 200 to not get over key mapping @@ -106,6 +106,20 @@ void GamePadDialog::showEvent(QShowEvent *) #endif } +void GamePadDialog::changeEvent(QEvent *event) +{ + QDialog::changeEvent(event); + + if (0 != event) + { + if (event->type() == QEvent::LanguageChange) + { + ui->retranslateUi(this); + on_refreshListBtn_clicked(); + } + } +} + void GamePadDialog::releaseLock() { EmuThread_LockDraw(false); @@ -117,7 +131,7 @@ void GamePadDialog::on_refreshListBtn_clicked() if(m_joystick) { SDL_JoystickClose(m_joystick); - ui->JoyName->setText("No GamePad"); + ui->JoyName->setText(tr("No gamepad")); m_joystick = 0; } SDL_QuitSubSystem(SDL_INIT_JOYSTICK); @@ -129,7 +143,7 @@ void GamePadDialog::on_refreshListBtn_clicked() { QListWidgetItem* item = new QListWidgetItem(); QString padName = SDL_JoystickName(i); - if(padName == "") padName = "Unknown GamePad"; + if(padName == "") padName = tr("Unknown gamepad"); item->setText(padName); item->setData(Qt::UserRole,i); ui->GamePadList->addItem(item); @@ -270,7 +284,7 @@ void GamePadDialog::on_SelectPadBtn_clicked() ui->comboPSPButton->clear(); QTreeWidgetItem* buttonItem = new QTreeWidgetItem(); - buttonItem->setText(0,"Buttons"); + buttonItem->setText(0,tr("Buttons")); ui->padValues->addTopLevelItem(buttonItem); for(int i = 0; i < SDL_JoystickNumButtons(m_joystick); i++) @@ -284,37 +298,37 @@ void GamePadDialog::on_SelectPadBtn_clicked() buttonItem->addChild(item); int id = i << 8; - ui->comboPadInput->addItem("Button "+QVariant(i).toString(),GetIntFromMapping(i,0,0)); + ui->comboPadInput->addItem(tr("Button %1").arg(i),GetIntFromMapping(i,0,0)); } QTreeWidgetItem* axesItem = new QTreeWidgetItem(); - axesItem->setText(0,"Axes"); + axesItem->setText(0,tr("Axes")); ui->padValues->addTopLevelItem(axesItem); for(int i = 0; i < SDL_JoystickNumAxes(m_joystick); i++) { QTreeWidgetItem* item = new QTreeWidgetItem(); - item->setText(0,QVariant(i).toString()+" Neg"); + item->setText(0,tr("%1 Neg").arg(i)); item->setText(1,QVariant(0).toString()); item->setData(0, Qt::UserRole,1); item->setData(0, Qt::UserRole+1,i); item->setData(0, Qt::UserRole+2,-1); axesItem->addChild(item); - ui->comboPadInput->addItem("Axes "+QVariant(i).toString()+" Neg",GetIntFromMapping(i,1,-1)); + ui->comboPadInput->addItem(tr("Axes %1 Neg").arg(i),GetIntFromMapping(i,1,-1)); item = new QTreeWidgetItem(); - item->setText(0,QVariant(i).toString()+" Pos"); + item->setText(0,tr("%1 Pos").arg(i)); item->setText(1,QVariant(0).toString()); item->setData(0, Qt::UserRole,1); item->setData(0, Qt::UserRole+1,i); item->setData(0, Qt::UserRole+2,1); axesItem->addChild(item); - ui->comboPadInput->addItem("Axes "+QVariant(i).toString()+" Pos",GetIntFromMapping(i,1,1)); + ui->comboPadInput->addItem(tr("Axes %1 Pos").arg(i),GetIntFromMapping(i,1,1)); } QTreeWidgetItem* hatsItem = new QTreeWidgetItem(); - hatsItem->setText(0,"Hats"); + hatsItem->setText(0,tr("Hats")); ui->padValues->addTopLevelItem(hatsItem); for(int i = 0; i < SDL_JoystickNumHats(m_joystick); i++) @@ -327,14 +341,14 @@ void GamePadDialog::on_SelectPadBtn_clicked() item->setData(0, Qt::UserRole+2,0); hatsItem->addChild(item); - ui->comboPadInput->addItem("Button "+QVariant(i).toString(),GetIntFromMapping(i,2,0)); + ui->comboPadInput->addItem(tr("Button %1").arg(i),GetIntFromMapping(i,2,0)); } for(int i = 0; i < 18; i++) { if(GamepadPadMapping[i].Name != "") { - ui->comboPSPButton->addItem(GamepadPadMapping[i].Name,i); + ui->comboPSPButton->addItem(QApplication::translate("gamepadMapping", GamepadPadMapping[i].Name.toStdString().c_str()),i); } } @@ -349,9 +363,9 @@ void GamePadDialog::SetViewMode() ui->refreshListBtn->setEnabled(true); ui->SelectPadBtn->setEnabled(true); if(!m_joystick) - ui->JoyName->setText("No GamePad"); + ui->JoyName->setText(tr("No gamepad")); else - ui->JoyName->setText(QString("Current gamepad : ")+SDL_JoystickName(m_joyId)+""); + ui->JoyName->setText(tr("Current gamepad: %1").arg(SDL_JoystickName(m_joyId))); #endif } diff --git a/Qt/gamepaddialog.h b/Qt/gamepaddialog.h index 4b1fe745ae..24163731cc 100644 --- a/Qt/gamepaddialog.h +++ b/Qt/gamepaddialog.h @@ -25,6 +25,7 @@ public: void CalibNextButton(); protected: void showEvent(QShowEvent *); + void changeEvent(QEvent *); private slots: void releaseLock(); void on_refreshListBtn_clicked(); diff --git a/Qt/languages/ppsspp_en.ts b/Qt/languages/ppsspp_en.ts new file mode 100644 index 0000000000..9343a24500 --- /dev/null +++ b/Qt/languages/ppsspp_en.ts @@ -0,0 +1,634 @@ + + + + + Controls + + + Controls + + + + + Debugger_Disasm + + + Dialog + + + + + Ctr: + + + + + &Go to + + + + + &PC + + + + + &LR + + + + + Tab 1 + + + + + Tab 2 + + + + + &Go + + + + + Stop + + + + + Step &Into + + + + + Step &Over + + + + + S&kip + + + + + Next &HLE + + + + + GamePadDialog + + + Gamepad Configuration + + + + + GamePad List + + + + + Refresh + + + + + Select + + + + + Gamepad Values : + + + + + TextLabel + + + + + Assign Gamepad input + + + + + to PSP button/axis + + + + + Assign + + + + + Press buttons on your gamePad to verify mapping : + + + + + + <b>No gamepad</b> + + + + + <b>Unknown gamepad</b> + + + + + Buttons + + + + + + Button %1 + + + + + Axes + + + + + %1 Neg + + + + + Axes %1 Neg + + + + + %1 Pos + + + + + Axes %1 Pos + + + + + Hats + + + + + <b>Current gamepad: %1</b> + + + + + MainWindow + + + PPSSPP + + + + + &File + + + + + &Emulation + + + + + Debu&g + + + + + &Options + + + + + &Log Levels + + + + + G3D + + + + + HLE + + + + + Default + + + + + Zoom + + + + + Language + + + + + &Help + + + + + &Open... + + + + + &Close + + + + + - + + + + + Quickload state + + + + + F4 + + + + + Quicksave state + + + + + F2 + + + + + &Load State File... + + + + + &Save State File... + + + + + E&xit + + + + + &Run + + + + + F7 + + + + + &Pause + + + + + F8 + + + + + R&eset + + + + + &Interpreter + + + + + &Slightly Faster Interpreter + + + + + &Dynarec + + + + + Load &Map File... + + + + + &Save Map File... + + + + + &Reset Symbol Table + + + + + &Disassembly + + + + + Ctrl+D + + + + + &Log Console + + + + + Ctrl+L + + + + + Memory &View... + + + + + Ctrl+M + + + + + Keyboard &Controls + + + + + &Toggle Full Screen + + + + + F12 + + + + + &Buffered Rendering + + + + + F5 + + + + + &Hardware Transform + + + + + F6 + + + + + &Linear Filtering + + + + + &Wireframe (experimental) + + + + + &Display Raw Framebuffer + + + + + &Show Debug Statistics + + + + + Screen &1x + + + + + Ctrl+1 + + + + + Screen &2x + + + + + Ctrl+2 + + + + + Screen &3x + + + + + Ctrl+3 + + + + + Screen &4x + + + + + Ctrl+4 + + + + + &Fast Memory (dynarec, unstable) + + + + + &Ignore illegal reads/writes + + + + + &Go to http://www.ppsspp.org/ + + + + + &About PPSSPP... + + + + + &Use VBO + + + + + + + Debug + + + + + + + Warning + + + + + + + Error + + + + + + + Info + + + + + GamePad Controls + + + + + No translations + + + + + gamepadMapping + + + Cross + + + + + Circle + + + + + Square + + + + + Triangle + + + + + Left Trigger + + + + + Right Trigger + + + + + Start + + + + + Select + + + + + Up + + + + + Down + + + + + Left + + + + + Right + + + + + Home + + + + + Stick left + + + + + Stick right + + + + + Stick up + + + + + Stick bottom + + + + diff --git a/Qt/languages/ppsspp_pl.ts b/Qt/languages/ppsspp_pl.ts new file mode 100644 index 0000000000..c2d398d637 --- /dev/null +++ b/Qt/languages/ppsspp_pl.ts @@ -0,0 +1,634 @@ + + + + + Controls + + + Controls + + + + + Debugger_Disasm + + + Dialog + + + + + Ctr: + + + + + &Go to + + + + + &PC + + + + + &LR + + + + + Tab 1 + + + + + Tab 2 + + + + + &Go + + + + + Stop + + + + + Step &Into + + + + + Step &Over + + + + + S&kip + + + + + Next &HLE + + + + + GamePadDialog + + + Gamepad Configuration + Konfiguracja kontrolera + + + + GamePad List + Lista kontrolerów + + + + Refresh + Odśwież + + + + Select + Wybierz + + + + Gamepad Values : + Wartości przycisków/osi: + + + + TextLabel + + + + + Assign Gamepad input + Przypisz przycisk + + + + to PSP button/axis + do przycisku/osi PSP + + + + Assign + Przypisz + + + + Press buttons on your gamePad to verify mapping : + Naciśnij przyciski na kontrolerze: + + + + + <b>No gamepad</b> + <b>Nie wykryto pada</b> + + + + <b>Unknown gamepad</b> + <b>Nieznany pad</b> + + + + Buttons + Przyciski + + + + + Button %1 + Przycisk %1 + + + + Axes + Osie + + + + %1 Neg + %1 zanegowany + + + + Axes %1 Neg + Oś %1 zanegowana + + + + %1 Pos + %1 pozycja + + + + Axes %1 Pos + Pozycja osi %1 + + + + Hats + + + + + <b>Current gamepad: %1</b> + <b>Wybrany pad: %1</b> + + + + MainWindow + + + PPSSPP + PPSSPP + + + + &File + &Plik + + + + &Emulation + &Emulacja + + + + Debu&g + &Debugger + + + + &Options + &Opcje + + + + &Log Levels + P&oziomy logowania + + + + G3D + G3D + + + + HLE + HLE + + + + Default + Domyślne + + + + Zoom + Zoom + + + + Language + Język + + + + &Help + Pomo&c + + + + &Open... + &Otwórz... + + + + &Close + &Zamknij + + + + - + + + + + Quickload state + Wczytaj stan + + + + F4 + F4 + + + + Quicksave state + Zapisz stan + + + + F2 + F2 + + + + &Load State File... + &Wczytaj plik stanu... + + + + &Save State File... + &Zapisz plik stanu... + + + + E&xit + Wyj&dź + + + + &Run + &Uruchom + + + + F7 + F7 + + + + &Pause + &Pauza + + + + F8 + F8 + + + + R&eset + &Reset + + + + &Interpreter + &Interpreter + + + + &Slightly Faster Interpreter + &Szybszy interpreter + + + + &Dynarec + R&ekompilacja (Dynarec) + + + + Load &Map File... + &Wczytaj plik mapy... + + + + &Save Map File... + &Zapisz plik mapy... + + + + &Reset Symbol Table + Zresetuj &tablicę symboli + + + + &Disassembly + &Dekompiluj + + + + Ctrl+D + Ctrl+D + + + + &Log Console + &Konsola logowania + + + + Ctrl+L + Ctrl+L + + + + Memory &View... + Widok &pamięci... + + + + Ctrl+M + Ctrl+M + + + + Keyboard &Controls + Ustawienia &klawiatury + + + + &Toggle Full Screen + &Pełny ekran + + + + F12 + F12 + + + + &Buffered Rendering + &Buffered rendering + + + + F5 + F5 + + + + &Hardware Transform + &Hardware Transform + + + + F6 + F6 + + + + &Linear Filtering + &Linear Filtering + + + + &Wireframe (experimental) + + + + + &Display Raw Framebuffer + + + + + &Show Debug Statistics + Pokaż &statystyki emulacji + + + + Screen &1x + &1x + + + + Ctrl+1 + Ctrl+1 + + + + Screen &2x + &2x + + + + Ctrl+2 + Ctrl+2 + + + + Screen &3x + &3x + + + + Ctrl+3 + Ctrl+3 + + + + Screen &4x + &4x + + + + Ctrl+4 + Ctrl+4 + + + + &Fast Memory (dynarec, unstable) + &Fast memory (wymagany Dynarec, niestabilne) + + + + &Ignore illegal reads/writes + &Ignoruj nieprawidłowe odczyty/zapisy + + + + &Go to http://www.ppsspp.org/ + &Idź do http://www.ppsspp.org + + + + &About PPSSPP... + &O PPSSPP... + + + + &Use VBO + Użyj &VBO + + + + + + Debug + Debug + + + + + + Warning + Ostrzeżenia + + + + + + Error + Błędy + + + + + + Info + Info + + + + GamePad Controls + &Ustawienia pada + + + + No translations + + + + + gamepadMapping + + + Cross + Krzyżyk + + + + Circle + Kółko + + + + Square + Kwadrat + + + + Triangle + Trójkąt + + + + Left Trigger + Lewy trigger + + + + Right Trigger + Prawy trigger + + + + Start + Start + + + + Select + Select + + + + Up + Góra + + + + Down + Dół + + + + Left + Lewo + + + + Right + Prawo + + + + Home + Klawisz Home + + + + Stick left + Lewo (analog) + + + + Stick right + Prawo (analog) + + + + Stick up + Góra (analog) + + + + Stick bottom + Dół (analog) + + + diff --git a/Qt/mainwindow.cpp b/Qt/mainwindow.cpp index 7b05624249..125043c00b 100644 --- a/Qt/mainwindow.cpp +++ b/Qt/mainwindow.cpp @@ -48,6 +48,7 @@ MainWindow::MainWindow(QWidget *parent) : DialogManager::AddDlg(vfpudlg = new CVFPUDlg(_hInstance, hwndMain, currentDebugMIPS)); */ // Update(); + createLanguageMenu(); UpdateMenus(); int zoom = g_Config.iWindowZoom; @@ -779,3 +780,102 @@ void MainWindow::on_action_OptionsGamePadControls_triggered() QMessageBox::information(this,"Gamepad","You need to compile with SDL to have Gamepad support.", QMessageBox::Ok); #endif } + +void MainWindow::on_language_changed(QAction *action) +{ + if (0 != action) + { + loadLanguage(action->data().toString()); + } +} + +void switchTranslator(QTranslator &translator, const QString &filename) +{ + qApp->removeTranslator(&translator); + + if (translator.load(filename)) + qApp->installTranslator(&translator); +} + +void MainWindow::loadLanguage(const QString& language) +{ + if (currentLanguage != language) + { + currentLanguage = language; + QLocale locale = QLocale(currentLanguage); + QLocale::setDefault(locale); + QString languageName = QLocale::languageToString(locale.language()); + switchTranslator(translator, QString("languages/ppsspp_%1.qm").arg(language)); + } +} + +void MainWindow::createLanguageMenu() +{ + QActionGroup *langGroup = new QActionGroup(ui->menuLanguage); + langGroup->setExclusive(true); + + connect(langGroup, SIGNAL(triggered(QAction *)), this, SLOT(on_language_changed(QAction *))); + + QString defaultLocale = QLocale::system().name(); + defaultLocale.truncate(defaultLocale.lastIndexOf('_')); + languagePath = QApplication::applicationDirPath(); + languagePath.append("/languages"); + QDir langDir(languagePath); + QStringList fileNames = langDir.entryList(QStringList("ppsspp_*.qm")); + + if (fileNames.size() == 0) + { + QAction *action = new QAction(tr("No translations"), this); + action->setCheckable(false); + action->setDisabled(true); + ui->menuLanguage->addAction(action); + langGroup->addAction(action); + } + + for (int i = 0; i < fileNames.size(); ++i) + { + QString locale = fileNames[i]; + locale.truncate(locale.lastIndexOf('.')); + locale.remove(0, locale.indexOf('_') + 1); + + //QString language = QLocale::languageToString(QLocale(locale).language()); + QString language = QLocale(locale).nativeLanguageName(); + QAction *action = new QAction(language, this); + action->setCheckable(true); + action->setData(locale); + + ui->menuLanguage->addAction(action); + langGroup->addAction(action); + + // TODO check en as default until we save language to config + if ("en" == locale) + { + action->setChecked(true); + currentLanguage = "en"; + } + } +} + +void MainWindow::changeEvent(QEvent *event) +{ + QMainWindow::changeEvent(event); + + if (0 != event) + { + switch (event->type()) + { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + case QEvent::LocaleChange: + { + QString locale = QLocale::system().name(); + locale.truncate(locale.lastIndexOf('_')); + loadLanguage(locale); + } + break; + default: + break; + } + } +} diff --git a/Qt/mainwindow.h b/Qt/mainwindow.h index d8a41a0e99..dbb27e3323 100644 --- a/Qt/mainwindow.h +++ b/Qt/mainwindow.h @@ -2,6 +2,7 @@ #define MAINWINDOW_H #include +#include #include "Core/Core.h" #include "input/input_state.h" @@ -135,7 +136,18 @@ private slots: void on_action_OptionsGamePadControls_triggered(); + void on_language_changed(QAction *action); + private: + void loadLanguage(const QString &language); + void createLanguageMenu(); + void changeEvent(QEvent *); + + QTranslator translator; + QTranslator qtTranslator; + QString currentLanguage; + QString languagePath; + Ui::MainWindow *ui; QtEmuGL* w; diff --git a/Qt/mainwindow.ui b/Qt/mainwindow.ui index 1a563fe407..acf08f6a94 100644 --- a/Qt/mainwindow.ui +++ b/Qt/mainwindow.ui @@ -44,7 +44,7 @@ 0 0 800 - 23 + 21 @@ -135,6 +135,11 @@ + + + Language + + @@ -155,6 +160,7 @@ + diff --git a/Windows/main.cpp b/Windows/main.cpp index c875f0c7b2..b568189d07 100644 --- a/Windows/main.cpp +++ b/Windows/main.cpp @@ -191,6 +191,8 @@ int WINAPI WinMain(HINSTANCE _hInstance, HINSTANCE hPrevInstance, LPSTR szCmdLin } } + VFSShutdown(); + LogManager::Shutdown(); DialogManager::DestroyAll(); timeEndPeriod(1); diff --git a/headless/Compare.cpp b/headless/Compare.cpp new file mode 100644 index 0000000000..02252d9833 --- /dev/null +++ b/headless/Compare.cpp @@ -0,0 +1,77 @@ +// Copyright (c) 2012- 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 "Compare.h" +#include "FileUtil.h" + +#include + +bool CompareOutput(const std::string bootFilename) +{ + std::string expect_filename = bootFilename.substr(bootFilename.length() - 4) + ".expected"; + if (File::Exists(expect_filename)) + { + // TODO: Do the compare here + return true; + } + else + { + fprintf(stderr, "Expectation file %s not found", expect_filename.c_str()); + return false; + } +} + +inline int ComparePixel(u32 pix1, u32 pix2) +{ + // For now, if they're different at all except alpha, it's an error. + if ((pix1 & 0xFFFFFF) != (pix2 & 0xFFFFFF)) + return 1; + return 0; +} + +double CompareScreenshot(const u8 *pixels, int w, int h, int stride, const std::string screenshotFilename, std::string &error) +{ + u32 *pixels32 = (u32 *) pixels; + // We assume the bitmap is the specified size, not including whatever stride. + u32 *reference = (u32 *) calloc(w * h, sizeof(u32)); + + FILE *bmp = fopen(screenshotFilename.c_str(), "rb"); + if (bmp) + { + // The bitmap header is 14 + 40 bytes. We could validate it but the test would fail either way. + fseek(bmp, 14 + 40, SEEK_SET); + fread(reference, sizeof(u32), w * h, bmp); + fclose(bmp); + } + else + { + error = "Unable to read screenshot: " + screenshotFilename; + free(reference); + return -1.0f; + } + + u32 errors = 0; + for (int y = 0; y < h; ++y) + { + for (int x = 0; x < w; ++x) + errors += ComparePixel(pixels32[y * stride + x], reference[y * w + x]); + } + + free(reference); + + return (double) errors / (double) (w * h); +} \ No newline at end of file diff --git a/headless/Compare.h b/headless/Compare.h new file mode 100644 index 0000000000..f51f6c1be2 --- /dev/null +++ b/headless/Compare.h @@ -0,0 +1,23 @@ +// Copyright (c) 2012- 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 "Globals.h" + +bool CompareOutput(std::string bootFilename); +double CompareScreenshot(const u8 *pixels, int w, int h, int stride, const std::string screenshotFilename, std::string &error); \ No newline at end of file diff --git a/headless/Headless.cpp b/headless/Headless.cpp index b01338982e..3cc16ae7f9 100644 --- a/headless/Headless.cpp +++ b/headless/Headless.cpp @@ -13,6 +13,7 @@ #include "Log.h" #include "LogManager.h" +#include "Compare.h" #include "StubHost.h" #ifdef _WIN32 #include "WindowsHeadlessHost.h" @@ -59,10 +60,14 @@ void printUsage(const char *progname, const char *reason) HEADLESSHOST_CLASS h1; HeadlessHost h2; if (typeid(h1) != typeid(h2)) + { fprintf(stderr, " --graphics use the full gpu backend (slower)\n"); + fprintf(stderr, " --screenshot=FILE compare against a screenshot\n"); + } + fprintf(stderr, " -i use the interpreter\n"); fprintf(stderr, " -f use the fast interpreter\n"); - fprintf(stderr, " -j use jit (overrides -f)\n"); + fprintf(stderr, " -j use jit (default)\n"); fprintf(stderr, " -c, --compare compare with output in file.expected\n"); fprintf(stderr, "\nSee headless.txt for details.\n"); } @@ -77,6 +82,7 @@ int main(int argc, const char* argv[]) const char *bootFilename = 0; const char *mountIso = 0; + const char *screenshotFilename = 0; bool readMount = false; for (int i = 1; i < argc; i++) @@ -91,6 +97,8 @@ int main(int argc, const char* argv[]) readMount = true; else if (!strcmp(argv[i], "-l") || !strcmp(argv[i], "--log")) fullLog = true; + else if (!strcmp(argv[i], "-i")) + useJit = false; else if (!strcmp(argv[i], "-j")) useJit = true; else if (!strcmp(argv[i], "-f")) @@ -99,6 +107,8 @@ int main(int argc, const char* argv[]) autoCompare = true; else if (!strcmp(argv[i], "--graphics")) useGraphics = true; + else if (!strncmp(argv[i], "--screenshot=", strlen("--screenshot=")) && strlen(argv[i]) > strlen("--screenshot=")) + screenshotFilename = argv[i] + strlen("--screenshot="); else if (bootFilename == 0) bootFilename = argv[i]; else @@ -146,7 +156,7 @@ int main(int argc, const char* argv[]) coreParameter.fileToStart = bootFilename; coreParameter.mountIso = mountIso ? mountIso : ""; coreParameter.startPaused = false; - coreParameter.cpuCore = useJit ? CPU_JIT : (fastInterpreter ? CPU_FASTINTERPRETER : CPU_INTERPRETER); + coreParameter.cpuCore = fastInterpreter ? CPU_FASTINTERPRETER : (useJit ? CPU_JIT : CPU_INTERPRETER); coreParameter.gpuCore = headlessHost->isGLWorking() ? GPU_GLES : GPU_NULL; coreParameter.enableSound = false; coreParameter.headLess = true; @@ -174,6 +184,9 @@ int main(int argc, const char* argv[]) host->BootDone(); + if (screenshotFilename != 0) + headlessHost->SetComparisonScreenshot(screenshotFilename); + coreState = CORE_RUNNING; while (coreState == CORE_RUNNING) { @@ -195,17 +208,7 @@ int main(int argc, const char* argv[]) headlessHost = NULL; if (autoCompare) - { - std::string expect_filename = std::string(bootFilename).substr(strlen(bootFilename - 4)) + ".expected"; - if (File::Exists(expect_filename)) - { - // TODO: Do the compare here - } - else - { - fprintf(stderr, "Expectation file %s not found", expect_filename.c_str()); - } - } + CompareOutput(bootFilename); return 0; } diff --git a/headless/Headless.vcxproj b/headless/Headless.vcxproj index bc5cb35624..ecfadbd1ab 100644 --- a/headless/Headless.vcxproj +++ b/headless/Headless.vcxproj @@ -145,6 +145,7 @@ + NotUsing NotUsing @@ -177,6 +178,7 @@ + diff --git a/headless/Headless.vcxproj.filters b/headless/Headless.vcxproj.filters index 5982bf35db..e0debd7e7b 100644 --- a/headless/Headless.vcxproj.filters +++ b/headless/Headless.vcxproj.filters @@ -4,6 +4,7 @@ + @@ -11,5 +12,6 @@ + \ No newline at end of file diff --git a/headless/StubHost.h b/headless/StubHost.h index 9445aee06c..1533c381c3 100644 --- a/headless/StubHost.h +++ b/headless/StubHost.h @@ -50,6 +50,7 @@ public: virtual bool AttemptLoadSymbolMap() {return false;} virtual void SendDebugOutput(const std::string &output) { printf("%s", output.c_str()); } + virtual void SetComparisonScreenshot(const std::string &filename) {} virtual bool isGLWorking() { return false; } }; \ No newline at end of file diff --git a/headless/WindowsHeadlessHost.cpp b/headless/WindowsHeadlessHost.cpp index 606235c26a..5e7448ae23 100644 --- a/headless/WindowsHeadlessHost.cpp +++ b/headless/WindowsHeadlessHost.cpp @@ -16,6 +16,7 @@ // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/. #include "WindowsHeadlessHost.h" +#include "Compare.h" #include #include @@ -89,6 +90,54 @@ void WindowsHeadlessHost::SendDebugOutput(const std::string &output) OutputDebugString(output.c_str()); } +void WindowsHeadlessHost::SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h) +{ + // We ignore the current framebuffer parameters and just grab the full screen. + const static int FRAME_WIDTH = 512; + const static int FRAME_HEIGHT = 272; + u8 *pixels = new u8[FRAME_WIDTH * FRAME_HEIGHT * 4]; + + // TODO: Maybe this code should be moved into GLES_GPU. + glReadBuffer(GL_FRONT); + glReadPixels(0, 0, FRAME_WIDTH, FRAME_HEIGHT, GL_BGRA, GL_UNSIGNED_BYTE, pixels); + + std::string error; + double errors = CompareScreenshot(pixels, FRAME_WIDTH, FRAME_HEIGHT, FRAME_WIDTH, comparisonScreenshot, error); + if (errors < 0) + fprintf_s(out, "%s\n", error.c_str()); + + if (errors > 0) + { + fprintf_s(out, "Screenshot error: %f%%\n", errors * 100.0f); + + // Lazy, just read in the original header to output the failed screenshot. + u8 header[14 + 40] = {0}; + FILE *bmp = fopen(comparisonScreenshot.c_str(), "rb"); + if (bmp) + { + fread(&header, sizeof(header), 1, bmp); + fclose(bmp); + } + + FILE *saved = fopen("__testfailure.bmp", "wb"); + if (saved) + { + fwrite(&header, sizeof(header), 1, saved); + fwrite(pixels, sizeof(u32), FRAME_WIDTH * FRAME_HEIGHT, saved); + fclose(saved); + + fprintf_s(out, "Actual output written to: __testfailure.bmp\n"); + } + } + + delete [] pixels; +} + +void WindowsHeadlessHost::SetComparisonScreenshot(const std::string &filename) +{ + comparisonScreenshot = filename; +} + void WindowsHeadlessHost::InitGL() { glOkay = false; diff --git a/headless/WindowsHeadlessHost.h b/headless/WindowsHeadlessHost.h index 28e51ee543..5abc20ca05 100644 --- a/headless/WindowsHeadlessHost.h +++ b/headless/WindowsHeadlessHost.h @@ -35,6 +35,8 @@ public: virtual bool isGLWorking() { return glOkay; } virtual void SendDebugOutput(const std::string &output); + virtual void SendDebugScreenshot(const u8 *pixbuf, u32 w, u32 h); + virtual void SetComparisonScreenshot(const std::string &filename); private: bool ResizeGL(); @@ -45,4 +47,5 @@ private: HDC hDC; HGLRC hRC; FILE *out; + std::string comparisonScreenshot; }; \ No newline at end of file diff --git a/native b/native index f22ad17d40..3caced8524 160000 --- a/native +++ b/native @@ -1 +1 @@ -Subproject commit f22ad17d40c00d9a60bd21f53820012b302d7559 +Subproject commit 3caced8524c06cabcf968942a7780d80337de7bf diff --git a/pspautotests b/pspautotests index 4f047eb8c7..c4427bd55d 160000 --- a/pspautotests +++ b/pspautotests @@ -1 +1 @@ -Subproject commit 4f047eb8c76c6388a63a87ec72e98c64670152d5 +Subproject commit c4427bd55d57af2484f5ccd4ec1bed2bcf674395 diff --git a/test.py b/test.py index 52b2b906b1..2d98e03079 100755 --- a/test.py +++ b/test.py @@ -109,6 +109,12 @@ tests_good = [ "threads/semaphores/refer/refer", "threads/semaphores/signal/signal", "threads/semaphores/wait/wait", + "threads/vpl/vpl", + "threads/vpl/delete", + "threads/vpl/free", + "threads/vpl/priority", + "threads/vpl/refer", + "threads/vpl/try", "power/power", "umd/callbacks/umd", "umd/wait/wait", @@ -124,11 +130,13 @@ tests_next = [ "threads/msgpipe/msgpipe", "threads/scheduling/scheduling", "threads/threads/threads", - "threads/vpl/vpl", "threads/vtimers/vtimer", + "threads/vpl/allocate", + "threads/vpl/create", "threads/wakeup/wakeup", "gpu/simple/simple", "gpu/triangle/triangle", + "gpu/commands/basic", "hle/check_not_used_uids", "font/fonttest", "io/cwd/cwd", @@ -222,6 +230,8 @@ def run_tests(test_list, args): cmdline = [PPSSPP_EXE, elf_filename] cmdline.extend([i for i in args if i not in ['-v', '-g']]) + if os.path.exists(expected_filename + ".bmp"): + cmdline.extend(["--screenshot=" + expected_filename + ".bmp", "--graphics"]) c = Command(cmdline) c.run(TIMEOUT) @@ -231,6 +241,7 @@ def run_tests(test_list, args): if c.timeout: print(output) print("Test exceded limit of %d seconds." % TIMEOUT) + tests_failed.append(test) tcprint("##teamcity[testFailed name='%s' message='Test timeout']" % test) tcprint("##teamcity[testFinished name='%s']" % test) continue