CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/Core/HLE/sceDisplay.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include <algorithm>18#include <cmath>19#include <map>20#include <mutex>21#include <vector>2223// TODO: Move this somewhere else, cleanup.24#ifndef _WIN3225#include <unistd.h>26#include <sys/time.h>27#endif2829#include "Common/Data/Text/I18n.h"30#include "Common/Profiler/Profiler.h"31#include "Common/System/System.h"32#include "Common/System/OSD.h"33#include "Common/Serialize/Serializer.h"34#include "Common/Serialize/SerializeFuncs.h"35#include "Common/Serialize/SerializeMap.h"36#include "Common/TimeUtil.h"37#include "Core/Config.h"38#include "Core/CoreTiming.h"39#include "Core/CoreParameter.h"40#include "Core/FrameTiming.h"41#include "Core/Reporting.h"42#include "Core/Core.h"43#include "Core/System.h"44#include "Core/HLE/HLE.h"45#include "Core/HLE/FunctionWrappers.h"46#include "Core/HLE/sceDisplay.h"47#include "Core/HLE/sceKernel.h"48#include "Core/HLE/sceKernelThread.h"49#include "Core/HLE/sceKernelInterrupt.h"50#include "Core/HW/Display.h"51#include "Core/Util/PPGeDraw.h"52#include "Core/RetroAchievements.h"5354#include "GPU/GPU.h"55#include "GPU/GPUState.h"56#include "GPU/GPUInterface.h"57#include "GPU/Common/FramebufferManagerCommon.h"58#include "GPU/Common/PostShader.h"59#include "GPU/Debugger/Record.h"6061struct FrameBufferState {62u32 topaddr;63GEBufferFormat fmt;64int stride;65};6667struct WaitVBlankInfo {68WaitVBlankInfo(u32 tid) : threadID(tid), vcountUnblock(1) {}69WaitVBlankInfo(u32 tid, int vcount) : threadID(tid), vcountUnblock(vcount) {}70SceUID threadID;71// Number of vcounts to block for.72int vcountUnblock;7374void DoState(PointerWrap &p) {75auto s = p.Section("WaitVBlankInfo", 1);76if (!s)77return;7879Do(p, threadID);80Do(p, vcountUnblock);81}82};8384// STATE BEGIN85static FrameBufferState framebuf;86static FrameBufferState latchedFramebuf;87static bool framebufIsLatched;8889static int enterVblankEvent = -1;90static int leaveVblankEvent = -1;91static int afterFlipEvent = -1;92static int lagSyncEvent = -1;9394static double lastLagSync = 0.0;95static bool lagSyncScheduled = false;9697static int numSkippedFrames;98static bool hasSetMode;99static int resumeMode;100static int holdMode;101static int brightnessLevel;102static int mode;103static int width;104static int height;105static bool wasPaused;106static bool flippedThisFrame;107108static int framerate;109110// 1.001f to compensate for the classic 59.94 NTSC framerate that the PSP seems to have.111static double timePerVblank;112113// Don't include this in the state, time increases regardless of state.114static double curFrameTime;115static double lastFrameTime;116static double nextFrameTime;117static int numVBlanksSinceFlip;118119const int PSP_DISPLAY_MODE_LCD = 0;120121std::vector<WaitVBlankInfo> vblankWaitingThreads;122// Key is the callback id it was for, or if no callback, the thread id.123// Value is the goal vcount number (in case the callback takes >= 1 vcount to return.)124std::map<SceUID, int> vblankPausedWaits;125126// STATE END127128// The vblank period is 731.5 us (0.7315 ms)129const double vblankMs = 0.7315;130// These are guesses based on tests.131const double vsyncStartMs = 0.5925;132const double vsyncEndMs = 0.7265;133double frameMs;134135enum {136PSP_DISPLAY_SETBUF_IMMEDIATE = 0,137PSP_DISPLAY_SETBUF_NEXTFRAME = 1138};139140// For the "max 60 fps" setting.141static int lastFlipsTooFrequent = 0;142static u64 lastFlipCycles = 0;143static u64 nextFlipCycles = 0;144145void hleEnterVblank(u64 userdata, int cyclesLate);146void hleLeaveVblank(u64 userdata, int cyclesLate);147void hleAfterFlip(u64 userdata, int cyclesLate);148void hleLagSync(u64 userdata, int cyclesLate);149150void __DisplayVblankBeginCallback(SceUID threadID, SceUID prevCallbackId);151void __DisplayVblankEndCallback(SceUID threadID, SceUID prevCallbackId);152153void __DisplayFlip(int cyclesLate);154static void __DisplaySetFramerate(void);155156static bool UseLagSync() {157return g_Config.bForceLagSync && !g_Config.bAutoFrameSkip;158}159160static void ScheduleLagSync(int over = 0) {161lagSyncScheduled = UseLagSync();162if (lagSyncScheduled) {163// Reset over if it became too high, such as after pausing or initial loading.164// There's no real sense in it being more than 1/60th of a second.165if (over > 1000000 / framerate) {166over = 0;167}168CoreTiming::ScheduleEvent(usToCycles(1000 + over), lagSyncEvent, 0);169lastLagSync = time_now_d();170}171}172173void __DisplayInit() {174__DisplaySetFramerate();175DisplayHWInit();176hasSetMode = false;177mode = 0;178resumeMode = 0;179holdMode = 0;180brightnessLevel = 84;181width = 480;182height = 272;183numSkippedFrames = 0;184numVBlanksSinceFlip = 0;185flippedThisFrame = false;186framebufIsLatched = false;187framebuf.topaddr = 0x04000000;188framebuf.fmt = GE_FORMAT_8888;189framebuf.stride = 512;190memcpy(&latchedFramebuf, &framebuf, sizeof(latchedFramebuf));191lastFlipsTooFrequent = 0;192lastFlipCycles = 0;193nextFlipCycles = 0;194wasPaused = false;195196enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank);197leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank);198afterFlipEvent = CoreTiming::RegisterEvent("AfterFlip", &hleAfterFlip);199200lagSyncEvent = CoreTiming::RegisterEvent("LagSync", &hleLagSync);201ScheduleLagSync();202203CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0);204curFrameTime = 0.0;205nextFrameTime = 0.0;206lastFrameTime = 0.0;207208__KernelRegisterWaitTypeFuncs(WAITTYPE_VBLANK, __DisplayVblankBeginCallback, __DisplayVblankEndCallback);209}210211struct GPUStatistics_v0 {212int firstInts[11];213double msProcessingDisplayLists;214int moreInts[15];215};216217void __DisplayDoState(PointerWrap &p) {218auto s = p.Section("sceDisplay", 1, 7);219if (!s)220return;221222Do(p, framebuf);223Do(p, latchedFramebuf);224Do(p, framebufIsLatched);225DisplayHWDoState(p, s <= 2);226Do(p, hasSetMode);227Do(p, mode);228Do(p, resumeMode);229Do(p, holdMode);230if (s >= 4) {231Do(p, brightnessLevel);232}233Do(p, width);234Do(p, height);235WaitVBlankInfo wvi(0);236Do(p, vblankWaitingThreads, wvi);237Do(p, vblankPausedWaits);238239Do(p, enterVblankEvent);240CoreTiming::RestoreRegisterEvent(enterVblankEvent, "EnterVBlank", &hleEnterVblank);241Do(p, leaveVblankEvent);242CoreTiming::RestoreRegisterEvent(leaveVblankEvent, "LeaveVBlank", &hleLeaveVblank);243Do(p, afterFlipEvent);244CoreTiming::RestoreRegisterEvent(afterFlipEvent, "AfterFlip", &hleAfterFlip);245246if (s >= 5) {247Do(p, lagSyncEvent);248Do(p, lagSyncScheduled);249CoreTiming::RestoreRegisterEvent(lagSyncEvent, "LagSync", &hleLagSync);250lastLagSync = time_now_d();251if (lagSyncScheduled != UseLagSync()) {252ScheduleLagSync();253}254} else {255lagSyncEvent = -1;256CoreTiming::RestoreRegisterEvent(lagSyncEvent, "LagSync", &hleLagSync);257ScheduleLagSync();258}259260Do(p, gstate);261262// TODO: GPU stuff is really not the responsibility of sceDisplay.263// Display just displays the buffers the GPU has drawn, they are really completely distinct.264// Maybe a bit tricky to move at this point, though...265266gstate_c.DoState(p);267if (s < 2) {268// This shouldn't have been savestated anyway, but it was.269// It's unlikely to overlap with the first value in gpuStats.270int gpuVendorTemp = 0;271p.ExpectVoid(&gpuVendorTemp, sizeof(gpuVendorTemp));272}273if (s < 6) {274GPUStatistics_v0 oldStats;275Do(p, oldStats);276}277278if (s < 7) {279u64 now = CoreTiming::GetTicks();280lastFlipCycles = now;281nextFlipCycles = now;282} else {283Do(p, lastFlipCycles);284Do(p, nextFlipCycles);285}286287gpu->DoState(p);288289if (p.mode == p.MODE_READ) {290gpu->ReapplyGfxState();291gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt);292}293}294295void __DisplayShutdown() {296DisplayHWShutdown();297vblankWaitingThreads.clear();298}299300void __DisplayVblankBeginCallback(SceUID threadID, SceUID prevCallbackId) {301SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;302303// This means two callbacks in a row. PSP crashes if the same callback waits inside itself (may need more testing.)304// TODO: Handle this better?305if (vblankPausedWaits.find(pauseKey) != vblankPausedWaits.end()) {306return;307}308309WaitVBlankInfo waitData(0);310for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {311WaitVBlankInfo *t = &vblankWaitingThreads[i];312if (t->threadID == threadID) {313waitData = *t;314vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i);315break;316}317}318319if (waitData.threadID != threadID) {320WARN_LOG_REPORT(Log::sceDisplay, "sceDisplayWaitVblankCB: could not find waiting thread info.");321return;322}323324vblankPausedWaits[pauseKey] = __DisplayGetVCount() + waitData.vcountUnblock;325DEBUG_LOG(Log::sceDisplay, "sceDisplayWaitVblankCB: Suspending vblank wait for callback");326}327328void __DisplayVblankEndCallback(SceUID threadID, SceUID prevCallbackId) {329SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;330331// Probably should not be possible.332if (vblankPausedWaits.find(pauseKey) == vblankPausedWaits.end()) {333__KernelResumeThreadFromWait(threadID, 0);334return;335}336337int vcountUnblock = vblankPausedWaits[pauseKey];338vblankPausedWaits.erase(pauseKey);339if (vcountUnblock <= __DisplayGetVCount()) {340__KernelResumeThreadFromWait(threadID, 0);341return;342}343344// Still have to wait a bit longer.345vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vcountUnblock - __DisplayGetVCount()));346DEBUG_LOG(Log::sceDisplay, "sceDisplayWaitVblankCB: Resuming vblank wait from callback");347}348349void __DisplaySetWasPaused() {350wasPaused = true;351}352353// TOOD: Should return 59.997?354static int FrameTimingLimit() {355bool challenge = Achievements::HardcoreModeActive();356357auto fixRate = [=](int limit) {358int minRate = challenge ? 60 : 1;359if (limit != 0) {360return std::max(limit, minRate);361} else {362return limit;363}364};365366// Note: Fast-forward is OK in hardcore mode.367if (PSP_CoreParameter().fastForward)368return 0;369// Can't slow down in hardcore mode.370if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1)371return fixRate(g_Config.iFpsLimit1);372if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2)373return fixRate(g_Config.iFpsLimit2);374if (PSP_CoreParameter().fpsLimit == FPSLimit::ANALOG)375return fixRate(PSP_CoreParameter().analogFpsLimit);376return framerate;377}378379static bool FrameTimingThrottled() {380return FrameTimingLimit() != 0;381}382383static void DoFrameDropLogging(float scaledTimestep) {384if (lastFrameTime != 0.0 && !wasPaused && lastFrameTime + scaledTimestep < curFrameTime) {385const double actualTimestep = curFrameTime - lastFrameTime;386387char stats[4096];388__DisplayGetDebugStats(stats, sizeof(stats));389NOTICE_LOG(Log::sceDisplay, "Dropping frames - budget = %.2fms / %.1ffps, actual = %.2fms (+%.2fms) / %.1ffps\n%s", scaledTimestep * 1000.0, 1.0 / scaledTimestep, actualTimestep * 1000.0, (actualTimestep - scaledTimestep) * 1000.0, 1.0 / actualTimestep, stats);390}391}392393// All the throttling and frameskipping logic is here.394// This is called just before we drop out of the main loop, in order to allow the submit and present to happen.395static void DoFrameTiming(bool throttle, bool *skipFrame, float scaledTimestep, bool endOfFrame) {396PROFILE_THIS_SCOPE("timing");397*skipFrame = false;398399// Check if the frameskipping code should be enabled. If neither throttling or frameskipping is on,400// we have nothing to do here.401bool doFrameSkip = g_Config.iFrameSkip != 0;402if (!throttle && !doFrameSkip)403return;404405if (lastFrameTime == 0.0 || wasPaused) {406nextFrameTime = time_now_d() + scaledTimestep;407} else {408// Advance lastFrameTime by a constant amount each frame,409// but don't let it get too far behind as things can get very jumpy.410const double maxFallBehindFrames = 5.5;411412nextFrameTime = std::max(lastFrameTime + scaledTimestep, time_now_d() - maxFallBehindFrames * scaledTimestep);413}414curFrameTime = time_now_d();415416if (g_Config.bLogFrameDrops) {417DoFrameDropLogging(scaledTimestep);418}419420// Auto-frameskip automatically if speed limit is set differently than the default.421int frameSkipNum = DisplayCalculateFrameSkip();422if (g_Config.bAutoFrameSkip) {423// autoframeskip424// Argh, we are falling behind! Let's skip a frame and see if we catch up.425if (curFrameTime > nextFrameTime && doFrameSkip) {426*skipFrame = true;427}428} else if (frameSkipNum >= 1) {429// fixed frameskip430if (numSkippedFrames >= frameSkipNum)431*skipFrame = false;432else433*skipFrame = true;434}435436if (curFrameTime < nextFrameTime && throttle) {437// If time gap is huge just jump (somebody fast-forwarded)438if (nextFrameTime - curFrameTime > 2*scaledTimestep) {439nextFrameTime = curFrameTime;440} else {441// Wait until we've caught up.442// If we're ending the frame here, we'll defer the sleep until after the command buffers443// have been handed off to the render thread, for some more overlap.444if (endOfFrame) {445g_frameTiming.DeferWaitUntil(nextFrameTime, &curFrameTime);446} else {447WaitUntil(curFrameTime, nextFrameTime);448curFrameTime = time_now_d(); // I guess we could also just set it to nextFrameTime...449}450}451}452453lastFrameTime = nextFrameTime;454wasPaused = false;455}456457static void DoFrameIdleTiming() {458PROFILE_THIS_SCOPE("timing");459if (!FrameTimingThrottled() || !g_Config.bEnableSound || wasPaused) {460return;461}462463double before = time_now_d();464double dist = before - lastFrameTime;465// Ignore if the distance is just crazy. May mean wrap or pause.466if (dist < 0.0 || dist >= 15.0 * timePerVblank) {467return;468}469470float scaledVblank = timePerVblank;471int fpsLimit = FrameTimingLimit();472if (fpsLimit != 0 && fpsLimit != framerate) {473// 0 is handled in FrameTimingThrottled().474scaledVblank *= (float)framerate / fpsLimit;475}476477// If we have over at least a vblank of spare time, maintain at least 30fps in delay.478// This prevents fast forward during loading screens.479// Give a little extra wiggle room in case the next vblank does more work.480const double goal = lastFrameTime + (numVBlanksSinceFlip - 1) * scaledVblank - 0.001;481if (numVBlanksSinceFlip >= 2 && before < goal) {482double cur_time;483while ((cur_time = time_now_d()) < goal) {484#ifdef _WIN32485sleep_ms(1);486#else487const double left = goal - cur_time;488if (left > 0.0f && left < 1.0f) { // Sanity check489usleep((long)(left * 1000000));490}491#endif492}493494if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {495DisplayNotifySleep(time_now_d() - before);496}497}498}499500void hleEnterVblank(u64 userdata, int cyclesLate) {501int vbCount = userdata;502503VERBOSE_LOG(Log::sceDisplay, "Enter VBlank %i", vbCount);504505DisplayFireVblankStart();506507CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount + 1);508509// Trigger VBlank interrupt handlers.510__TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED | PSP_INTR_ALWAYS_RESCHED, PSP_VBLANK_INTR, PSP_INTR_SUB_ALL);511512// Wake up threads waiting for VBlank513u32 error;514bool wokeThreads = false;515for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {516if (--vblankWaitingThreads[i].vcountUnblock == 0) {517// Only wake it if it wasn't already released by someone else.518SceUID waitID = __KernelGetWaitID(vblankWaitingThreads[i].threadID, WAITTYPE_VBLANK, error);519if (waitID == 1) {520__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);521wokeThreads = true;522}523vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);524}525}526if (wokeThreads) {527__KernelReSchedule("entered vblank");528}529530numVBlanksSinceFlip++;531532// TODO: Should this be done here or in hleLeaveVblank?533if (framebufIsLatched) {534DEBUG_LOG(Log::sceDisplay, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr);535framebuf = latchedFramebuf;536framebufIsLatched = false;537gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt);538__DisplayFlip(cyclesLate);539} else if (!flippedThisFrame) {540// Gotta flip even if sceDisplaySetFramebuf was not called.541__DisplayFlip(cyclesLate);542}543}544545static void NotifyUserIfSlow() {546// Let the user know if we're running slow, so they know to adjust settings.547// Sometimes users just think the sound emulation is broken.548static bool hasNotifiedSlow = false;549if (!g_Config.bHideSlowWarnings &&550!hasNotifiedSlow &&551PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL &&552DisplayIsRunningSlow()) {553#ifndef _DEBUG554auto err = GetI18NCategory(I18NCat::ERRORS);555if (g_Config.bSoftwareRendering) {556g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: Try turning off Software Rendering"), 5.0f);557} else {558g_OSD.Show(OSDType::MESSAGE_INFO, err->T("Running slow: try frameskip, sound is choppy when slow"));559}560#endif561hasNotifiedSlow = true;562}563}564565void __DisplayFlip(int cyclesLate) {566__DisplaySetFramerate();567568flippedThisFrame = true;569// We flip only if the framebuffer was dirty. This eliminates flicker when using570// non-buffered rendering. The interaction with frame skipping seems to need571// some work.572// But, let's flip at least once every 10 vblanks, to update fps, etc.573const bool noRecentFlip = !g_Config.bSkipBufferEffects && numVBlanksSinceFlip >= 10;574// Also let's always flip for animated shaders.575bool postEffectRequiresFlip = false;576577bool duplicateFrames = g_Config.bRenderDuplicateFrames && g_Config.iFrameSkip == 0;578579bool fastForwardSkipFlip = g_Config.iFastForwardMode != (int)FastForwardMode::CONTINUOUS;580581if (gpu) {582Draw::DrawContext *draw = gpu->GetDrawContext();583584if (draw) {585g_frameTiming.presentMode = ComputePresentMode(draw, &g_frameTiming.presentInterval);586if (!draw->GetDeviceCaps().presentInstantModeChange && g_frameTiming.presentMode == Draw::PresentMode::FIFO) {587// Some backends can't just flip into MAILBOX/IMMEDIATE mode instantly.588// Vulkan doesn't support the interval setting, so we force skipping the flip.589// TODO: We'll clean this up in a more backend-independent way later.590fastForwardSkipFlip = true;591}592} else {593g_frameTiming.presentMode = Draw::PresentMode::FIFO;594g_frameTiming.presentInterval = 1;595}596}597598if (!g_Config.bSkipBufferEffects) {599postEffectRequiresFlip = duplicateFrames || g_Config.bShaderChainRequires60FPS;600}601602if (!FrameTimingThrottled()) {603// NOTICE_LOG(Log::System, "Throttle: %d %d", (int)fastForwardSkipFlip, (int)postEffectRequiresFlip);604}605606const bool fbDirty = gpu->FramebufferDirty();607608bool needFlip = fbDirty || noRecentFlip || postEffectRequiresFlip;609if (!needFlip) {610// Okay, there's no new frame to draw, game might be sitting in a static loading screen611// or similar, and not long enough to trigger noRecentFlip. But audio may be playing, so we need to time still.612DoFrameIdleTiming();613return;614}615616// Debugger integration617int frameSleepPos = DisplayGetSleepPos();618double frameSleepStart = time_now_d();619DisplayFireFlip();620621NotifyUserIfSlow();622623bool forceNoFlip = false;624float refreshRate = System_GetPropertyFloat(SYSPROP_DISPLAY_REFRESH_RATE);625// Avoid skipping on devices that have 58 or 59 FPS, except when alternate speed is set.626bool refreshRateNeedsSkip = FrameTimingLimit() != framerate && FrameTimingLimit() > refreshRate;627// Alternative to frameskip fast-forward, where we draw everything.628// Useful if skipping a frame breaks graphics or for checking drawing speed.629if (fastForwardSkipFlip && (!FrameTimingThrottled() || refreshRateNeedsSkip)) {630static double lastFlip = 0;631double now = time_now_d();632if ((now - lastFlip) < 1.0f / refreshRate) {633forceNoFlip = true;634} else {635lastFlip = now;636}637}638639// Setting CORE_NEXTFRAME (which Core_NextFrame does) causes a swap.640const bool fbReallyDirty = gpu->FramebufferReallyDirty();641642bool nextFrame = false;643644if (fbReallyDirty || noRecentFlip || postEffectRequiresFlip) {645// Check first though, might've just quit / been paused.646if (!forceNoFlip)647nextFrame = Core_NextFrame();648if (nextFrame) {649gpu->CopyDisplayToOutput(fbReallyDirty);650if (fbReallyDirty) {651DisplayFireActualFlip();652}653}654}655656if (fbDirty) {657gpuStats.numFlips++;658}659660bool throttle = FrameTimingThrottled();661662int fpsLimit = FrameTimingLimit();663float scaledTimestep = (float)numVBlanksSinceFlip * timePerVblank;664if (fpsLimit > 0 && fpsLimit != framerate) {665scaledTimestep *= (float)framerate / fpsLimit;666}667bool skipFrame;668DoFrameTiming(throttle, &skipFrame, scaledTimestep, nextFrame);669670int maxFrameskip = 8;671int frameSkipNum = DisplayCalculateFrameSkip();672if (throttle) {673// 4 here means 1 drawn, 4 skipped - so 12 fps minimum.674maxFrameskip = frameSkipNum;675}676if (numSkippedFrames >= maxFrameskip || GPURecord::IsActivePending()) {677skipFrame = false;678}679680if (skipFrame) {681// Tell the emulated GPU to skip the next frame.682gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;683numSkippedFrames++;684} else {685gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;686numSkippedFrames = 0;687}688689// Returning here with coreState == CORE_NEXTFRAME causes a buffer flip to happen (next frame).690// Right after, we regain control for a little bit in hleAfterFlip. I think that's a great691// place to do housekeeping.692693CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);694numVBlanksSinceFlip = 0;695696if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {697// Track how long we sleep (whether vsync or sleep_ms.)698DisplayNotifySleep(time_now_d() - frameSleepStart, frameSleepPos);699}700}701702void hleAfterFlip(u64 userdata, int cyclesLate) {703gpu->PSPFrame();704705PPGeNotifyFrame();706707// This seems like as good a time as any to check if the config changed.708if (lagSyncScheduled != UseLagSync()) {709ScheduleLagSync();710}711}712713void hleLeaveVblank(u64 userdata, int cyclesLate) {714flippedThisFrame = false;715VERBOSE_LOG(Log::sceDisplay,"Leave VBlank %i", (int)userdata - 1);716CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs) - cyclesLate, enterVblankEvent, userdata);717718// Fire the vblank listeners after the vblank completes.719DisplayFireVblankEnd();720}721722void hleLagSync(u64 userdata, int cyclesLate) {723// The goal here is to prevent network, audio, and input lag from the real world.724// Our normal timing is very "stop and go". This is efficient, but causes real world lag.725// This event (optionally) runs every 1ms to sync with the real world.726PROFILE_THIS_SCOPE("timing");727728if (!FrameTimingThrottled()) {729lagSyncScheduled = false;730return;731}732733float scale = 1.0f;734int fpsLimit = FrameTimingLimit();735if (fpsLimit != 0 && fpsLimit != framerate) {736// 0 is handled in FrameTimingThrottled().737scale = (float)framerate / fpsLimit;738}739740const double goal = lastLagSync + (scale / 1000.0f);741double before = time_now_d();742// Don't lag too long ever, if they leave it paused.743double now = before;744while (now < goal && goal < now + 0.01) {745// Tight loop on win32 - intentionally, as timing is otherwise not precise enough.746#ifndef _WIN32747const double left = goal - now;748if (left > 0.0f && left < 1.0f) { // Sanity check749usleep((long)(left * 1000000.0));750}751#else752yield();753#endif754now = time_now_d();755}756757const int emuOver = (int)cyclesToUs(cyclesLate);758const int over = (int)((now - goal) * 1000000);759ScheduleLagSync(over - emuOver);760761if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_GRAPH || coreCollectDebugStats) {762DisplayNotifySleep(now - before);763}764}765766static u32 sceDisplayIsVblank() {767return hleLogSuccessI(Log::sceDisplay, DisplayIsVblank());768}769770static int DisplayWaitForVblanks(const char *reason, int vblanks, bool callbacks = false) {771const s64 ticksIntoFrame = CoreTiming::GetTicks() - DisplayFrameStartTicks();772const s64 cyclesToNextVblank = msToCycles(frameMs) - ticksIntoFrame;773774// These syscalls take about 115 us, so if the next vblank is before then, we're waiting extra.775// At least, on real firmware a wait >= 16500 into the frame will wait two.776if (cyclesToNextVblank <= usToCycles(115)) {777++vblanks;778}779780vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vblanks));781__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, callbacks, reason);782783return hleLogSuccessVerboseI(Log::sceDisplay, 0, "waiting for %d vblanks", vblanks);784}785786void __DisplayWaitForVblanks(const char* reason, int vblanks, bool callbacks) {787DisplayWaitForVblanks(reason, vblanks, callbacks);788}789790static u32 sceDisplaySetMode(int displayMode, int displayWidth, int displayHeight) {791if (displayMode != PSP_DISPLAY_MODE_LCD || displayWidth != 480 || displayHeight != 272) {792WARN_LOG_REPORT(Log::sceDisplay, "Video out requested, not supported: mode=%d size=%d,%d", displayMode, displayWidth, displayHeight);793}794if (displayMode != PSP_DISPLAY_MODE_LCD) {795return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_MODE, "invalid mode");796}797if (displayWidth != 480 || displayHeight != 272) {798return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid size");799}800801hasSetMode = true;802mode = displayMode;803width = displayWidth;804height = displayHeight;805806hleLogSuccessI(Log::sceDisplay, 0);807// On success, this implicitly waits for a vblank start.808return DisplayWaitForVblanks("display mode", 1);809}810811void __DisplaySetFramebuf(u32 topaddr, int linesize, int pixelFormat, int sync) {812FrameBufferState fbstate = {0};813fbstate.topaddr = topaddr;814fbstate.fmt = (GEBufferFormat)pixelFormat;815fbstate.stride = linesize;816817if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) {818// Write immediately to the current framebuffer parameters.819framebuf = fbstate;820// Also update latchedFramebuf for any sceDisplayGetFramebuf() after this.821latchedFramebuf = fbstate;822gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.stride, framebuf.fmt);823// IMMEDIATE means that the buffer is fine. We can just flip immediately.824// Doing it in non-buffered though creates problems (black screen) on occasion though825// so let's not.826if (!flippedThisFrame && !g_Config.bSkipBufferEffects) {827double before_flip = time_now_d();828__DisplayFlip(0);829double after_flip = time_now_d();830// Ignore for debug stats.831hleSetFlipTime(after_flip - before_flip);832}833} else {834// Delay the write until vblank835latchedFramebuf = fbstate;836framebufIsLatched = true;837838// If we update the format or stride, this affects the current framebuf immediately.839framebuf.fmt = latchedFramebuf.fmt;840framebuf.stride = latchedFramebuf.stride;841}842}843844// Some games (GTA) never call this during gameplay, so bad place to put a framerate counter.845u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) {846if (sync != PSP_DISPLAY_SETBUF_IMMEDIATE && sync != PSP_DISPLAY_SETBUF_NEXTFRAME) {847return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_MODE, "invalid sync mode");848}849if (topaddr != 0 && !Memory::IsRAMAddress(topaddr) && !Memory::IsVRAMAddress(topaddr)) {850return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_POINTER, "invalid address");851}852if ((topaddr & 0xF) != 0) {853return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_POINTER, "misaligned address");854}855if ((linesize & 0x3F) != 0 || (linesize == 0 && topaddr != 0)) {856return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid stride");857}858if (pixelformat < 0 || pixelformat > GE_FORMAT_8888) {859return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_FORMAT, "invalid format");860}861862if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) {863if ((GEBufferFormat)pixelformat != latchedFramebuf.fmt || linesize != latchedFramebuf.stride) {864return hleReportError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_MODE, "must change latched framebuf first");865}866}867868hleEatCycles(290);869870s64 delayCycles = 0;871// Don't count transitions between display off and display on.872if (topaddr != 0 &&873(topaddr != framebuf.topaddr || PSP_CoreParameter().compat.flags().SplitFramebufferMargin) &&874framebuf.topaddr != 0 &&875PSP_CoreParameter().compat.flags().ForceMax60FPS) {876// sceDisplaySetFramebuf() isn't supposed to delay threads at all. This is a hack.877// So let's only delay when it's more than 1ms.878const s64 FLIP_DELAY_CYCLES_MIN = usToCycles(1000);879// Some games (like Final Fantasy 4) only call this too much in spurts.880// The goal is to fix games where this would result in a consistent overhead.881const int FLIP_DELAY_MIN_FLIPS = 30;882// Since we move nextFlipCycles forward a whole frame each time, we allow it to be a little ahead.883// Otherwise it'll always be ahead if the game messes up even once.884const s64 LEEWAY_CYCLES_PER_FLIP = usToCycles(10);885886u64 now = CoreTiming::GetTicks();887s64 cyclesAhead = nextFlipCycles - now;888if (cyclesAhead > FLIP_DELAY_CYCLES_MIN) {889if (lastFlipsTooFrequent >= FLIP_DELAY_MIN_FLIPS) {890delayCycles = cyclesAhead;891} else {892++lastFlipsTooFrequent;893}894} else if (-lastFlipsTooFrequent < FLIP_DELAY_MIN_FLIPS) {895--lastFlipsTooFrequent;896}897898// 1001 to account for NTSC timing (59.94 fps.)899u64 expected = msToCycles(1001) / framerate - LEEWAY_CYCLES_PER_FLIP;900lastFlipCycles = now;901nextFlipCycles = std::max(lastFlipCycles, nextFlipCycles) + expected;902}903904__DisplaySetFramebuf(topaddr, linesize, pixelformat, sync);905906// No delaying while inside an interrupt. It'll cause idle threads to starve.907if (delayCycles > 0 && !__IsInInterrupt()) {908// Okay, the game is going at too high a frame rate. God of War and Fat Princess both do this.909// Simply eating the cycles works and is fast, but breaks other games (like Jeanne d'Arc.)910// So, instead, we delay this HLE thread only (a small deviation from correct behavior.)911return hleDelayResult(hleLogSuccessI(Log::sceDisplay, 0, "delaying frame thread"), "set framebuf", cyclesToUs(delayCycles));912} else {913if (topaddr == 0) {914return hleLogSuccessI(Log::sceDisplay, 0, "disabling display");915} else {916return hleLogSuccessI(Log::sceDisplay, 0);917}918}919}920921bool __DisplayGetFramebuf(PSPPointer<u8> *topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) {922const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf;923if (topaddr != nullptr)924(*topaddr).ptr = fbState.topaddr;925if (linesize != nullptr)926*linesize = fbState.stride;927if (pixelFormat != nullptr)928*pixelFormat = fbState.fmt;929930return true;931}932933static u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int latchedMode) {934const FrameBufferState &fbState = latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME ? latchedFramebuf : framebuf;935936if (Memory::IsValidAddress(topaddrPtr))937Memory::Write_U32(fbState.topaddr, topaddrPtr);938if (Memory::IsValidAddress(linesizePtr))939Memory::Write_U32(fbState.stride, linesizePtr);940if (Memory::IsValidAddress(pixelFormatPtr))941Memory::Write_U32(fbState.fmt, pixelFormatPtr);942943return hleLogSuccessI(Log::sceDisplay, 0);944}945946static int DisplayWaitForVblanksCB(const char *reason, int vblanks) {947return DisplayWaitForVblanks(reason, vblanks, true);948}949950static u32 sceDisplayWaitVblankStart() {951return DisplayWaitForVblanks("vblank start waited", 1);952}953954static u32 sceDisplayWaitVblank() {955if (!DisplayIsVblank()) {956return DisplayWaitForVblanks("vblank waited", 1);957} else {958hleEatCycles(1110);959hleReSchedule("vblank wait skipped");960return hleLogSuccessI(Log::sceDisplay, 1, "not waiting since in vblank");961}962}963964static u32 sceDisplayWaitVblankStartMulti(int vblanks) {965if (vblanks <= 0) {966return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid number of vblanks");967}968if (!__KernelIsDispatchEnabled())969return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");970if (__IsInInterrupt())971return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "in interrupt");972973return DisplayWaitForVblanks("vblank start multi waited", vblanks);974}975976static u32 sceDisplayWaitVblankCB() {977if (!DisplayIsVblank()) {978return DisplayWaitForVblanksCB("vblank waited", 1);979} else {980hleEatCycles(1110);981hleReSchedule("vblank wait skipped");982return hleLogSuccessI(Log::sceDisplay, 1, "not waiting since in vblank");983}984}985986static u32 sceDisplayWaitVblankStartCB() {987return DisplayWaitForVblanksCB("vblank start waited", 1);988}989990static u32 sceDisplayWaitVblankStartMultiCB(int vblanks) {991if (vblanks <= 0) {992return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid number of vblanks");993}994if (!__KernelIsDispatchEnabled())995return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_CAN_NOT_WAIT, "dispatch disabled");996if (__IsInInterrupt())997return hleLogWarning(Log::sceDisplay, SCE_KERNEL_ERROR_ILLEGAL_CONTEXT, "in interrupt");998999return DisplayWaitForVblanksCB("vblank start multi waited", vblanks);1000}10011002static u32 sceDisplayGetVcount() {1003hleEatCycles(150);1004hleReSchedule("get vcount");1005return hleLogSuccessVerboseI(Log::sceDisplay, __DisplayGetVCount());1006}10071008static u32 sceDisplayGetCurrentHcount() {1009hleEatCycles(275);1010return hleLogSuccessI(Log::sceDisplay, __DisplayGetCurrentHcount());1011}10121013static int sceDisplayAdjustAccumulatedHcount(int value) {1014if (value < 0) {1015return hleLogError(Log::sceDisplay, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid value");1016}10171018// Since it includes the current hCount, find the difference to apply to the base.1019u32 accumHCount = __DisplayGetAccumulatedHcount();1020int diff = value - accumHCount;1021DisplayAdjustAccumulatedHcount(diff);10221023return hleLogSuccessI(Log::sceDisplay, 0);1024}10251026static int sceDisplayGetAccumulatedHcount() {1027u32 accumHCount = __DisplayGetAccumulatedHcount();1028hleEatCycles(235);1029return hleLogSuccessI(Log::sceDisplay, accumHCount);1030}10311032static float sceDisplayGetFramePerSec() {1033const static float framePerSec = 59.9400599f;1034VERBOSE_LOG(Log::sceDisplay,"%f=sceDisplayGetFramePerSec()", framePerSec);1035return framePerSec; // (9MHz * 1)/(525 * 286)1036}10371038static u32 sceDisplayIsForeground() {1039int result = hasSetMode && framebuf.topaddr != 0 ? 1 : 0;1040return hleLogSuccessI(Log::sceDisplay, result);1041}10421043static u32 sceDisplayGetMode(u32 modeAddr, u32 widthAddr, u32 heightAddr) {1044if (Memory::IsValidAddress(modeAddr))1045Memory::Write_U32(mode, modeAddr);1046if (Memory::IsValidAddress(widthAddr))1047Memory::Write_U32(width, widthAddr);1048if (Memory::IsValidAddress(heightAddr))1049Memory::Write_U32(height, heightAddr);1050return hleLogSuccessI(Log::sceDisplay, 0);1051}10521053static u32 sceDisplayIsVsync() {1054u64 now = CoreTiming::GetTicks();1055u64 start = DisplayFrameStartTicks() + msToCycles(vsyncStartMs);1056u64 end = DisplayFrameStartTicks() + msToCycles(vsyncEndMs);10571058return hleLogSuccessI(Log::sceDisplay, now >= start && now <= end ? 1 : 0);1059}10601061static u32 sceDisplayGetResumeMode(u32 resumeModeAddr) {1062if (Memory::IsValidAddress(resumeModeAddr))1063Memory::Write_U32(resumeMode, resumeModeAddr);1064return hleLogSuccessI(Log::sceDisplay, 0);1065}10661067static u32 sceDisplaySetResumeMode(u32 rMode) {1068// Not sure what this does, seems to do nothing in tests and accept all values.1069resumeMode = rMode;1070return hleReportError(Log::sceDisplay, 0, "unsupported");1071}10721073static u32 sceDisplayGetBrightness(u32 levelAddr, u32 otherAddr) {1074// Standard levels on a PSP: 44, 60, 72, 84 (AC only)10751076if (Memory::IsValidAddress(levelAddr)) {1077Memory::Write_U32(brightnessLevel, levelAddr);1078}1079// Always seems to write zero?1080if (Memory::IsValidAddress(otherAddr)) {1081Memory::Write_U32(0, otherAddr);1082}1083return hleLogWarning(Log::sceDisplay, 0);1084}10851086static u32 sceDisplaySetBrightness(int level, int other) {1087// Note: Only usable in kernel mode.1088brightnessLevel = level;1089return hleLogWarning(Log::sceDisplay, 0);1090}10911092static u32 sceDisplaySetHoldMode(u32 hMode) {1093// Not sure what this does, seems to do nothing in tests and accept all values.1094holdMode = hMode;1095return hleReportError(Log::sceDisplay, 0, "unsupported");1096}10971098const HLEFunction sceDisplay[] = {1099{0X0E20F177, &WrapU_III<sceDisplaySetMode>, "sceDisplaySetMode", 'x', "iii" },1100{0X289D82FE, &WrapU_UIII<sceDisplaySetFramebuf>, "sceDisplaySetFrameBuf", 'x', "xiii"},1101{0XEEDA2E54, &WrapU_UUUI<sceDisplayGetFramebuf>, "sceDisplayGetFrameBuf", 'x', "pppi"},1102{0X36CDFADE, &WrapU_V<sceDisplayWaitVblank>, "sceDisplayWaitVblank", 'x', "", HLE_NOT_DISPATCH_SUSPENDED },1103{0X984C27E7, &WrapU_V<sceDisplayWaitVblankStart>, "sceDisplayWaitVblankStart", 'x', "", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },1104{0X40F1469C, &WrapU_I<sceDisplayWaitVblankStartMulti>, "sceDisplayWaitVblankStartMulti", 'x', "i" },1105{0X8EB9EC49, &WrapU_V<sceDisplayWaitVblankCB>, "sceDisplayWaitVblankCB", 'x', "", HLE_NOT_DISPATCH_SUSPENDED },1106{0X46F186C3, &WrapU_V<sceDisplayWaitVblankStartCB>, "sceDisplayWaitVblankStartCB", 'x', "", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED },1107{0X77ED8B3A, &WrapU_I<sceDisplayWaitVblankStartMultiCB>, "sceDisplayWaitVblankStartMultiCB", 'x', "i" },1108{0XDBA6C4C4, &WrapF_V<sceDisplayGetFramePerSec>, "sceDisplayGetFramePerSec", 'f', "" },1109{0X773DD3A3, &WrapU_V<sceDisplayGetCurrentHcount>, "sceDisplayGetCurrentHcount", 'x', "" },1110{0X210EAB3A, &WrapI_V<sceDisplayGetAccumulatedHcount>, "sceDisplayGetAccumulatedHcount", 'i', "" },1111{0XA83EF139, &WrapI_I<sceDisplayAdjustAccumulatedHcount>, "sceDisplayAdjustAccumulatedHcount", 'i', "i" },1112{0X9C6EAAD7, &WrapU_V<sceDisplayGetVcount>, "sceDisplayGetVcount", 'x', "" },1113{0XDEA197D4, &WrapU_UUU<sceDisplayGetMode>, "sceDisplayGetMode", 'x', "ppp" },1114{0X7ED59BC4, &WrapU_U<sceDisplaySetHoldMode>, "sceDisplaySetHoldMode", 'x', "x" },1115{0XA544C486, &WrapU_U<sceDisplaySetResumeMode>, "sceDisplaySetResumeMode", 'x', "x" },1116{0XBF79F646, &WrapU_U<sceDisplayGetResumeMode>, "sceDisplayGetResumeMode", 'x', "p" },1117{0XB4F378FA, &WrapU_V<sceDisplayIsForeground>, "sceDisplayIsForeground", 'x', "" },1118{0X31C4BAA8, &WrapU_UU<sceDisplayGetBrightness>, "sceDisplayGetBrightness", 'x', "pp" },1119{0X9E3C6DC6, &WrapU_II<sceDisplaySetBrightness>, "sceDisplaySetBrightness", 'x', "ii" },1120{0X4D4E10EC, &WrapU_V<sceDisplayIsVblank>, "sceDisplayIsVblank", 'x', "" },1121{0X21038913, &WrapU_V<sceDisplayIsVsync>, "sceDisplayIsVsync", 'x', "" },1122};11231124void Register_sceDisplay() {1125RegisterModule("sceDisplay", ARRAY_SIZE(sceDisplay), sceDisplay);1126}11271128void Register_sceDisplay_driver() {1129RegisterModule("sceDisplay_driver", ARRAY_SIZE(sceDisplay), sceDisplay);1130}11311132static void __DisplaySetFramerate(void) {1133if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_VR)1134framerate = g_Config.bForce72Hz ? 72 : 60;1135else1136framerate = g_Config.iDisplayRefreshRate;11371138timePerVblank = 1.001 / (double)framerate;1139frameMs = 1001.0 / (double)framerate;1140}114111421143