mirror of
https://github.com/hrydgard/ppsspp.git
synced 2025-04-02 11:01:50 -04:00
This fixes loading screens and such, which drop the fps down to 0 or 6 according to our measure, which can distort audio if we've got free time. If the system is not keeping up, should have the same behavior as before.
1073 lines
34 KiB
C++
1073 lines
34 KiB
C++
// 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 <vector>
|
|
#include <map>
|
|
#include <cmath>
|
|
#include <algorithm>
|
|
|
|
// TODO: Move this somewhere else, cleanup.
|
|
#ifndef _WIN32
|
|
#include <unistd.h>
|
|
#include <sys/time.h>
|
|
#endif
|
|
|
|
// TODO: Move the relevant parts into common. Don't want the core
|
|
// to be dependent on "native", I think. Or maybe should get rid of common
|
|
// and move everything into native...
|
|
#include "base/logging.h"
|
|
#include "base/timeutil.h"
|
|
|
|
#ifndef _XBOX
|
|
#include "gfx_es2/gl_state.h"
|
|
#endif
|
|
|
|
#include "Common/ChunkFile.h"
|
|
#include "Core/CoreTiming.h"
|
|
#include "Core/CoreParameter.h"
|
|
#include "Core/Reporting.h"
|
|
#include "Core/Config.h"
|
|
#include "Core/System.h"
|
|
#include "Core/HLE/HLE.h"
|
|
#include "Core/HLE/FunctionWrappers.h"
|
|
#include "Core/HLE/sceDisplay.h"
|
|
#include "Core/HLE/sceKernel.h"
|
|
#include "Core/HLE/sceKernelThread.h"
|
|
#include "Core/HLE/sceKernelInterrupt.h"
|
|
|
|
#include "GPU/GPUState.h"
|
|
#include "GPU/GPUInterface.h"
|
|
|
|
struct FrameBufferState {
|
|
u32 topaddr;
|
|
GEBufferFormat pspFramebufFormat;
|
|
int pspFramebufLinesize;
|
|
};
|
|
|
|
struct WaitVBlankInfo
|
|
{
|
|
WaitVBlankInfo(u32 tid) : threadID(tid), vcountUnblock(1) {}
|
|
WaitVBlankInfo(u32 tid, int vcount) : threadID(tid), vcountUnblock(vcount) {}
|
|
SceUID threadID;
|
|
// Number of vcounts to block for.
|
|
int vcountUnblock;
|
|
|
|
void DoState(PointerWrap &p)
|
|
{
|
|
auto s = p.Section("WaitVBlankInfo", 1);
|
|
if (!s)
|
|
return;
|
|
|
|
p.Do(threadID);
|
|
p.Do(vcountUnblock);
|
|
}
|
|
};
|
|
|
|
// STATE BEGIN
|
|
static FrameBufferState framebuf;
|
|
static FrameBufferState latchedFramebuf;
|
|
static bool framebufIsLatched;
|
|
|
|
static int enterVblankEvent = -1;
|
|
static int leaveVblankEvent = -1;
|
|
static int afterFlipEvent = -1;
|
|
static int lagSyncEvent = -1;
|
|
|
|
static double lastLagSync = 0.0;
|
|
static bool lagSyncScheduled = false;
|
|
|
|
// hCount is computed now.
|
|
static int vCount;
|
|
// The "AccumulatedHcount" can be adjusted, this is the base.
|
|
static u32 hCountBase;
|
|
static int isVblank;
|
|
static int numSkippedFrames;
|
|
static bool hasSetMode;
|
|
static int resumeMode;
|
|
static int holdMode;
|
|
static int brightnessLevel;
|
|
static int mode;
|
|
static int width;
|
|
static int height;
|
|
static bool wasPaused;
|
|
|
|
// 1.001f to compensate for the classic 59.94 NTSC framerate that the PSP seems to have.
|
|
static const double timePerVblank = 1.001f / 60.0f;
|
|
|
|
// Don't include this in the state, time increases regardless of state.
|
|
static double curFrameTime;
|
|
static double lastFrameTime;
|
|
static double nextFrameTime;
|
|
static int numVBlanksSinceFlip;
|
|
|
|
static u64 frameStartTicks;
|
|
const int hCountPerVblank = 286;
|
|
|
|
const int PSP_DISPLAY_MODE_LCD = 0;
|
|
|
|
std::vector<WaitVBlankInfo> vblankWaitingThreads;
|
|
// Key is the callback id it was for, or if no callback, the thread id.
|
|
// Value is the goal vcount number (in case the callback takes >= 1 vcount to return.)
|
|
std::map<SceUID, int> vblankPausedWaits;
|
|
|
|
// STATE END
|
|
|
|
// Called when vblank happens (like an internal interrupt.) Not part of state, should be static.
|
|
std::vector<VblankCallback> vblankListeners;
|
|
|
|
// The vblank period is 731.5 us (0.7315 ms)
|
|
const double vblankMs = 0.7315;
|
|
const double frameMs = 1001.0 / 60.0;
|
|
|
|
enum {
|
|
PSP_DISPLAY_SETBUF_IMMEDIATE = 0,
|
|
PSP_DISPLAY_SETBUF_NEXTFRAME = 1
|
|
};
|
|
|
|
static int lastFpsFrame = 0;
|
|
static double lastFpsTime = 0.0;
|
|
static double fps = 0.0;
|
|
static double fpsHistory[120];
|
|
static size_t fpsHistoryPos = 0;
|
|
static size_t fpsHistoryValid = 0;
|
|
static int lastNumFlips = 0;
|
|
static float flips = 0.0f;
|
|
static int actualFlips = 0; // taking frameskip into account
|
|
static int lastActualFlips = 0;
|
|
static float actualFps = 0;
|
|
static u64 lastFlipCycles = 0;
|
|
// For the "max 60 fps" setting.
|
|
static int lastFlipsTooFrequent = 0;
|
|
|
|
void hleEnterVblank(u64 userdata, int cyclesLate);
|
|
void hleLeaveVblank(u64 userdata, int cyclesLate);
|
|
void hleAfterFlip(u64 userdata, int cyclesLate);
|
|
void hleLagSync(u64 userdata, int cyclesLate);
|
|
|
|
void __DisplayVblankBeginCallback(SceUID threadID, SceUID prevCallbackId);
|
|
void __DisplayVblankEndCallback(SceUID threadID, SceUID prevCallbackId);
|
|
int __DisplayGetFlipCount() { return actualFlips; }
|
|
int __DisplayGetVCount() { return vCount; }
|
|
|
|
static void ScheduleLagSync(int over = 0) {
|
|
lagSyncScheduled = g_Config.bForceLagSync;
|
|
if (lagSyncScheduled) {
|
|
CoreTiming::ScheduleEvent(usToCycles(1000 + over), lagSyncEvent, 0);
|
|
lastLagSync = real_time_now();
|
|
}
|
|
}
|
|
|
|
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 __DisplayDoState(PointerWrap &p) {
|
|
auto s = p.Section("sceDisplay", 1, 5);
|
|
if (!s)
|
|
return;
|
|
|
|
p.Do(framebuf);
|
|
p.Do(latchedFramebuf);
|
|
p.Do(framebufIsLatched);
|
|
p.Do(frameStartTicks);
|
|
p.Do(vCount);
|
|
if (s <= 2) {
|
|
double oldHCountBase;
|
|
p.Do(oldHCountBase);
|
|
hCountBase = (int) oldHCountBase;
|
|
} else {
|
|
p.Do(hCountBase);
|
|
}
|
|
p.Do(isVblank);
|
|
p.Do(hasSetMode);
|
|
p.Do(mode);
|
|
p.Do(resumeMode);
|
|
p.Do(holdMode);
|
|
if (s >= 4) {
|
|
p.Do(brightnessLevel);
|
|
}
|
|
p.Do(width);
|
|
p.Do(height);
|
|
WaitVBlankInfo wvi(0);
|
|
p.Do(vblankWaitingThreads, wvi);
|
|
p.Do(vblankPausedWaits);
|
|
|
|
p.Do(enterVblankEvent);
|
|
CoreTiming::RestoreRegisterEvent(enterVblankEvent, "EnterVBlank", &hleEnterVblank);
|
|
p.Do(leaveVblankEvent);
|
|
CoreTiming::RestoreRegisterEvent(leaveVblankEvent, "LeaveVBlank", &hleLeaveVblank);
|
|
p.Do(afterFlipEvent);
|
|
CoreTiming::RestoreRegisterEvent(afterFlipEvent, "AfterFlip", &hleAfterFlip);
|
|
|
|
if (s >= 5) {
|
|
p.Do(lagSyncEvent);
|
|
p.Do(lagSyncScheduled);
|
|
CoreTiming::RestoreRegisterEvent(lagSyncEvent, "LagSync", &hleLagSync);
|
|
lastLagSync = real_time_now();
|
|
if (lagSyncScheduled != g_Config.bForceLagSync) {
|
|
ScheduleLagSync();
|
|
}
|
|
} else {
|
|
lagSyncEvent = CoreTiming::RegisterEvent("LagSync", &hleLagSync);
|
|
ScheduleLagSync();
|
|
}
|
|
|
|
p.Do(gstate);
|
|
gstate_c.DoState(p);
|
|
#ifndef _XBOX
|
|
if (s < 2) {
|
|
// This shouldn't have been savestated anyway, but it was.
|
|
// It's unlikely to overlap with the first value in gpuStats.
|
|
p.ExpectVoid(&gl_extensions.gpuVendor, sizeof(gl_extensions.gpuVendor));
|
|
}
|
|
#endif
|
|
p.Do(gpuStats);
|
|
gpu->DoState(p);
|
|
|
|
ReapplyGfxState();
|
|
|
|
if (p.mode == p.MODE_READ) {
|
|
if (hasSetMode) {
|
|
gpu->InitClear();
|
|
}
|
|
gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat);
|
|
}
|
|
}
|
|
|
|
void __DisplayShutdown() {
|
|
vblankListeners.clear();
|
|
vblankWaitingThreads.clear();
|
|
ShutdownGfxState();
|
|
}
|
|
|
|
void __DisplayListenVblank(VblankCallback callback) {
|
|
vblankListeners.push_back(callback);
|
|
}
|
|
|
|
void __DisplayFireVblank() {
|
|
for (std::vector<VblankCallback>::iterator iter = vblankListeners.begin(), end = vblankListeners.end(); iter != end; ++iter) {
|
|
VblankCallback cb = *iter;
|
|
cb();
|
|
}
|
|
}
|
|
|
|
void __DisplayVblankBeginCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
|
|
|
|
// This means two callbacks in a row. PSP crashes if the same callback waits inside itself (may need more testing.)
|
|
// TODO: Handle this better?
|
|
if (vblankPausedWaits.find(pauseKey) != vblankPausedWaits.end()) {
|
|
return;
|
|
}
|
|
|
|
WaitVBlankInfo waitData(0);
|
|
for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
|
|
WaitVBlankInfo *t = &vblankWaitingThreads[i];
|
|
if (t->threadID == threadID)
|
|
{
|
|
waitData = *t;
|
|
vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (waitData.threadID != threadID)
|
|
{
|
|
WARN_LOG_REPORT(SCEDISPLAY, "sceDisplayWaitVblankCB: could not find waiting thread info.");
|
|
return;
|
|
}
|
|
|
|
vblankPausedWaits[pauseKey] = vCount + waitData.vcountUnblock;
|
|
DEBUG_LOG(SCEDISPLAY, "sceDisplayWaitVblankCB: Suspending vblank wait for callback")
|
|
}
|
|
|
|
void __DisplayVblankEndCallback(SceUID threadID, SceUID prevCallbackId) {
|
|
SceUID pauseKey = prevCallbackId == 0 ? threadID : prevCallbackId;
|
|
|
|
// Probably should not be possible.
|
|
if (vblankPausedWaits.find(pauseKey) == vblankPausedWaits.end()) {
|
|
__KernelResumeThreadFromWait(threadID, 0);
|
|
return;
|
|
}
|
|
|
|
int vcountUnblock = vblankPausedWaits[pauseKey];
|
|
vblankPausedWaits.erase(pauseKey);
|
|
if (vcountUnblock <= vCount) {
|
|
__KernelResumeThreadFromWait(threadID, 0);
|
|
return;
|
|
}
|
|
|
|
// Still have to wait a bit longer.
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vcountUnblock - vCount));
|
|
DEBUG_LOG(SCEDISPLAY, "sceDisplayWaitVblankCB: Resuming vblank wait from callback")
|
|
}
|
|
|
|
// TODO: Also average actualFps
|
|
void __DisplayGetFPS(float *out_vps, float *out_fps, float *out_actual_fps) {
|
|
*out_vps = fps;
|
|
*out_fps = flips;
|
|
*out_actual_fps = actualFps;
|
|
}
|
|
|
|
void __DisplayGetVPS(float *out_vps) {
|
|
*out_vps = fps;
|
|
}
|
|
|
|
void __DisplayGetAveragedFPS(float *out_vps, float *out_fps) {
|
|
float avg = 0.0;
|
|
if (fpsHistoryValid > 0) {
|
|
if (fpsHistoryValid > ARRAY_SIZE(fpsHistory)) {
|
|
fpsHistoryValid = ARRAY_SIZE(fpsHistory);
|
|
}
|
|
for (size_t i = 0; i < fpsHistoryValid; ++i) {
|
|
avg += fpsHistory[i];
|
|
}
|
|
avg /= (double) fpsHistoryValid;
|
|
}
|
|
|
|
*out_vps = *out_fps = avg;
|
|
}
|
|
|
|
void CalculateFPS() {
|
|
time_update();
|
|
double now = time_now_d();
|
|
|
|
if (now >= lastFpsTime + 1.0) {
|
|
double frames = (gpuStats.numVBlanks - lastFpsFrame);
|
|
actualFps = (actualFlips - lastActualFlips);
|
|
|
|
fps = frames / (now - lastFpsTime);
|
|
flips = 60.0 * (double) (gpuStats.numFlips - lastNumFlips) / frames;
|
|
|
|
lastFpsFrame = gpuStats.numVBlanks;
|
|
lastNumFlips = gpuStats.numFlips;
|
|
lastActualFlips = actualFlips;
|
|
lastFpsTime = now;
|
|
|
|
fpsHistory[fpsHistoryPos++] = fps;
|
|
fpsHistoryPos = fpsHistoryPos % ARRAY_SIZE(fpsHistory);
|
|
++fpsHistoryValid;
|
|
}
|
|
}
|
|
|
|
void __DisplayGetDebugStats(char stats[2048]) {
|
|
gpu->UpdateStats();
|
|
|
|
float vertexAverageCycles = gpuStats.numVertsSubmitted > 0 ? (float)gpuStats.vertexGPUCycles / (float)gpuStats.numVertsSubmitted : 0.0f;
|
|
|
|
snprintf(stats, 2047,
|
|
"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"
|
|
"Alpha Tested draws: %i\n"
|
|
"Non Alpha Tested draws: %i\n"
|
|
"Num Tracked Vertex Arrays: %i\n"
|
|
"Cycles executed: %d (%f per vertex)\n"
|
|
"Commands per call level: %i %i %i %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.numVBlanks,
|
|
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.numAlphaTestedDraws,
|
|
gpuStats.numNonAlphaTestedDraws,
|
|
gpuStats.numTrackedVertexArrays,
|
|
gpuStats.vertexGPUCycles + gpuStats.otherGPUCycles,
|
|
vertexAverageCycles,
|
|
gpuStats.gpuCommandsAtCallLevel[0],gpuStats.gpuCommandsAtCallLevel[1],gpuStats.gpuCommandsAtCallLevel[2],gpuStats.gpuCommandsAtCallLevel[3],
|
|
gpuStats.numVertsSubmitted,
|
|
gpuStats.numCachedVertsDrawn,
|
|
gpuStats.numUncachedVertsDrawn,
|
|
gpuStats.numFBOs,
|
|
gpuStats.numTextures,
|
|
gpuStats.numTexturesDecoded,
|
|
gpuStats.numTextureInvalidations,
|
|
gpuStats.numVertexShaders,
|
|
gpuStats.numFragmentShaders,
|
|
gpuStats.numShaders
|
|
);
|
|
stats[2047] = '\0';
|
|
gpuStats.ResetFrame();
|
|
kernelStats.ResetFrame();
|
|
}
|
|
|
|
enum {
|
|
FPS_LIMIT_NORMAL = 0,
|
|
FPS_LIMIT_CUSTOM = 1,
|
|
};
|
|
|
|
void __DisplaySetWasPaused() {
|
|
wasPaused = true;
|
|
}
|
|
|
|
static bool FrameTimingThrottled() {
|
|
if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit == 0) {
|
|
return false;
|
|
}
|
|
return !PSP_CoreParameter().unthrottle;
|
|
}
|
|
|
|
// Let's collect all the throttling and frameskipping logic here.
|
|
void DoFrameTiming(bool &throttle, bool &skipFrame, float timestep) {
|
|
int fpsLimiter = PSP_CoreParameter().fpsLimit;
|
|
throttle = FrameTimingThrottled();
|
|
skipFrame = false;
|
|
|
|
// Check if the frameskipping code should be enabled. If neither throttling or frameskipping is on,
|
|
// we have nothing to do here.
|
|
bool doFrameSkip = g_Config.iFrameSkip != 0;
|
|
|
|
if (!throttle && g_Config.bFrameSkipUnthrottle) {
|
|
doFrameSkip = true;
|
|
skipFrame = true;
|
|
if (numSkippedFrames >= 7) {
|
|
skipFrame = false;
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (!throttle && !doFrameSkip)
|
|
return;
|
|
|
|
time_update();
|
|
|
|
float scaledTimestep = timestep;
|
|
if (fpsLimiter == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit != 0) {
|
|
scaledTimestep *= 60.0f / g_Config.iFpsLimit;
|
|
}
|
|
|
|
if (lastFrameTime == 0.0 || wasPaused) {
|
|
nextFrameTime = time_now_d() + scaledTimestep;
|
|
if (wasPaused)
|
|
wasPaused = false;
|
|
} else {
|
|
// Advance lastFrameTime by a constant amount each frame,
|
|
// but don't let it get too far behind as things can get very jumpy.
|
|
const double maxFallBehindFrames = 5.5;
|
|
|
|
nextFrameTime = std::max(lastFrameTime + scaledTimestep, time_now_d() - maxFallBehindFrames * scaledTimestep);
|
|
}
|
|
curFrameTime = time_now_d();
|
|
|
|
// Auto-frameskip automatically if speed limit is set differently than the default.
|
|
if (g_Config.bAutoFrameSkip || (g_Config.iFrameSkip == 0 && fpsLimiter == FPS_LIMIT_CUSTOM && g_Config.iFpsLimit > 60)) {
|
|
// autoframeskip
|
|
// Argh, we are falling behind! Let's skip a frame and see if we catch up.
|
|
if (curFrameTime > nextFrameTime && doFrameSkip) {
|
|
skipFrame = true;
|
|
}
|
|
} else if (g_Config.iFrameSkip >= 1) {
|
|
// fixed frameskip
|
|
if (numSkippedFrames >= g_Config.iFrameSkip)
|
|
skipFrame = false;
|
|
else
|
|
skipFrame = true;
|
|
}
|
|
|
|
if (curFrameTime < nextFrameTime && throttle) {
|
|
// If time gap is huge just jump (somebody unthrottled)
|
|
if (nextFrameTime - curFrameTime > 2*scaledTimestep) {
|
|
nextFrameTime = curFrameTime;
|
|
} else {
|
|
// Wait until we've caught up.
|
|
while (time_now_d() < nextFrameTime) {
|
|
sleep_ms(1); // Sleep for 1ms on this thread
|
|
time_update();
|
|
}
|
|
}
|
|
curFrameTime = time_now_d();
|
|
}
|
|
|
|
lastFrameTime = nextFrameTime;
|
|
}
|
|
|
|
void DoFrameIdleTiming() {
|
|
if (!FrameTimingThrottled() || wasPaused) {
|
|
return;
|
|
}
|
|
|
|
time_update();
|
|
|
|
double dist = time_now_d() - lastFrameTime;
|
|
// Ignore if the distance is just crazy. May mean wrap or pause.
|
|
if (dist < 0.0 || dist >= 15 * timePerVblank) {
|
|
return;
|
|
}
|
|
|
|
float scaledVblank = timePerVblank;
|
|
if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM) {
|
|
// 0 is handled in FrameTimingThrottled().
|
|
scaledVblank *= 60.0f / g_Config.iFpsLimit;
|
|
}
|
|
|
|
// If we have over at least a vblank of spare time, maintain at least 30fps in delay.
|
|
// This prevents fast forward during loading screens.
|
|
const double thresh = lastFrameTime + (numVBlanksSinceFlip - 1) * scaledVblank;
|
|
if (numVBlanksSinceFlip >= 2 && time_now_d() < thresh) {
|
|
// Give a little extra wiggle room in case the next vblank does more work.
|
|
const double goal = lastFrameTime + numVBlanksSinceFlip * scaledVblank - 0.001;
|
|
while (time_now_d() < goal) {
|
|
sleep_ms(1);
|
|
time_update();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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;
|
|
bool wokeThreads = false;
|
|
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);
|
|
wokeThreads = true;
|
|
}
|
|
vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
|
|
}
|
|
}
|
|
if (wokeThreads) {
|
|
__KernelReSchedule("entered vblank");
|
|
}
|
|
|
|
// 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;
|
|
DoFrameTiming(throttle, skipFrame, (float)numVBlanksSinceFlip * timePerVblank);
|
|
|
|
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;
|
|
} else {
|
|
// Okay, there's no new frame to draw. But audio may be playing, so we need to time still.
|
|
DoFrameIdleTiming();
|
|
}
|
|
}
|
|
|
|
void hleAfterFlip(u64 userdata, int cyclesLate)
|
|
{
|
|
gpu->BeginFrame(); // doesn't really matter if begin or end of frame.
|
|
|
|
// This seems like as good a time as any to check if the config changed.
|
|
if (lagSyncScheduled != g_Config.bForceLagSync) {
|
|
ScheduleLagSync();
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// Fire the vblank listeners after the vblank completes.
|
|
__DisplayFireVblank();
|
|
}
|
|
|
|
void hleLagSync(u64 userdata, int cyclesLate) {
|
|
// The goal here is to prevent network, audio, and input lag from the real world.
|
|
// Our normal timing is very "stop and go". This is efficient, but causes real world lag.
|
|
// This event (optionally) runs every 1ms to sync with the real world.
|
|
|
|
if (!FrameTimingThrottled()) {
|
|
lagSyncScheduled = false;
|
|
return;
|
|
}
|
|
|
|
float scale = 1.0f;
|
|
if (PSP_CoreParameter().fpsLimit == FPS_LIMIT_CUSTOM) {
|
|
// 0 is handled in FrameTimingThrottled().
|
|
scale = 60.0f / g_Config.iFpsLimit;
|
|
}
|
|
|
|
const double goal = lastLagSync + (scale / 1000.0f);
|
|
time_update();
|
|
// Don't lag too long ever, if they leave it paused.
|
|
while (time_now_d() < goal && goal < time_now_d() + 0.01) {
|
|
const double left = goal - time_now_d();
|
|
#ifndef _WIN32
|
|
usleep((long)(left * 1000000));
|
|
#endif
|
|
time_update();
|
|
}
|
|
|
|
const int emuOver = (int)cyclesToUs(cyclesLate);
|
|
const int over = (int)((time_now_d() - goal) * 1000000);
|
|
ScheduleLagSync(over - emuOver);
|
|
}
|
|
|
|
u32 sceDisplayIsVblank() {
|
|
DEBUG_LOG(SCEDISPLAY,"%i=sceDisplayIsVblank()",isVblank);
|
|
return isVblank;
|
|
}
|
|
|
|
u32 sceDisplaySetMode(int displayMode, int displayWidth, int displayHeight) {
|
|
if (displayWidth <= 0 || displayHeight <= 0 || (displayWidth & 0x7) != 0) {
|
|
WARN_LOG(SCEDISPLAY, "sceDisplaySetMode INVALID SIZE (%i, %i, %i)", displayMode, displayWidth, displayHeight);
|
|
return SCE_KERNEL_ERROR_INVALID_SIZE;
|
|
}
|
|
|
|
if (displayMode != PSP_DISPLAY_MODE_LCD) {
|
|
WARN_LOG(SCEDISPLAY, "sceDisplaySetMode INVALID MODE(%i, %i, %i)", displayMode, displayWidth, displayHeight);
|
|
return SCE_KERNEL_ERROR_INVALID_MODE;
|
|
}
|
|
|
|
DEBUG_LOG(SCEDISPLAY,"sceDisplaySetMode(%i, %i, %i)", displayMode, displayWidth, displayHeight);
|
|
if (!hasSetMode) {
|
|
gpu->InitClear();
|
|
hasSetMode = true;
|
|
}
|
|
mode = displayMode;
|
|
width = displayWidth;
|
|
height = displayHeight;
|
|
return 0;
|
|
}
|
|
|
|
// Some games (GTA) never call this during gameplay, so bad place to put a framerate counter.
|
|
u32 sceDisplaySetFramebuf(u32 topaddr, int linesize, int pixelformat, int sync) {
|
|
FrameBufferState fbstate;
|
|
DEBUG_LOG(SCEDISPLAY,"sceDisplaySetFramebuf(topaddr=%08x,linesize=%d,pixelsize=%d,sync=%d)", topaddr, linesize, pixelformat, sync);
|
|
hleEatCycles(290);
|
|
if (topaddr == 0) {
|
|
DEBUG_LOG(SCEDISPLAY,"- screen off");
|
|
} else {
|
|
fbstate.topaddr = topaddr;
|
|
fbstate.pspFramebufFormat = (GEBufferFormat)pixelformat;
|
|
fbstate.pspFramebufLinesize = linesize;
|
|
}
|
|
|
|
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
|
|
if (topaddr != 0) {
|
|
framebuf = fbstate;
|
|
gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat);
|
|
} else {
|
|
WARN_LOG(SCEDISPLAY, "%s: PSP_DISPLAY_SETBUF_IMMEDIATE without topaddr?", __FUNCTION__);
|
|
}
|
|
} else if (topaddr != 0) {
|
|
// 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(0, "set framebuf", cyclesToUs(delayCycles));
|
|
} else {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
bool __DisplayGetFramebuf(u8 **topaddr, u32 *linesize, u32 *pixelFormat, int latchedMode) {
|
|
const FrameBufferState &fbState = latchedMode == 1 ? latchedFramebuf : framebuf;
|
|
if (topaddr != NULL)
|
|
*topaddr = Memory::GetPointer(fbState.topaddr);
|
|
if (linesize != NULL)
|
|
*linesize = fbState.pspFramebufLinesize;
|
|
if (pixelFormat != NULL)
|
|
*pixelFormat = fbState.pspFramebufFormat;
|
|
|
|
return true;
|
|
}
|
|
|
|
u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int latchedMode) {
|
|
const FrameBufferState &fbState = latchedMode == 1 && framebufIsLatched ? latchedFramebuf : framebuf;
|
|
DEBUG_LOG(SCEDISPLAY,"sceDisplayGetFramebuf(*%08x = %08x, *%08x = %08x, *%08x = %08x, %i)", topaddrPtr, fbState.topaddr, linesizePtr, fbState.pspFramebufLinesize, pixelFormatPtr, fbState.pspFramebufFormat, latchedMode);
|
|
|
|
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 0;
|
|
}
|
|
|
|
u32 sceDisplayWaitVblankStart() {
|
|
VERBOSE_LOG(SCEDISPLAY,"sceDisplayWaitVblankStart()");
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
|
|
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, false, "vblank start waited");
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplayWaitVblank() {
|
|
if (!isVblank) {
|
|
VERBOSE_LOG(SCEDISPLAY,"sceDisplayWaitVblank()");
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
|
|
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, false, "vblank waited");
|
|
return 0;
|
|
} else {
|
|
DEBUG_LOG(SCEDISPLAY,"sceDisplayWaitVblank() - not waiting since in vBlank");
|
|
hleEatCycles(1110);
|
|
hleReSchedule("vblank wait skipped");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
u32 sceDisplayWaitVblankStartMulti(int vblanks) {
|
|
if (vblanks <= 0) {
|
|
WARN_LOG(SCEDISPLAY, "sceDisplayWaitVblankStartMulti(%d): invalid number of vblanks", vblanks);
|
|
return SCE_KERNEL_ERROR_INVALID_VALUE;
|
|
}
|
|
VERBOSE_LOG(SCEDISPLAY, "sceDisplayWaitVblankStartMulti(%d)", vblanks);
|
|
if (!__KernelIsDispatchEnabled())
|
|
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
|
|
if (__IsInInterrupt())
|
|
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vblanks));
|
|
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, false, "vblank start multi waited");
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplayWaitVblankCB() {
|
|
if (!isVblank) {
|
|
VERBOSE_LOG(SCEDISPLAY,"sceDisplayWaitVblankCB()");
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
|
|
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, true, "vblank waited");
|
|
return 0;
|
|
} else {
|
|
DEBUG_LOG(SCEDISPLAY,"sceDisplayWaitVblankCB() - not waiting since in vBlank");
|
|
hleEatCycles(1110);
|
|
hleReSchedule("vblank wait skipped");
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
u32 sceDisplayWaitVblankStartCB() {
|
|
VERBOSE_LOG(SCEDISPLAY,"sceDisplayWaitVblankStartCB()");
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
|
|
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, true, "vblank start waited");
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplayWaitVblankStartMultiCB(int vblanks) {
|
|
if (vblanks <= 0) {
|
|
WARN_LOG(SCEDISPLAY, "sceDisplayWaitVblankStartMultiCB(%d): invalid number of vblanks", vblanks);
|
|
return SCE_KERNEL_ERROR_INVALID_VALUE;
|
|
}
|
|
VERBOSE_LOG(SCEDISPLAY,"sceDisplayWaitVblankStartMultiCB(%d)", vblanks);
|
|
if (!__KernelIsDispatchEnabled())
|
|
return SCE_KERNEL_ERROR_CAN_NOT_WAIT;
|
|
if (__IsInInterrupt())
|
|
return SCE_KERNEL_ERROR_ILLEGAL_CONTEXT;
|
|
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread(), vblanks));
|
|
__KernelWaitCurThread(WAITTYPE_VBLANK, 1, 0, 0, true, "vblank start multi waited");
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplayGetVcount() {
|
|
VERBOSE_LOG(SCEDISPLAY,"%i=sceDisplayGetVcount()", vCount);
|
|
|
|
hleEatCycles(150);
|
|
hleReSchedule("get vcount");
|
|
return vCount;
|
|
}
|
|
|
|
u32 __DisplayGetCurrentHcount() {
|
|
const static int ticksPerVblank333 = 333 * 1000000 / 60 / hCountPerVblank;
|
|
const int ticksIntoFrame = CoreTiming::GetTicks() - frameStartTicks;
|
|
// Can't seem to produce a 0 on real hardware, offsetting by 1 makes things look right.
|
|
return 1 + (ticksIntoFrame / (CoreTiming::GetClockFrequencyMHz() * ticksPerVblank333 / 333));
|
|
}
|
|
|
|
u32 __DisplayGetAccumulatedHcount() {
|
|
// The hCount is always a positive int, and wraps from 0x7FFFFFFF -> 0.
|
|
int value = hCountBase + __DisplayGetCurrentHcount();
|
|
return value & 0x7FFFFFFF;
|
|
}
|
|
|
|
u32 sceDisplayGetCurrentHcount() {
|
|
u32 currentHCount = __DisplayGetCurrentHcount();
|
|
DEBUG_LOG(SCEDISPLAY, "%i=sceDisplayGetCurrentHcount()", currentHCount);
|
|
hleEatCycles(275);
|
|
return currentHCount;
|
|
}
|
|
|
|
int sceDisplayAdjustAccumulatedHcount(int value) {
|
|
if (value < 0) {
|
|
ERROR_LOG_REPORT(SCEDISPLAY, "sceDisplayAdjustAccumulatedHcount(%d): invalid value", value);
|
|
return SCE_KERNEL_ERROR_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;
|
|
|
|
DEBUG_LOG(SCEDISPLAY, "sceDisplayAdjustAccumulatedHcount(%d)", value);
|
|
return 0;
|
|
}
|
|
|
|
int sceDisplayGetAccumulatedHcount() {
|
|
u32 accumHCount = __DisplayGetAccumulatedHcount();
|
|
DEBUG_LOG(SCEDISPLAY, "%d=sceDisplayGetAccumulatedHcount()", accumHCount);
|
|
hleEatCycles(235);
|
|
return accumHCount;
|
|
}
|
|
|
|
float sceDisplayGetFramePerSec() {
|
|
const static float framePerSec = 59.9400599f;
|
|
DEBUG_LOG(SCEDISPLAY,"%f=sceDisplayGetFramePerSec()", framePerSec);
|
|
return framePerSec; // (9MHz * 1)/(525 * 286)
|
|
}
|
|
|
|
u32 sceDisplayIsForeground() {
|
|
DEBUG_LOG(SCEDISPLAY,"IMPL sceDisplayIsForeground()");
|
|
if (!hasSetMode || framebuf.topaddr == 0)
|
|
return 0;
|
|
else
|
|
return 1; // return value according to JPCSP comment
|
|
}
|
|
|
|
u32 sceDisplayGetMode(u32 modeAddr, u32 widthAddr, u32 heightAddr) {
|
|
DEBUG_LOG(SCEDISPLAY,"sceDisplayGetMode(%08x, %08x, %08x)", modeAddr, widthAddr, 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 0;
|
|
}
|
|
|
|
u32 sceDisplayIsVsync() {
|
|
ERROR_LOG(SCEDISPLAY,"UNIMPL sceDisplayIsVsync()");
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplayGetResumeMode(u32 resumeModeAddr) {
|
|
ERROR_LOG(SCEDISPLAY,"sceDisplayGetResumeMode(%08x)", resumeModeAddr);
|
|
if (Memory::IsValidAddress(resumeModeAddr))
|
|
Memory::Write_U32(resumeMode, resumeModeAddr);
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplaySetResumeMode(u32 rMode) {
|
|
ERROR_LOG(SCEDISPLAY,"sceDisplaySetResumeMode(%08x)", rMode);
|
|
resumeMode = rMode;
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplayGetBrightness(u32 levelAddr) {
|
|
ERROR_LOG(SCEDISPLAY,"sceDisplayGetBrightness(%08x)", levelAddr);
|
|
if (Memory::IsValidAddress(levelAddr))
|
|
Memory::Write_U32(brightnessLevel, levelAddr);
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplaySetBrightness(u32 bLevel) {
|
|
ERROR_LOG(SCEDISPLAY,"sceDisplaySetBrightness(%08x)", bLevel);
|
|
brightnessLevel = bLevel;
|
|
return 0;
|
|
}
|
|
|
|
u32 sceDisplaySetHoldMode(u32 hMode) {
|
|
ERROR_LOG(SCEDISPLAY,"sceDisplaySetHoldMode(%08x)", hMode);
|
|
holdMode = hMode;
|
|
return 0;
|
|
}
|
|
|
|
const HLEFunction sceDisplay[] = {
|
|
{0x0E20F177,WrapU_III<sceDisplaySetMode>, "sceDisplaySetMode"},
|
|
{0x289D82FE,WrapU_UIII<sceDisplaySetFramebuf>, "sceDisplaySetFrameBuf"},
|
|
{0xEEDA2E54,WrapU_UUUI<sceDisplayGetFramebuf>,"sceDisplayGetFrameBuf"},
|
|
{0x36CDFADE,WrapU_V<sceDisplayWaitVblank>, "sceDisplayWaitVblank", HLE_NOT_DISPATCH_SUSPENDED},
|
|
{0x984C27E7,WrapU_V<sceDisplayWaitVblankStart>, "sceDisplayWaitVblankStart", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED},
|
|
{0x40f1469c,WrapU_I<sceDisplayWaitVblankStartMulti>, "sceDisplayWaitVblankStartMulti"},
|
|
{0x8EB9EC49,WrapU_V<sceDisplayWaitVblankCB>, "sceDisplayWaitVblankCB", HLE_NOT_DISPATCH_SUSPENDED},
|
|
{0x46F186C3,WrapU_V<sceDisplayWaitVblankStartCB>, "sceDisplayWaitVblankStartCB", HLE_NOT_IN_INTERRUPT | HLE_NOT_DISPATCH_SUSPENDED},
|
|
{0x77ed8b3a,WrapU_I<sceDisplayWaitVblankStartMultiCB>,"sceDisplayWaitVblankStartMultiCB"},
|
|
{0xdba6c4c4,WrapF_V<sceDisplayGetFramePerSec>,"sceDisplayGetFramePerSec"},
|
|
{0x773dd3a3,WrapU_V<sceDisplayGetCurrentHcount>,"sceDisplayGetCurrentHcount"},
|
|
{0x210eab3a,WrapI_V<sceDisplayGetAccumulatedHcount>,"sceDisplayGetAccumulatedHcount"},
|
|
{0xA83EF139,WrapI_I<sceDisplayAdjustAccumulatedHcount>,"sceDisplayAdjustAccumulatedHcount"},
|
|
{0x9C6EAAD7,WrapU_V<sceDisplayGetVcount>,"sceDisplayGetVcount"},
|
|
{0xDEA197D4,WrapU_UUU<sceDisplayGetMode>,"sceDisplayGetMode"},
|
|
{0x7ED59BC4,WrapU_U<sceDisplaySetHoldMode>,"sceDisplaySetHoldMode"},
|
|
{0xA544C486,WrapU_U<sceDisplaySetResumeMode>,"sceDisplaySetResumeMode"},
|
|
{0xBF79F646,WrapU_U<sceDisplayGetResumeMode>,"sceDisplayGetResumeMode"},
|
|
{0xB4F378FA,WrapU_V<sceDisplayIsForeground>,"sceDisplayIsForeground"},
|
|
{0x31C4BAA8,WrapU_U<sceDisplayGetBrightness>,"sceDisplayGetBrightness"},
|
|
{0x9E3C6DC6,WrapU_U<sceDisplaySetBrightness>,"sceDisplaySetBrightness"},
|
|
{0x4D4E10EC,WrapU_V<sceDisplayIsVblank>,"sceDisplayIsVblank"},
|
|
{0x21038913,WrapU_V<sceDisplayIsVsync>,"sceDisplayIsVsync"},
|
|
};
|
|
|
|
void Register_sceDisplay() {
|
|
RegisterModule("sceDisplay", ARRAY_SIZE(sceDisplay), sceDisplay);
|
|
}
|
|
|
|
void Register_sceDisplay_driver() {
|
|
RegisterModule("sceDisplay_driver", ARRAY_SIZE(sceDisplay), sceDisplay);
|
|
}
|