void PSP_RunLoopUntil(u64 globalticks) { SaveState::Process(); if (coreState == CORE_POWERDOWN || coreState == CORE_ERROR) { return; } // Switch the CPU thread on or off, as the case may be. bool useCPUThread = g_Config.bSeparateCPUThread; if (useCPUThread && cpuThread == nullptr) { // Need to start the cpu thread. Core_ListenShutdown(System_Wake); CPU_SetState(CPU_THREAD_RESUME); cpuThread = new std::thread(&CPU_RunLoop); cpuThreadID = cpuThread->get_id(); cpuThread->detach(); if (gpu) { gpu->SetThreadEnabled(true); } CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } else if (!useCPUThread && cpuThread != nullptr) { CPU_SetState(CPU_THREAD_QUIT); CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsShutdown); delete cpuThread; cpuThread = nullptr; cpuThreadID = std::thread::id(); if (gpu) { gpu->SetThreadEnabled(false); } } if (cpuThread != nullptr) { // Tell the gpu a new frame is about to begin, before we start the CPU. gpu->SyncBeginFrame(); cpuThreadUntil = globalticks; if (CPU_NextState(CPU_THREAD_RUNNING, CPU_THREAD_EXECUTE)) { // The CPU doesn't actually respect cpuThreadUntil well, especially when skipping frames. // TODO: Something smarter? Or force CPU to bail periodically? while (!CPU_IsReady()) { gpu->RunEventsUntil(CoreTiming::GetTicks() + msToCycles(1000)); if (coreState != CORE_RUNNING) { CPU_WaitStatus(cpuThreadReplyCond, &CPU_IsReady); } } } else { ERROR_LOG(CPU, "Unable to execute CPU run loop, unexpected state: %d", cpuThreadState); } } else { mipsr4k.RunLoopUntil(globalticks); } gpu->CleanupBeforeUI(); }
void ExtraHID::HandleConfigureHIDPollingRequest(const std::vector<u8>& request) { if (request.size() != 3) { LOG_ERROR(Service_IR, "Wrong request size ({}): {}", request.size(), fmt::format("{:02x}", fmt::join(request, " "))); return; } // Change HID input polling interval Core::System::GetInstance().CoreTiming().UnscheduleEvent(hid_polling_callback_id, 0); hid_period = request[1]; Core::System::GetInstance().CoreTiming().ScheduleEvent(msToCycles(hid_period), hid_polling_callback_id); }
void __CheatInit() { // Always register the event, want savestates to be compatible whether cheats on or off. CheatEvent = CoreTiming::RegisterEvent("CheatEvent", &hleCheat); if (g_Config.bEnableCheats) { __CheatStart(); } int refresh = g_Config.iCwCheatRefreshRate; // Only check once a second for cheats to be enabled. CoreTiming::ScheduleEvent(msToCycles(cheatsEnabled ? refresh : 1000), CheatEvent, 0); }
static void UpdateTimeCallback(u64 userdata, int cycles_late) { DateTime& date_time = shared_page.date_time_counter % 2 ? shared_page.date_time_0 : shared_page.date_time_1; date_time.date_time = GetSystemTime(); date_time.update_tick = CoreTiming::GetTicks(); date_time.tick_to_second_coefficient = g_clock_rate_arm11; date_time.tick_offset = 0; ++shared_page.date_time_counter; // system time is updated hourly CoreTiming::ScheduleEvent(msToCycles(60 * 60 * 1000) - cycles_late, update_time_event); }
void hleCheat(u64 userdata, int cyclesLate) { CoreTiming::ScheduleEvent(msToCycles(77), CheatEvent, 0); if (!cheatEngine) return; if (g_Config.bReloadCheats) { //Checks if the "reload cheats" button has been pressed. cheatEngine->CreateCodeList(); g_Config.bReloadCheats = false; } if (g_Config.bEnableCheats) { cheatEngine->Run(); } }
static int DisplayWaitForVblanks(const char *reason, int vblanks, bool callbacks = false) { const s64 ticksIntoFrame = CoreTiming::GetTicks() - frameStartTicks; const s64 cyclesToNextVblank = msToCycles(frameMs) - ticksIntoFrame; // These syscalls take about 115 us, so if the next vblank is before then, we're waiting extra. // At least, on real firmware a wait >= 16500 into the frame will wait two. if (cyclesToNextVblank <= usToCycles(115)) { ++vblanks; } vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vblanks)); __KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, callbacks, reason); return hleLogSuccessVerboseI(SCEDISPLAY, 0, "waiting for %d vblanks", vblanks); }
void __CheatDoState(PointerWrap &p) { auto s = p.Section("CwCheat", 0, 2); if (!s) { return; } p.Do(CheatEvent); CoreTiming::RestoreRegisterEvent(CheatEvent, "CheatEvent", &hleCheat); if (s < 2) { // Before this we didn't have a checkpoint, so reset didn't work. // Let's just force one in. CoreTiming::RemoveEvent(CheatEvent); CoreTiming::ScheduleEvent(msToCycles(cheatsEnabled ? 77 : 1000), CheatEvent, 0); } }
void __DisplayInit() { gpuStats.Reset(); hasSetMode = false; mode = 0; resumeMode = 0; holdMode = 0; brightnessLevel = 100; width = 480; height = 272; numSkippedFrames = 0; numVBlanksSinceFlip = 0; framebufIsLatched = false; framebuf.topaddr = 0x04000000; framebuf.pspFramebufFormat = GE_FORMAT_8888; framebuf.pspFramebufLinesize = 480; // ?? lastFlipCycles = 0; lastFlipsTooFrequent = 0; wasPaused = false; enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank); leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank); afterFlipEvent = CoreTiming::RegisterEvent("AfterFlip", &hleAfterFlip); lagSyncEvent = CoreTiming::RegisterEvent("LagSync", &hleLagSync); ScheduleLagSync(); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0); isVblank = 0; vCount = 0; hCountBase = 0; curFrameTime = 0.0; nextFrameTime = 0.0; lastFrameTime = 0.0; flips = 0; fps = 0.0; actualFlips = 0; lastActualFlips = 0; lastNumFlips = 0; fpsHistoryValid = 0; fpsHistoryPos = 0; InitGfxState(); __KernelRegisterWaitTypeFuncs(WAITTYPE_VBLANK, __DisplayVblankBeginCallback, __DisplayVblankEndCallback); }
void __DisplayInit() { framebufIsLatched = false; framebuf.topaddr = 0x04000000; framebuf.pspframebuf = Memory::GetPointer(0x04000000); framebuf.pspFramebufFormat = PSP_DISPLAY_PIXEL_FORMAT_8888; framebuf.pspFramebufLinesize = 480; // ?? enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank); leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0); isVblank = 0; vCount = 0; InitGfxState(); }
void PSP_RunLoopUntil(u64 globalticks) { SaveState::Process(); if (cpuThread != NULL) { cpuThreadUntil = globalticks; if (CPU_NextState(CPU_THREAD_RUNNING, CPU_THREAD_EXECUTE)) { // The CPU doesn't actually respect cpuThreadUntil well, especially when skipping frames. // TODO: Something smarter? Or force CPU to bail periodically? while (!CPU_IsReady()) { gpu->RunEventsUntil(CoreTiming::GetTicks() + msToCycles(100)); } } else { ERROR_LOG(CPU, "Unable to execute CPU run loop, unexpected state: %d", cpuThreadState); } } else { mipsr4k.RunLoopUntil(globalticks); } }
void __CheatInit() { //Moved createFullPath to CheatInit from the constructor because it spams the log and constantly checks if exists. In here, only checks once. activeCheatFile = CHEATS_DIR + "/" + g_paramSFO.GetValueString("DISC_ID").c_str() + ".ini"; File::CreateFullPath(CHEATS_DIR); if (g_Config.bEnableCheats) { if (!File::Exists(activeCheatFile)) { File::CreateEmptyFile(activeCheatFile); } cheatEngine = new CWCheatEngine(); cheatEngine->CreateCodeList(); g_Config.bReloadCheats = false; CheatEvent = CoreTiming::RegisterEvent("CheatEvent", &hleCheat); CoreTiming::ScheduleEvent(msToCycles(77), CheatEvent, 0); } }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; // Wake up threads waiting for VBlank __KernelTriggerWait(WAITTYPE_VBLANK, 0, true); // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_VBLANK_INTR); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount+1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; } // Yeah, this has to be the right moment to end the frame. Should possibly blit the right buffer // depending on what's set in sceDisplaySetFramebuf, in order to support half-framerate games - // an initial hack could be to NOT end the frame if the buffer didn't change? that should work okay. { host->EndFrame(); host->BeginFrame(); if (g_Config.bDisplayFramebuffer) { INFO_LOG(HLE, "Drawing the framebuffer"); DisplayDrawer_DrawFramebuffer(framebuf.pspframebuf, framebuf.pspFramebufFormat, framebuf.pspFramebufLinesize); } shaderManager.DirtyShader(); shaderManager.DirtyUniform(DIRTY_ALL); } // TODO: Find a way to tell the CPU core to stop emulating here, when running on Android. }
void __DisplayInit() { gpuStats.reset(); hasSetMode = false; framebufIsLatched = false; framebuf.topaddr = 0x04000000; framebuf.pspFramebufFormat = PSP_DISPLAY_PIXEL_FORMAT_8888; framebuf.pspFramebufLinesize = 480; // ?? enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank); leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0); isVblank = 0; vCount = 0; hCount = 0; hCountTotal = 0; lastFrameTime = 0; InitGfxState(); }
u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) { FrameBufferState fbstate; DEBUG_LOG(HLE,"sceDisplaySetFramebuf(topaddr=%08x,linesize=%d,pixelsize=%d,sync=%d)", topaddr, linesize, pixelformat, sync); if (topaddr == 0) { DEBUG_LOG(HLE,"- screen off"); } else { fbstate.topaddr = topaddr; fbstate.pspFramebufFormat = (GEBufferFormat)pixelformat; fbstate.pspFramebufLinesize = linesize; } if (g_Config.iShowFPSCounter) { CalculateFPS(); } if (topaddr != framebuf.topaddr) { if (g_Config.iForceMaxEmulatedFPS) { u64 now = CoreTiming::GetTicks(); u64 expected = msToCycles(1000) / g_Config.iForceMaxEmulatedFPS; u64 actual = now - lastFlipCycles; if (actual < expected) hleEatCycles((int)(expected - actual)); lastFlipCycles = CoreTiming::GetTicks(); } } if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { // Write immediately to the current framebuffer parameters if (topaddr != 0) { framebuf = fbstate; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } else { WARN_LOG(HLE, "%s: PSP_DISPLAY_SETBUF_IMMEDIATE without topaddr?", __FUNCTION__); } } else if (topaddr != 0) { // Delay the write until vblank latchedFramebuf = fbstate; framebufIsLatched = true; } return 0; }
void hleCheat(u64 userdata, int cyclesLate) { if (cheatsEnabled != g_Config.bEnableCheats) { // Okay, let's move to the desired state, then. if (g_Config.bEnableCheats) { __CheatStart(); } else { __CheatStop(); } } // Only check once a second for cheats to be enabled. CoreTiming::ScheduleEvent(msToCycles(cheatsEnabled ? 77 : 1000), CheatEvent, 0); if (!cheatEngine || !cheatsEnabled) return; if (g_Config.bReloadCheats) { //Checks if the "reload cheats" button has been pressed. cheatEngine->CreateCodeList(); g_Config.bReloadCheats = false; } cheatEngine->Run(); }
void __DisplayInit() { gpuStats.reset(); hasSetMode = false; mode = 0; width = 480; height = 272; numSkippedFrames = 0; framebufIsLatched = false; framebuf.topaddr = 0x04000000; framebuf.pspFramebufFormat = PSP_DISPLAY_PIXEL_FORMAT_8888; framebuf.pspFramebufLinesize = 480; // ?? enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank); leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank); afterFlipEvent = CoreTiming::RegisterEvent("AfterFlip", &hleAfterFlip); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0); isVblank = 0; vCount = 0; curFrameTime = 0.0; nextFrameTime = 0.0; InitGfxState(); }
void hleLeaveVblank(u64 userdata, int cyclesLate) { isVblank = 0; DEBUG_LOG(HLE,"Leave VBlank %i", (int)userdata - 1); frameStartTicks = CoreTiming::GetTicks(); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs) - cyclesLate, enterVblankEvent, userdata); }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; vCount++; // // vCount increases at each VBLANK. // Fire the vblank listeners before we wake threads. __DisplayFireVblank(); // Wake up threads waiting for VBlank for (size_t i = 0; i < vblankWaitingThreads.size(); i++) { if (--vblankWaitingThreads[i].vcountUnblock == 0) { __KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0); vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--); } } // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount + 1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } gpuStats.numFrames++; if (g_Config.bShowFPSCounter) { CalculateFPS(); } bool skipFlip = false; // This frame was skipped, so no need to flip. if (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) { skipFlip = true; } // Draw screen overlays before blitting. Saves and restores the Ge context. // Yeah, this has to be the right moment to end the frame. Give the graphics backend opportunity // to blit the framebuffer, in order to support half-framerate games that otherwise wouldn't have // anything to draw here. gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME; bool throttle, skipFrame; DoFrameTiming(throttle, skipFrame); if (skipFrame) { gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME; numSkippedFrames++; } else { numSkippedFrames = 0; } if (!skipFlip) { // Setting CORE_NEXTFRAME causes a swap. // Check first though, might've just quit / been paused. if (coreState == CORE_RUNNING && gpu->FramebufferDirty()) { coreState = CORE_NEXTFRAME; } gpu->CopyDisplayToOutput(); } // Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame). // Right after, we regain control for a little bit in hleAfterFlip. I think that's a great // place to do housekeeping. CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0); }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; // Fire the vblank listeners before we wake threads. __DisplayFireVblank(); // Wake up threads waiting for VBlank for (size_t i = 0; i < vblankWaitingThreads.size(); i++) { if (--vblankWaitingThreads[i].vcountUnblock == 0) { __KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0); vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--); } } // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount + 1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } gpuStats.numFrames++; // Now we can subvert the Ge engine in order to draw custom overlays like stat counters etc. if (g_Config.bShowDebugStats && gpuStats.numDrawCalls) { DebugStats(); } if (g_Config.bShowFPSCounter) { char stats[50]; sprintf(stats, "%0.1f", calculateFPS()); #ifdef USING_GLES2 float zoom = 0.7f; /// g_Config.iWindowZoom; float soff = 0.7f; #else float zoom = 0.5f; /// g_Config.iWindowZoom; float soff = 0.5f; #endif PPGeBegin(); PPGeDrawText(stats, 476 + soff, 4 + soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000); PPGeDrawText(stats, 476 + -soff, 4 -soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000); PPGeDrawText(stats, 476, 4, PPGE_ALIGN_RIGHT, zoom, 0xFF30FF30); PPGeEnd(); } // Draw screen overlays before blitting. Saves and restores the Ge context. // Yeah, this has to be the right moment to end the frame. Give the graphics backend opportunity // to blit the framebuffer, in order to support half-framerate games that otherwise wouldn't have // anything to draw here. gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME; bool throttle, skipFrame, skipFlip; DoFrameTiming(throttle, skipFrame, skipFlip); // Setting CORE_NEXTFRAME causes a swap. if (skipFrame) { gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME; numSkippedFrames++; } else { numSkippedFrames = 0; } if (!skipFlip) { // Might've just quit / been paused. if (coreState == CORE_RUNNING) { coreState = CORE_NEXTFRAME; } CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0); gpu->CopyDisplayToOutput(); } // Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame). // Right after, we regain control for a little bit in hleAfterFlip. I think that's a great // place to do housekeeping. }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(SCEDISPLAY, "Enter VBlank %i", vbCount); isVblank = 1; vCount++; // vCount increases at each VBLANK. hCountBase += hCountPerVblank; // This is the "accumulated" hcount base. if (hCountBase > 0x7FFFFFFF) { hCountBase -= 0x80000000; } frameStartTicks = CoreTiming::GetTicks(); // Wake up threads waiting for VBlank u32 error; for (size_t i = 0; i < vblankWaitingThreads.size(); i++) { if (--vblankWaitingThreads[i].vcountUnblock == 0) { // Only wake it if it wasn't already released by someone else. SceUID waitID = __KernelGetWaitID(vblankWaitingThreads[i].threadID, WAITTYPE_VBLANK, error); if (waitID == 1) { __KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0); } vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--); } } // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount + 1); gpuStats.numVBlanks++; numVBlanksSinceFlip++; // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(SCEDISPLAY, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } // We flip only if the framebuffer was dirty. This eliminates flicker when using // non-buffered rendering. The interaction with frame skipping seems to need // some work. // But, let's flip at least once every 10 frames if possible, since there may be sound effects. if (gpu->FramebufferDirty() || (g_Config.iRenderingMode != 0 && numVBlanksSinceFlip >= 10)) { if (g_Config.iShowFPSCounter && g_Config.iShowFPSCounter < 4) { CalculateFPS(); } // Setting CORE_NEXTFRAME causes a swap. // Check first though, might've just quit / been paused. if (gpu->FramebufferReallyDirty()) { if (coreState == CORE_RUNNING) { coreState = CORE_NEXTFRAME; gpu->CopyDisplayToOutput(); actualFlips++; } } gpuStats.numFlips++; bool throttle, skipFrame; // 1.001f to compensate for the classic 59.94 NTSC framerate that the PSP seems to have. DoFrameTiming(throttle, skipFrame, (float)numVBlanksSinceFlip * (1.001f / 60.0f)); int maxFrameskip = 8; if (throttle) { // 4 here means 1 drawn, 4 skipped - so 12 fps minimum. maxFrameskip = g_Config.iFrameSkip; } if (numSkippedFrames >= maxFrameskip) { skipFrame = false; } if (skipFrame) { gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME; numSkippedFrames++; } else { gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME; numSkippedFrames = 0; } // Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame). // Right after, we regain control for a little bit in hleAfterFlip. I think that's a great // place to do housekeeping. CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0); numVBlanksSinceFlip = 0; } }
// Some games (GTA) never call this during gameplay, so bad place to put a framerate counter. static u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) { FrameBufferState fbstate = {0}; fbstate.topaddr = topaddr; fbstate.fmt = (GEBufferFormat)pixelformat; fbstate.stride = linesize; if (sync != PSP_DISPLAY_SETBUF_IMMEDIATE && sync != PSP_DISPLAY_SETBUF_NEXTFRAME) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_MODE, "invalid sync mode"); } if (topaddr != 0 && !Memory::IsRAMAddress(topaddr) && !Memory::IsVRAMAddress(topaddr)) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid address"); } if ((topaddr & 0xF) != 0) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_POINTER, "misaligned address"); } if ((linesize & 0x3F) != 0 || (linesize == 0 && topaddr != 0)) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid stride"); } if (pixelformat < 0 || pixelformat > GE_FORMAT_8888) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_FORMAT, "invalid format"); } if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { if (fbstate.fmt != latchedFramebuf.fmt || fbstate.stride != latchedFramebuf.stride) { return hleReportError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_MODE, "must change latched framebuf first"); } } hleEatCycles(290); s64 delayCycles = 0; // Don't count transitions between display off and display on. if (topaddr != 0 && topaddr != framebuf.topaddr && framebuf.topaddr != 0 && g_Config.iForceMaxEmulatedFPS > 0) { // Sometimes we get a small number, there's probably no need to delay the thread for this. // sceDisplaySetFramebuf() isn't supposed to delay threads at all. This is a hack. const int FLIP_DELAY_CYCLES_MIN = 10; // Some games (like Final Fantasy 4) only call this too much in spurts. // The goal is to fix games where this would result in a consistent overhead. const int FLIP_DELAY_MIN_FLIPS = 30; u64 now = CoreTiming::GetTicks(); // 1001 to account for NTSC timing (59.94 fps.) u64 expected = msToCycles(1001) / g_Config.iForceMaxEmulatedFPS; u64 actual = now - lastFlipCycles; if (actual < expected - FLIP_DELAY_CYCLES_MIN) { if (lastFlipsTooFrequent >= FLIP_DELAY_MIN_FLIPS) { delayCycles = expected - actual; } else { ++lastFlipsTooFrequent; } } else { --lastFlipsTooFrequent; } lastFlipCycles = CoreTiming::GetTicks(); } if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { // Write immediately to the current framebuffer parameters framebuf = fbstate; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt); } else { // Delay the write until vblank latchedFramebuf = fbstate; framebufIsLatched = true; // If we update the format or stride, this affects the current framebuf immediately. framebuf.fmt = latchedFramebuf.fmt; framebuf.stride = latchedFramebuf.stride; } if (delayCycles > 0) { // Okay, the game is going at too high a frame rate. God of War and Fat Princess both do this. // Simply eating the cycles works and is fast, but breaks other games (like Jeanne d'Arc.) // So, instead, we delay this HLE thread only (a small deviation from correct behavior.) return hleDelayResult(hleLogSuccessI(SCEDISPLAY, 0, "delaying frame thread"), "set framebuf", cyclesToUs(delayCycles)); } else { if (topaddr == 0) { return hleLogSuccessI(SCEDISPLAY, 0, "disabling display"); } else { return hleLogSuccessI(SCEDISPLAY, 0); } } }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; vCount++; // // vCount increases at each VBLANK. // Fire the vblank listeners before we wake threads. __DisplayFireVblank(); // Wake up threads waiting for VBlank for (size_t i = 0; i < vblankWaitingThreads.size(); i++) { if (--vblankWaitingThreads[i].vcountUnblock == 0) { __KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0); vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--); } } // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount + 1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } gpuStats.numVBlanks++; numVBlanksSinceFlip++; if (g_Config.iShowFPSCounter) { CalculateFPS(); } // We flip only if the framebuffer was dirty. This eliminates flicker when using // non-buffered rendering. The interaction with frame skipping seems to need // some work. if (gpu->FramebufferDirty()) { gpuStats.numFlips++; bool wasSkipped = (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) != 0; gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME; bool throttle, skipFrame; DoFrameTiming(throttle, skipFrame, (float)numVBlanksSinceFlip * (1.0f / 60.0f)); if (skipFrame) { gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME; numSkippedFrames++; } else { numSkippedFrames = 0; } // Setting CORE_NEXTFRAME causes a swap. // Check first though, might've just quit / been paused. if (!wasSkipped) { if (coreState == CORE_RUNNING) { coreState = CORE_NEXTFRAME; gpu->CopyDisplayToOutput(); } } // Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame). // Right after, we regain control for a little bit in hleAfterFlip. I think that's a great // place to do housekeeping. CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0); numVBlanksSinceFlip = 0; } }
void hleLeaveVblank(u64 userdata, int cyclesLate) { isVblank = 0; DEBUG_LOG(SCEDISPLAY,"Leave VBlank %i", (int)userdata - 1); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs) - cyclesLate, enterVblankEvent, userdata); }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; // Fire the vblank listeners before we wake threads. __DisplayFireVblank(); // Wake up threads waiting for VBlank for (size_t i = 0; i < vblankWaitingThreads.size(); i++) { __KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0); } vblankWaitingThreads.clear(); // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED, PSP_VBLANK_INTR); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount+1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } // Draw screen overlays before blitting. Saves and restores the Ge context. gpuStats.numFrames++; // Yeah, this has to be the right moment to end the frame. Give the graphics backend opportunity // to blit the framebuffer, in order to support half-framerate games that otherwise wouldn't have // anything to draw here. gpu->CopyDisplayToOutput(); // Now we can subvert the Ge engine in order to draw custom overlays like stat counters etc. // Here we will be drawing to the non buffered front surface. if (g_Config.bShowDebugStats && gpuStats.numDrawCalls) { gpu->UpdateStats(); char stats[512]; sprintf(stats, "Frames: %i\n" "Draw calls: %i\n" "Draw flushes: %i\n" "Vertices Transformed: %i\n" "Textures active: %i\n" "Textures decoded: %i\n" "Vertex shaders loaded: %i\n" "Fragment shaders loaded: %i\n" "Combined shaders loaded: %i\n", gpuStats.numFrames, gpuStats.numDrawCalls, gpuStats.numFlushes, gpuStats.numVertsTransformed, gpuStats.numTextures, gpuStats.numTexturesDecoded, gpuStats.numVertexShaders, gpuStats.numFragmentShaders, gpuStats.numShaders ); float zoom = 0.7f; /// g_Config.iWindowZoom; PPGeBegin(); PPGeDrawText(stats, 0, 0, 0, zoom, 0xFFc0c0c0); PPGeEnd(); gpuStats.resetFrame(); } 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)) { while (time_now_d() < lastFrameTime + 1.0 / 60.0) { Common::SleepCurrentThread(1); time_update(); } // Advance lastFrameTime by a constant amount each frame, // but don't let it get too far behind. lastFrameTime = std::max(lastFrameTime + 1.0 / 60.0, time_now_d() - 1.5 / 60.0); } // We are going to have to do something about audio timing for platforms that // are vsynced to something that's not exactly 60fps.. #endif host->BeginFrame(); gpu->BeginFrame(); // Tell the emu core that it's time to stop emulating // Win32 doesn't need this. #ifndef _WIN32 coreState = CORE_NEXTFRAME; #endif }
void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; // Fire the vblank listeners before we wake threads. __DisplayFireVblank(); // Wake up threads waiting for VBlank for (size_t i = 0; i < vblankWaitingThreads.size(); i++) { if (--vblankWaitingThreads[i].vcountUnblock == 0) { __KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0); vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--); } } // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount+1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } // Draw screen overlays before blitting. Saves and restores the Ge context. gpuStats.numFrames++; // Now we can subvert the Ge engine in order to draw custom overlays like stat counters etc. if (g_Config.bShowDebugStats && gpuStats.numDrawCalls) { gpu->UpdateStats(); char stats[2048]; sprintf(stats, "Frames: %i\n" "DL processing time: %0.2f ms\n" "Kernel processing time: %0.2f ms\n" "Slowest syscall: %s : %0.2f ms\n" "Most active syscall: %s : %0.2f ms\n" "Draw calls: %i, flushes %i\n" "Cached Draw calls: %i\n" "Num Tracked Vertex Arrays: %i\n" "Vertices Submitted: %i\n" "Cached Vertices Drawn: %i\n" "Uncached Vertices Drawn: %i\n" "FBOs active: %i\n" "Textures active: %i, decoded: %i\n" "Texture invalidations: %i\n" "Vertex shaders loaded: %i\n" "Fragment shaders loaded: %i\n" "Combined shaders loaded: %i\n", gpuStats.numFrames, gpuStats.msProcessingDisplayLists * 1000.0f, kernelStats.msInSyscalls * 1000.0f, kernelStats.slowestSyscallName ? kernelStats.slowestSyscallName : "(none)", kernelStats.slowestSyscallTime * 1000.0f, kernelStats.summedSlowestSyscallName ? kernelStats.summedSlowestSyscallName : "(none)", kernelStats.summedSlowestSyscallTime * 1000.0f, gpuStats.numDrawCalls, gpuStats.numFlushes, gpuStats.numCachedDrawCalls, gpuStats.numTrackedVertexArrays, gpuStats.numVertsSubmitted, gpuStats.numCachedVertsDrawn, gpuStats.numUncachedVertsDrawn, gpuStats.numFBOs, gpuStats.numTextures, gpuStats.numTexturesDecoded, gpuStats.numTextureInvalidations, gpuStats.numVertexShaders, gpuStats.numFragmentShaders, gpuStats.numShaders ); float zoom = 0.3f; /// g_Config.iWindowZoom; float soff = 0.3f; PPGeBegin(); PPGeDrawText(stats, soff, soff, 0, zoom, 0xCC000000); PPGeDrawText(stats, -soff, -soff, 0, zoom, 0xCC000000); PPGeDrawText(stats, 0, 0, 0, zoom, 0xFFFFFFFF); PPGeEnd(); gpuStats.resetFrame(); kernelStats.ResetFrame(); } if (g_Config.bShowFPSCounter) { char stats[50]; sprintf(stats, "%0.1f", calculateFPS()); #ifdef USING_GLES2 float zoom = 0.7f; /// g_Config.iWindowZoom; float soff = 0.7f; #else float zoom = 0.5f; /// g_Config.iWindowZoom; float soff = 0.5f; #endif PPGeBegin(); PPGeDrawText(stats, 476 + soff, 4 + soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000); PPGeDrawText(stats, 476 + -soff, 4 -soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000); PPGeDrawText(stats, 476, 4, PPGE_ALIGN_RIGHT, zoom, 0xFF30FF30); PPGeEnd(); } // Yeah, this has to be the right moment to end the frame. Give the graphics backend opportunity // to blit the framebuffer, in order to support half-framerate games that otherwise wouldn't have // anything to draw here. gpu->CopyDisplayToOutput(); host->EndFrame(); #ifdef _WIN32 // 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) && !PSP_CoreParameter().headLess) { while (time_now_d() < lastFrameTime + 1.0 / 60.0) { Common::SleepCurrentThread(1); time_update(); } // Advance lastFrameTime by a constant amount each frame, // but don't let it get too far behind. lastFrameTime = std::max(lastFrameTime + 1.0 / 60.0, time_now_d() - 1.5 / 60.0); } // We are going to have to do something about audio timing for platforms that // are vsynced to something that's not exactly 60fps.. #endif host->BeginFrame(); gpu->BeginFrame(); // Tell the emu core that it's time to stop emulating // Win32 doesn't need this. #ifndef _WIN32 coreState = CORE_NEXTFRAME; #endif }