// 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 #include "Common/ChunkFile.h" #include "Core/HLE/HLE.h" #include "Core/HLE/FunctionWrappers.h" #include "Core/MIPS/MIPS.h" #include "Core/System.h" #include "Core/CoreParameter.h" #include "Core/CoreTiming.h" #include "Core/Reporting.h" #include "Core/HLE/sceGe.h" #include "Core/HLE/sceKernelMemory.h" #include "Core/HLE/sceKernelThread.h" #include "Core/HLE/sceKernelInterrupt.h" #include "Core/HLE/KernelWaitHelpers.h" #include "GPU/GPUState.h" #include "GPU/GPUInterface.h" static PspGeCallbackData ge_callback_data[16]; static bool ge_used_callbacks[16] = {0}; typedef std::vector WaitingThreadList; static std::map listWaitingThreads; static WaitingThreadList drawWaitingThreads; struct GeInterruptData { int listid; u32 pc; }; static std::list ge_pending_cb; static int geSyncEvent; static int geInterruptEvent; static int geCycleEvent; // Let's try updating 10 times per vblank. const int geIntervalUs = 1000000 / (60 * 10); const int geBehindThresholdUs = 1000000 / (60 * 10); class GeIntrHandler : public IntrHandler { public: GeIntrHandler() : IntrHandler(PSP_GE_INTR) {} bool run(PendingInterrupt& pend) { GeInterruptData intrdata = ge_pending_cb.front(); DisplayList* dl = gpu->getList(intrdata.listid); if (dl == NULL) { WARN_LOG(SCEGE, "Unable to run GE interrupt: list doesn't exist: %d", intrdata.listid); return false; } if (!dl->interruptsEnabled) { ERROR_LOG_REPORT(SCEGE, "Unable to run GE interrupt: list has interrupts disabled, should not happen"); return false; } gpu->InterruptStart(intrdata.listid); u32 cmd = Memory::ReadUnchecked_U32(intrdata.pc - 4) >> 24; int subintr = -1; if (dl->subIntrBase >= 0) { switch (dl->signal) { case PSP_GE_SIGNAL_SYNC: case PSP_GE_SIGNAL_JUMP: case PSP_GE_SIGNAL_CALL: case PSP_GE_SIGNAL_RET: // Do nothing. break; case PSP_GE_SIGNAL_HANDLER_PAUSE: if (cmd == GE_CMD_FINISH) subintr = dl->subIntrBase | PSP_GE_SUBINTR_SIGNAL; break; default: if (cmd == GE_CMD_SIGNAL) subintr = dl->subIntrBase | PSP_GE_SUBINTR_SIGNAL; else subintr = dl->subIntrBase | PSP_GE_SUBINTR_FINISH; break; } } SubIntrHandler* handler = get(subintr); if (handler != NULL) { DEBUG_LOG(CPU, "Entering interrupt handler %08x", handler->handlerAddress); currentMIPS->pc = handler->handlerAddress; u32 data = dl->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; // RA is already taken care of in __RunOnePendingInterrupt return true; } ge_pending_cb.pop_front(); gpu->InterruptEnd(intrdata.listid); // Seen in GoW. if (subintr >= 0) DEBUG_LOG(SCEGE, "Ignoring interrupt for display list %d, already been released.", intrdata.listid); return false; } virtual void handleResult(PendingInterrupt& pend) { GeInterruptData intrdata = ge_pending_cb.front(); ge_pending_cb.pop_front(); DisplayList* dl = gpu->getList(intrdata.listid); if (!dl->interruptsEnabled) { ERROR_LOG_REPORT(SCEGE, "Unable to finish GE interrupt: list has interrupts disabled, should not happen"); return; } switch (dl->signal) { case PSP_GE_SIGNAL_HANDLER_SUSPEND: if (sceKernelGetCompiledSdkVersion() <= 0x02000010) { // uofw says dl->state = endCmd & 0xFF; DisplayListState newState = static_cast(Memory::ReadUnchecked_U32(intrdata.pc - 4) & 0xFF); //dl->status = static_cast(Memory::ReadUnchecked_U32(intrdata.pc) & 0xFF); //if(dl->status < 0 || dl->status > PSP_GE_LIST_PAUSED) // ERROR_LOG(SCEGE, "Weird DL status after signal suspend %x", dl->status); if (newState != PSP_GE_DL_STATE_RUNNING) DEBUG_LOG_REPORT(SCEGE, "GE Interrupt: newState might be %d", newState); dl->state = PSP_GE_DL_STATE_RUNNING; } break; default: break; } dl->signal = PSP_GE_SIGNAL_NONE; gpu->InterruptEnd(intrdata.listid); } }; void __GeExecuteSync(u64 userdata, int cyclesLate) { int listid = userdata >> 32; WaitType waitType = (WaitType) (userdata & 0xFFFFFFFF); bool wokeThreads = __GeTriggerWait(waitType, listid); gpu->SyncEnd(waitType == WAITTYPE_GELISTSYNC ? GPU_SYNC_LIST : GPU_SYNC_DRAW, listid, wokeThreads); } void __GeExecuteInterrupt(u64 userdata, int cyclesLate) { int listid = userdata >> 32; u32 pc = userdata & 0xFFFFFFFF; GeInterruptData intrdata; intrdata.listid = listid; intrdata.pc = pc; ge_pending_cb.push_back(intrdata); __TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_GE_INTR, PSP_INTR_SUB_NONE); } void __GeCheckCycles(u64 userdata, int cyclesLate) { u64 geTicks = gpu->GetTickEstimate(); if (geTicks != 0) { if (CoreTiming::GetTicks() > geTicks + usToCycles(geBehindThresholdUs)) { u64 diff = CoreTiming::GetTicks() - geTicks; gpu->SyncThread(); CoreTiming::Advance(); } } CoreTiming::ScheduleEvent(usToCycles(geIntervalUs), geCycleEvent, 0); } void __GeInit() { memset(&ge_used_callbacks, 0, sizeof(ge_used_callbacks)); ge_pending_cb.clear(); __RegisterIntrHandler(PSP_GE_INTR, new GeIntrHandler()); geSyncEvent = CoreTiming::RegisterEvent("GeSyncEvent", &__GeExecuteSync); geInterruptEvent = CoreTiming::RegisterEvent("GeInterruptEvent", &__GeExecuteInterrupt); geCycleEvent = CoreTiming::RegisterEvent("GeCycleEvent", &__GeCheckCycles); listWaitingThreads.clear(); drawWaitingThreads.clear(); // When we're using separate CPU/GPU threads, we need to keep them in sync. if (IsOnSeparateCPUThread()) { CoreTiming::ScheduleEvent(usToCycles(geIntervalUs), geCycleEvent, 0); } } void __GeDoState(PointerWrap &p) { auto s = p.Section("sceGe", 1); if (!s) return; p.DoArray(ge_callback_data, ARRAY_SIZE(ge_callback_data)); p.DoArray(ge_used_callbacks, ARRAY_SIZE(ge_used_callbacks)); p.Do(ge_pending_cb); p.Do(geSyncEvent); CoreTiming::RestoreRegisterEvent(geSyncEvent, "GeSyncEvent", &__GeExecuteSync); p.Do(geInterruptEvent); CoreTiming::RestoreRegisterEvent(geInterruptEvent, "GeInterruptEvent", &__GeExecuteInterrupt); p.Do(geCycleEvent); CoreTiming::RestoreRegisterEvent(geCycleEvent, "GeCycleEvent", &__GeCheckCycles); p.Do(listWaitingThreads); p.Do(drawWaitingThreads); // Everything else is done in sceDisplay. } void __GeShutdown() { } // Warning: may be called from the GPU thread. bool __GeTriggerSync(WaitType waitType, int id, u64 atTicks) { u64 userdata = (u64)id << 32 | (u64) waitType; s64 future = atTicks - CoreTiming::GetTicks(); if (waitType == WAITTYPE_GEDRAWSYNC) { s64 left = CoreTiming::UnscheduleThreadsafeEvent(geSyncEvent, userdata); if (left > future) future = left; } CoreTiming::ScheduleEvent_Threadsafe(future, geSyncEvent, userdata); return true; } // Warning: may be called from the GPU thread. bool __GeTriggerInterrupt(int listid, u32 pc, u64 atTicks) { u64 userdata = (u64)listid << 32 | (u64) pc; CoreTiming::ScheduleEvent_Threadsafe(atTicks - CoreTiming::GetTicks(), geInterruptEvent, userdata); return true; } void __GeWaitCurrentThread(WaitType type, SceUID waitId, const char *reason) { if (type == WAITTYPE_GEDRAWSYNC) drawWaitingThreads.push_back(__KernelGetCurThread()); else if (type == WAITTYPE_GELISTSYNC) listWaitingThreads[waitId].push_back(__KernelGetCurThread()); else ERROR_LOG_REPORT(SCEGE, "__GeWaitCurrentThread: bad wait type"); __KernelWaitCurThread(type, waitId, 0, 0, false, reason); } bool __GeTriggerWait(WaitType type, SceUID waitId, WaitingThreadList &waitingThreads) { // TODO: Do they ever get a result other than 0? bool wokeThreads = false; for (auto it = waitingThreads.begin(), end = waitingThreads.end(); it != end; ++it) wokeThreads |= HLEKernel::ResumeFromWait(*it, type, waitId, 0); waitingThreads.clear(); return wokeThreads; } bool __GeTriggerWait(WaitType type, SceUID waitId) { if (type == WAITTYPE_GEDRAWSYNC) return __GeTriggerWait(type, waitId, drawWaitingThreads); else if (type == WAITTYPE_GELISTSYNC) return __GeTriggerWait(type, waitId, listWaitingThreads[waitId]); else ERROR_LOG_REPORT(SCEGE, "__GeTriggerWait: bad wait type"); return false; } bool __GeHasPendingInterrupt() { return !ge_pending_cb.empty(); } u32 sceGeEdramGetAddr() { u32 retVal = 0x04000000; DEBUG_LOG(SCEGE, "%08x = sceGeEdramGetAddr", retVal); hleEatCycles(150); return retVal; } u32 sceGeEdramGetSize() { u32 retVal = 0x00200000; DEBUG_LOG(SCEGE, "%08x = sceGeEdramGetSize()", retVal); return retVal; } int __GeSubIntrBase(int callbackId) { return callbackId * 2; } u32 sceGeListEnQueue(u32 listAddress, u32 stallAddress, int callbackId, u32 optParamAddr) { DEBUG_LOG(SCEGE, "sceGeListEnQueue(addr=%08x, stall=%08x, cbid=%08x, param=%08x)", listAddress, stallAddress, callbackId, optParamAddr); auto optParam = PSPPointer::Create(optParamAddr); u32 listID = gpu->EnqueueList(listAddress, stallAddress, __GeSubIntrBase(callbackId), optParam, false); if ((int)listID >= 0) listID = 0x35000000 ^ listID; DEBUG_LOG(SCEGE, "List %i enqueued.", listID); hleEatCycles(490); CoreTiming::ForceCheck(); return listID; } u32 sceGeListEnQueueHead(u32 listAddress, u32 stallAddress, int callbackId, u32 optParamAddr) { DEBUG_LOG(SCEGE, "sceGeListEnQueueHead(addr=%08x, stall=%08x, cbid=%08x, param=%08x)", listAddress, stallAddress, callbackId, optParamAddr); auto optParam = PSPPointer::Create(optParamAddr); u32 listID = gpu->EnqueueList(listAddress, stallAddress, __GeSubIntrBase(callbackId), optParam, true); if ((int)listID >= 0) listID = 0x35000000 ^ listID; DEBUG_LOG(SCEGE, "List %i enqueued.", listID); hleEatCycles(480); CoreTiming::ForceCheck(); return listID; } int sceGeListDeQueue(u32 listID) { WARN_LOG(SCEGE, "sceGeListDeQueue(%08x)", listID); int result = gpu->DequeueList(0x35000000 ^ listID); hleReSchedule("dlist dequeued"); return result; } int sceGeListUpdateStallAddr(u32 displayListID, u32 stallAddress) { // Advance() might cause an interrupt, so defer the Advance but do it ASAP. // Final Fantasy Type-0 has a graphical artifact without this (timing issue.) hleEatCycles(190); CoreTiming::ForceCheck(); DEBUG_LOG(SCEGE, "sceGeListUpdateStallAddr(dlid=%i, stalladdr=%08x)", displayListID, stallAddress); return gpu->UpdateStall(0x35000000 ^ displayListID, stallAddress); } int sceGeListSync(u32 displayListID, u32 mode) //0 : wait for completion 1:check and return { DEBUG_LOG(SCEGE, "sceGeListSync(dlid=%08x, mode=%08x)", displayListID, mode); return gpu->ListSync(0x35000000 ^ displayListID, mode); } u32 sceGeDrawSync(u32 mode) { //wait/check entire drawing state DEBUG_LOG(SCEGE, "sceGeDrawSync(mode=%d) (0=wait for completion, 1=peek)", mode); return gpu->DrawSync(mode); } int sceGeContinue() { DEBUG_LOG(SCEGE, "sceGeContinue"); return gpu->Continue(); } int sceGeBreak(u32 mode, u32 unknownPtr) { if (mode > 1) { WARN_LOG(SCEGE, "sceGeBreak(mode=%d, unknown=%08x): invalid mode", mode, unknownPtr); return SCE_KERNEL_ERROR_INVALID_MODE; } // Not sure what this is supposed to be for... if ((int)unknownPtr < 0 || (int)unknownPtr + 16 < 0) { WARN_LOG_REPORT(SCEGE, "sceGeBreak(mode=%d, unknown=%08x): invalid ptr", mode, unknownPtr); return 0x80000023; } else if (unknownPtr != 0) WARN_LOG_REPORT(SCEGE, "sceGeBreak(mode=%d, unknown=%08x): unknown ptr (%s)", mode, unknownPtr, Memory::IsValidAddress(unknownPtr) ? "valid" : "invalid"); //mode => 0 : current dlist 1: all drawing DEBUG_LOG(SCEGE, "sceGeBreak(mode=%d, unknown=%08x)", mode, unknownPtr); int result = gpu->Break(mode); if (result >= 0 && mode == 0) return 0x35000000 ^ result; return result; } u32 sceGeSetCallback(u32 structAddr) { DEBUG_LOG(SCEGE, "sceGeSetCallback(struct=%08x)", structAddr); int cbID = -1; for (size_t i = 0; i < ARRAY_SIZE(ge_used_callbacks); ++i) if (!ge_used_callbacks[i]) { cbID = (int) i; break; } if (cbID == -1) { WARN_LOG(SCEGE, "sceGeSetCallback(): out of callback ids"); return SCE_KERNEL_ERROR_OUT_OF_MEMORY; } ge_used_callbacks[cbID] = true; Memory::ReadStruct(structAddr, &ge_callback_data[cbID]); int subIntrBase = __GeSubIntrBase(cbID); if (ge_callback_data[cbID].finish_func != 0) { sceKernelRegisterSubIntrHandler(PSP_GE_INTR, subIntrBase | PSP_GE_SUBINTR_FINISH, ge_callback_data[cbID].finish_func, ge_callback_data[cbID].finish_arg); sceKernelEnableSubIntr(PSP_GE_INTR, subIntrBase | PSP_GE_SUBINTR_FINISH); } if (ge_callback_data[cbID].signal_func != 0) { sceKernelRegisterSubIntrHandler(PSP_GE_INTR, subIntrBase | PSP_GE_SUBINTR_SIGNAL, ge_callback_data[cbID].signal_func, ge_callback_data[cbID].signal_arg); sceKernelEnableSubIntr(PSP_GE_INTR, subIntrBase | PSP_GE_SUBINTR_SIGNAL); } return cbID; } int sceGeUnsetCallback(u32 cbID) { DEBUG_LOG(SCEGE, "sceGeUnsetCallback(cbid=%08x)", cbID); if (cbID >= ARRAY_SIZE(ge_used_callbacks)) { WARN_LOG(SCEGE, "sceGeUnsetCallback(cbid=%08x): invalid callback id", cbID); return SCE_KERNEL_ERROR_INVALID_ID; } if (ge_used_callbacks[cbID]) { int subIntrBase = __GeSubIntrBase(cbID); sceKernelReleaseSubIntrHandler(PSP_GE_INTR, subIntrBase | PSP_GE_SUBINTR_FINISH); sceKernelReleaseSubIntrHandler(PSP_GE_INTR, subIntrBase | PSP_GE_SUBINTR_SIGNAL); } else WARN_LOG(SCEGE, "sceGeUnsetCallback(cbid=%08x): ignoring unregistered callback id", cbID); ge_used_callbacks[cbID] = false; return 0; } // Points to 512 32-bit words, where we can probably layout the context however we want // unless some insane game pokes it and relies on it... u32 sceGeSaveContext(u32 ctxAddr) { DEBUG_LOG(SCEGE, "sceGeSaveContext(%08x)", ctxAddr); gpu->SyncThread(); if (gpu->BusyDrawing()) { WARN_LOG(SCEGE, "sceGeSaveContext(%08x): lists in process, aborting", ctxAddr); // Real error code. return -1; } // Let's just dump gstate. if (Memory::IsValidAddress(ctxAddr)) { gstate.Save((u32_le *)Memory::GetPointer(ctxAddr)); } // This action should probably be pushed to the end of the queue of the display thread - // when we have one. return 0; } u32 sceGeRestoreContext(u32 ctxAddr) { DEBUG_LOG(SCEGE, "sceGeRestoreContext(%08x)", ctxAddr); gpu->SyncThread(); if (gpu->BusyDrawing()) { WARN_LOG(SCEGE, "sceGeRestoreContext(%08x): lists in process, aborting", ctxAddr); return SCE_KERNEL_ERROR_BUSY; } if (Memory::IsValidAddress(ctxAddr)) { gstate.Restore((u32_le *)Memory::GetPointer(ctxAddr)); } ReapplyGfxState(); return 0; } void __GeCopyMatrix(u32 matrixPtr, float *mtx, u32 size) { for (u32 i = 0; i < size / sizeof(float); ++i) { Memory::Write_U32(toFloat24(mtx[i]), matrixPtr + i * sizeof(float)); } } int sceGeGetMtx(int type, u32 matrixPtr) { if (!Memory::IsValidAddress(matrixPtr)) { ERROR_LOG(SCEGE, "sceGeGetMtx(%d, %08x) - bad matrix ptr", type, matrixPtr); return -1; } INFO_LOG(SCEGE, "sceGeGetMtx(%d, %08x)", type, matrixPtr); switch (type) { case GE_MTX_BONE0: case GE_MTX_BONE1: case GE_MTX_BONE2: case GE_MTX_BONE3: case GE_MTX_BONE4: case GE_MTX_BONE5: case GE_MTX_BONE6: case GE_MTX_BONE7: { int n = type - GE_MTX_BONE0; __GeCopyMatrix(matrixPtr, gstate.boneMatrix + n * 12, 12 * sizeof(float)); } break; case GE_MTX_TEXGEN: __GeCopyMatrix(matrixPtr, gstate.tgenMatrix, 12 * sizeof(float)); break; case GE_MTX_WORLD: __GeCopyMatrix(matrixPtr, gstate.worldMatrix, 12 * sizeof(float)); break; case GE_MTX_VIEW: __GeCopyMatrix(matrixPtr, gstate.viewMatrix, 12 * sizeof(float)); break; case GE_MTX_PROJECTION: __GeCopyMatrix(matrixPtr, gstate.projMatrix, 16 * sizeof(float)); break; default: return SCE_KERNEL_ERROR_INVALID_INDEX; } return 0; } u32 sceGeGetCmd(int cmd) { INFO_LOG(SCEGE, "sceGeGetCmd(%i)", cmd); if (cmd >= 0 && cmd < (int)ARRAY_SIZE(gstate.cmdmem)) { return gstate.cmdmem[cmd]; // Does not mask away the high bits. } else { return SCE_KERNEL_ERROR_INVALID_INDEX; } } int sceGeGetStack(int index, u32 stackPtr) { WARN_LOG_REPORT(SCEGE, "sceGeGetStack(%i, %08x)", index, stackPtr); return gpu->GetStack(index, stackPtr); } u32 sceGeEdramSetAddrTranslation(int new_size) { bool outsideRange = new_size != 0 && (new_size < 0x200 || new_size > 0x1000); bool notPowerOfTwo = (new_size & (new_size - 1)) != 0; if (outsideRange || notPowerOfTwo) { WARN_LOG(SCEGE, "sceGeEdramSetAddrTranslation(%i): invalid value", new_size); return SCE_KERNEL_ERROR_INVALID_VALUE; } DEBUG_LOG(SCEGE, "sceGeEdramSetAddrTranslation(%i)", new_size); static int EDRamWidth = 0x400; int last = EDRamWidth; EDRamWidth = new_size; return last; } const HLEFunction sceGe_user[] = { {0xE47E40E4, WrapU_V, "sceGeEdramGetAddr"}, {0xAB49E76A, WrapU_UUIU, "sceGeListEnQueue"}, {0x1C0D95A6, WrapU_UUIU, "sceGeListEnQueueHead"}, {0xE0D68148, WrapI_UU, "sceGeListUpdateStallAddr"}, {0x03444EB4, WrapI_UU, "sceGeListSync"}, {0xB287BD61, WrapU_U, "sceGeDrawSync"}, {0xB448EC0D, WrapI_UU, "sceGeBreak"}, {0x4C06E472, WrapI_V, "sceGeContinue"}, {0xA4FC06A4, WrapU_U, "sceGeSetCallback"}, {0x05DB22CE, WrapI_U, "sceGeUnsetCallback"}, {0x1F6752AD, WrapU_V, "sceGeEdramGetSize"}, {0xB77905EA, WrapU_I, "sceGeEdramSetAddrTranslation"}, {0xDC93CFEF, WrapU_I, "sceGeGetCmd"}, {0x57C8945B, WrapI_IU, "sceGeGetMtx"}, {0x438A385A, WrapU_U, "sceGeSaveContext"}, {0x0BF608FB, WrapU_U, "sceGeRestoreContext"}, {0x5FB86AB0, WrapI_U, "sceGeListDeQueue"}, {0xE66CB92E, WrapI_IU, "sceGeGetStack"}, }; void Register_sceGe_user() { RegisterModule("sceGe_user", ARRAY_SIZE(sceGe_user), sceGe_user); }