// 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}; if (topaddr != 0) { fbstate.topaddr = topaddr; fbstate.pspFramebufFormat = (GEBufferFormat)pixelformat; fbstate.pspFramebufLinesize = linesize; } hleEatCycles(290); s64 delayCycles = 0; if (topaddr != framebuf.topaddr && 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.pspFramebufLinesize, framebuf.pspFramebufFormat); } else { // Delay the write until vblank latchedFramebuf = fbstate; framebufIsLatched = true; } 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); } } }
static u32 sceDisplayIsVsync() { u64 now = CoreTiming::GetTicks(); u64 start = frameStartTicks + msToCycles(vsyncStartMs); u64 end = frameStartTicks + msToCycles(vsyncEndMs); return hleLogSuccessI(SCEDISPLAY, now >= start && now <= end ? 1 : 0); }
static u32 sceDisplayWaitVblankCB() { if (!isVblank) { return DisplayWaitForVblanksCB("vblank waited", 1); } else { hleEatCycles(1110); hleReSchedule("vblank wait skipped"); return hleLogSuccessI(SCEDISPLAY, 1, "not waiting since in vblank"); } }
static u32 sceDisplayGetMode(u32 modeAddr, u32 widthAddr, u32 heightAddr) { if (Memory::IsValidAddress(modeAddr)) Memory::Write_U32(mode, modeAddr); if (Memory::IsValidAddress(widthAddr)) Memory::Write_U32(width, widthAddr); if (Memory::IsValidAddress(heightAddr)) Memory::Write_U32(height, heightAddr); return hleLogSuccessI(SCEDISPLAY, 0); }
static int sceDisplayAdjustAccumulatedHcount(int value) { if (value < 0) { return hleLogError(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_VALUE, "invalid value"); } // Since it includes the current hCount, find the difference to apply to the base. u32 accumHCount = __DisplayGetAccumulatedHcount(); int diff = value - accumHCount; hCountBase += diff; return hleLogSuccessI(SCEDISPLAY, 0); }
static u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int latchedMode) { const FrameBufferState &fbState = latchedMode == 1 && framebufIsLatched ? latchedFramebuf : framebuf; if (Memory::IsValidAddress(topaddrPtr)) Memory::Write_U32(fbState.topaddr, topaddrPtr); if (Memory::IsValidAddress(linesizePtr)) Memory::Write_U32(fbState.pspFramebufLinesize, linesizePtr); if (Memory::IsValidAddress(pixelFormatPtr)) Memory::Write_U32(fbState.pspFramebufFormat, pixelFormatPtr); return hleLogSuccessI(SCEDISPLAY, 0); }
static u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int latchedMode) { // NOTE: This is wrong and partially reverts #8753. Presumably there's something else involved here as well. // See #8816. Could also be a firmware version difference, there are a few of those... const FrameBufferState &fbState = (latchedMode == PSP_DISPLAY_SETBUF_NEXTFRAME && framebufIsLatched) ? latchedFramebuf : framebuf; if (Memory::IsValidAddress(topaddrPtr)) Memory::Write_U32(fbState.topaddr, topaddrPtr); if (Memory::IsValidAddress(linesizePtr)) Memory::Write_U32(fbState.stride, linesizePtr); if (Memory::IsValidAddress(pixelFormatPtr)) Memory::Write_U32(fbState.fmt, pixelFormatPtr); return hleLogSuccessI(SCEDISPLAY, 0); }
static u32 sceDisplaySetMode(int displayMode, int displayWidth, int displayHeight) { if (displayMode != PSP_DISPLAY_MODE_LCD || displayWidth != 480 || displayHeight != 272) { WARN_LOG_REPORT(SCEDISPLAY, "Video out requested, not supported: mode=%d size=%d,%d", displayMode, displayWidth, displayHeight); } if (displayMode != PSP_DISPLAY_MODE_LCD) { return hleLogWarning(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_MODE, "invalid mode"); } if (displayWidth != 480 || displayHeight != 272) { return hleLogWarning(SCEDISPLAY, SCE_KERNEL_ERROR_INVALID_SIZE, "invalid size"); } if (!hasSetMode) { gpu->InitClear(); hasSetMode = true; } mode = displayMode; width = displayWidth; height = displayHeight; hleLogSuccessI(SCEDISPLAY, 0); // On success, this implicitly waits for a vblank start. return DisplayWaitForVblanks("display mode", 1); }
static u32 sceDisplayIsForeground() { int result = hasSetMode && framebuf.topaddr != 0 ? 1 : 0; return hleLogSuccessI(SCEDISPLAY, result); }
static int sceDisplayGetAccumulatedHcount() { u32 accumHCount = __DisplayGetAccumulatedHcount(); hleEatCycles(235); return hleLogSuccessI(SCEDISPLAY, accumHCount); }
static u32 sceDisplayGetCurrentHcount() { hleEatCycles(275); return hleLogSuccessI(SCEDISPLAY, __DisplayGetCurrentHcount()); }
// 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); } } }
static u32 sceDisplayIsVblank() { return hleLogSuccessI(SCEDISPLAY, isVblank); }
static u32 sceDisplayGetResumeMode(u32 resumeModeAddr) { if (Memory::IsValidAddress(resumeModeAddr)) Memory::Write_U32(resumeMode, resumeModeAddr); return hleLogSuccessI(SCEDISPLAY, 0); }