ppsspp/Core/HLE/sceDisplay.cpp

421 lines
12 KiB
C++
Raw Normal View History

2012-11-01 15:19:01 +00:00
// 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.
2012-11-01 15:19:01 +00:00
// 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 <cmath>
2012-11-01 15:19:01 +00:00
// 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/timeutil.h"
2012-11-01 15:19:01 +00:00
#include "Thread.h"
2012-11-01 15:19:01 +00:00
#include "../Core/CoreTiming.h"
#include "../MIPS/MIPS.h"
#include "../HLE/HLE.h"
#include "sceAudio.h"
#include "../Host.h"
#include "../Config.h"
2012-11-19 20:23:29 +00:00
#include "../System.h"
#include "../Core/Core.h"
2012-11-01 15:19:01 +00:00
#include "sceDisplay.h"
#include "sceKernel.h"
#include "sceKernelThread.h"
#include "sceKernelInterrupt.h"
// TODO: This file should not depend directly on GLES code.
#include "../../GPU/GLES/Framebuffer.h"
#include "../../GPU/GLES/ShaderManager.h"
#include "../../GPU/GLES/TextureCache.h"
2012-11-01 15:19:01 +00:00
#include "../../GPU/GPUState.h"
#include "../../GPU/GPUInterface.h"
// Internal drawing library
#include "../Util/PPGeDraw.h"
2012-11-01 15:19:01 +00:00
extern ShaderManager shaderManager;
struct FrameBufferState
{
u32 topaddr;
PspDisplayPixelFormat pspFramebufFormat;
int pspFramebufLinesize;
};
// STATE BEGIN
static FrameBufferState framebuf;
static FrameBufferState latchedFramebuf;
static bool framebufIsLatched;
static int enterVblankEvent = -1;
static int leaveVblankEvent = -1;
static int hCount = 0;
static int hCountTotal = 0; //unused
2012-11-01 15:19:01 +00:00
static int vCount = 0;
static int isVblank = 0;
2012-11-19 23:31:19 +00:00
static bool hasSetMode = false;
double lastFrameTime = 0;
2012-11-01 15:19:01 +00:00
// STATE END
std::vector<VblankCallback> vblankListeners;
2012-11-01 15:19:01 +00:00
// The vblank period is 731.5 us (0.7315 ms)
const double vblankMs = 0.7315;
const double frameMs = 1000.0 / 60.0;
enum {
PSP_DISPLAY_SETBUF_IMMEDIATE = 0,
2012-11-01 15:19:01 +00:00
PSP_DISPLAY_SETBUF_NEXTFRAME = 1
};
struct WaitVBlankInfo
{
WaitVBlankInfo(u32 tid) : threadID(tid), vcountUnblock(0) {}
2012-11-01 15:19:01 +00:00
u32 threadID;
int vcountUnblock; // what was this for again?
2012-11-01 15:19:01 +00:00
};
std::vector<WaitVBlankInfo> vblankWaitingThreads;
void hleEnterVblank(u64 userdata, int cyclesLate);
void hleLeaveVblank(u64 userdata, int cyclesLate);
void __DisplayInit()
{
gpuStats.reset();
2012-11-19 23:31:19 +00:00
hasSetMode = false;
2012-11-01 15:19:01 +00:00
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;
InitGfxState();
}
2012-11-06 16:05:27 +00:00
void __DisplayShutdown()
{
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();
}
}
2012-11-01 15:19:01 +00:00
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();
2012-11-01 15:19:01 +00:00
// Wake up threads waiting for VBlank
for (int i = 0; i < vblankWaitingThreads.size(); i++) {
__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
}
vblankWaitingThreads.clear();
2012-11-01 15:19:01 +00:00
// 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)
2012-11-01 15:19:01 +00:00
{
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);
2012-11-01 15:19:01 +00:00
}
// 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)
2012-11-18 16:51:14 +00:00
{
gpu->UpdateStats();
2012-11-18 16:51:14 +00:00
char stats[512];
sprintf(stats,
"Frames: %i\n"
"Draw calls: %i\n"
"Vertices Transformed: %i\n"
"Textures active: %i\n"
"Vertex shaders loaded: %i\n"
"Fragment shaders loaded: %i\n"
"Combined shaders loaded: %i\n",
gpuStats.numFrames,
gpuStats.numDrawCalls,
gpuStats.numVertsTransformed,
gpuStats.numTextures,
gpuStats.numVertexShaders,
gpuStats.numFragmentShaders,
gpuStats.numShaders
);
float zoom = 0.7f * sqrtf(g_Config.iWindowZoom);
PPGeBegin();
PPGeDrawText(stats, 2, 2, 0, zoom, 0x90000000);
PPGeDrawText(stats, 0, 0, 0, zoom);
PPGeEnd();
gpuStats.resetFrame();
}
2012-11-18 16:51:14 +00:00
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.0f) {
Common::SleepCurrentThread(1);
time_update();
}
lastFrameTime = time_now_d();
}
#endif
host->BeginFrame();
gpu->BeginFrame();
shaderManager.DirtyShader();
shaderManager.DirtyUniform(DIRTY_ALL);
2012-11-01 15:19:01 +00:00
// Tell the emu core that it's time to stop emulating
// Win32 doesn't need this.
#ifndef _WIN32
coreState = CORE_NEXTFRAME;
#endif
2012-11-01 15:19:01 +00:00
}
void hleLeaveVblank(u64 userdata, int cyclesLate)
{
isVblank = 0;
2012-11-19 23:31:19 +00:00
DEBUG_LOG(HLE,"Leave VBlank %i", (int)userdata - 1);
2012-11-01 15:19:01 +00:00
vCount++;
hCount = 0;
CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs) - cyclesLate, enterVblankEvent, userdata);
}
void sceDisplayIsVblank()
2012-11-01 15:19:01 +00:00
{
DEBUG_LOG(HLE,"%i=sceDisplayIsVblank()",isVblank);
RETURN(isVblank);
2012-11-01 15:19:01 +00:00
}
u32 sceDisplaySetMode(u32 unknown, u32 xres, u32 yres)
{
DEBUG_LOG(HLE,"sceDisplaySetMode(%d,%d,%d)",unknown,xres,yres);
host->BeginFrame();
2012-11-19 23:31:19 +00:00
if (!hasSetMode)
{
gpu->InitClear();
2012-11-19 23:31:19 +00:00
hasSetMode = true;
}
2012-11-01 15:19:01 +00:00
return 0;
}
void sceDisplaySetFramebuf()
2012-11-01 15:19:01 +00:00
{
//host->EndFrame();
u32 topaddr = PARAM(0);
int linesize = PARAM(1);
int pixelformat = PARAM(2);
int sync = PARAM(3);
2012-11-19 20:23:29 +00:00
FrameBufferState fbstate;
DEBUG_LOG(HLE,"sceDisplaySetFramebuf(topaddr=%08x,linesize=%d,pixelsize=%d,sync=%d)",topaddr,linesize,pixelformat,sync);
if (topaddr == 0)
2012-11-01 15:19:01 +00:00
{
2012-11-19 20:23:29 +00:00
DEBUG_LOG(HLE,"- screen off");
2012-11-01 15:19:01 +00:00
}
2012-11-19 20:23:29 +00:00
else
2012-11-01 15:19:01 +00:00
{
2012-11-19 20:23:29 +00:00
fbstate.topaddr = topaddr;
fbstate.pspFramebufFormat = (PspDisplayPixelFormat)pixelformat;
fbstate.pspFramebufLinesize = linesize;
2012-11-01 15:19:01 +00:00
}
2012-11-19 20:23:29 +00:00
if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE)
2012-11-01 15:19:01 +00:00
{
2012-11-19 20:23:29 +00:00
// Write immediately to the current framebuffer parameters
framebuf = fbstate;
gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat);
2012-11-01 15:19:01 +00:00
}
2012-11-19 20:23:29 +00:00
else if (topaddr != 0)
2012-11-01 15:19:01 +00:00
{
2012-11-19 20:23:29 +00:00
// Delay the write until vblank
latchedFramebuf = fbstate;
framebufIsLatched = true;
2012-11-01 15:19:01 +00:00
}
RETURN(0);
2012-11-01 15:19:01 +00:00
}
u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int mode)
2012-11-01 15:19:01 +00:00
{
const FrameBufferState &fbState = mode == 1 ? latchedFramebuf : framebuf;
2012-11-19 23:31:19 +00:00
DEBUG_LOG(HLE,"sceDisplayGetFramebuf(*%08x = %08x, *%08x = %08x, *%08x = %08x, %i)",
topaddrPtr, fbState.topaddr, linesizePtr, fbState.pspFramebufLinesize, pixelFormatPtr, fbState.pspFramebufFormat, mode);
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;
2012-11-01 15:19:01 +00:00
}
void sceDisplayWaitVblankStart()
2012-11-01 15:19:01 +00:00
{
DEBUG_LOG(HLE,"sceDisplayWaitVblankStart()");
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
2012-11-01 15:19:01 +00:00
__KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, false);
}
void sceDisplayWaitVblank()
2012-11-01 15:19:01 +00:00
{
DEBUG_LOG(HLE,"sceDisplayWaitVblank()");
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
2012-11-01 15:19:01 +00:00
__KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, false);
}
void sceDisplayWaitVblankStartMulti()
{
DEBUG_LOG(HLE,"sceDisplayWaitVblankStartMulti()");
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
__KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, false);
}
2012-11-08 15:28:45 +00:00
void sceDisplayWaitVblankCB()
{
DEBUG_LOG(HLE,"sceDisplayWaitVblankCB()");
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
2012-11-08 15:28:45 +00:00
__KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true);
}
void sceDisplayWaitVblankStartCB()
2012-11-01 15:19:01 +00:00
{
DEBUG_LOG(HLE,"sceDisplayWaitVblankStartCB()");
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
2012-11-01 15:19:01 +00:00
__KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true);
}
void sceDisplayWaitVblankStartMultiCB()
{
DEBUG_LOG(HLE,"sceDisplayWaitVblankStartMultiCB()");
vblankWaitingThreads.push_back(WaitVBlankInfo(__KernelGetCurThread()));
__KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true);
}
void sceDisplayGetVcount()
2012-11-01 15:19:01 +00:00
{
// Too spammy
// DEBUG_LOG(HLE,"%i=sceDisplayGetVcount()", vCount);
// Games like Puyo Puyo call this in a tight loop at end-of-frame. We could have it consume some time from CoreTiming?
CoreTiming::Idle(1000000);
RETURN(vCount);
2012-11-01 15:19:01 +00:00
}
void sceDisplayGetCurrentHcount()
2012-11-01 15:19:01 +00:00
{
RETURN(hCount++);
2012-11-01 15:19:01 +00:00
}
void sceDisplayGetAccumulatedHcount()
2012-11-01 15:19:01 +00:00
{
// Just do an estimate
u32 accumHCount = CoreTiming::GetTicks() / (222000000 / 60 / 272);
DEBUG_LOG(HLE,"%i=sceDisplayGetAccumulatedHcount()", accumHCount);
RETURN(accumHCount);
2012-11-01 15:19:01 +00:00
}
float sceDisplayGetFramePerSec()
{
float fps = 59.9400599f;
DEBUG_LOG(HLE,"%f=sceDisplayGetFramePerSec()", fps);
return fps; // (9MHz * 1)/(525 * 286)
}
const HLEFunction sceDisplay[] =
{
2012-11-06 17:14:09 +00:00
{0x0E20F177,&WrapU_UUU<sceDisplaySetMode>, "sceDisplaySetMode"},
{0x289D82FE,sceDisplaySetFramebuf, "sceDisplaySetFramebuf"},
{0xEEDA2E54,&WrapU_UUUI<sceDisplayGetFramebuf>,"sceDisplayGetFrameBuf"},
2012-11-06 17:14:09 +00:00
{0x36CDFADE,sceDisplayWaitVblank, "sceDisplayWaitVblank"},
{0x984C27E7,sceDisplayWaitVblankStart, "sceDisplayWaitVblankStart"},
{0x40f1469c,sceDisplayWaitVblankStartMulti, "sceDisplayWaitVblankStartMulti"},
2012-11-08 15:28:45 +00:00
{0x8EB9EC49,sceDisplayWaitVblankCB, "sceDisplayWaitVblankCB"},
2012-11-06 17:14:09 +00:00
{0x46F186C3,sceDisplayWaitVblankStartCB, "sceDisplayWaitVblankStartCB"},
{0x77ed8b3a,sceDisplayWaitVblankStartMultiCB,"sceDisplayWaitVblankStartMultiCB"},
{0xdba6c4c4,&WrapF_V<sceDisplayGetFramePerSec>,"sceDisplayGetFramePerSec"},
{0x773dd3a3,sceDisplayGetCurrentHcount,"sceDisplayGetCurrentHcount"},
{0x210eab3a,sceDisplayGetAccumulatedHcount,"sceDisplayGetAccumulatedHcount"},
{0x9C6EAAD7,sceDisplayGetVcount,"sceDisplayGetVcount"},
2012-11-01 15:19:01 +00:00
{0xDEA197D4,0,"sceDisplayGetMode"},
{0x7ED59BC4,0,"sceDisplaySetHoldMode"},
{0xA544C486,0,"sceDisplaySetResumeMode"},
{0xB4F378FA,0,"sceDisplayIsForeground"},
{0x31C4BAA8,0,"sceDisplayGetBrightness"},
{0x4D4E10EC,sceDisplayIsVblank,"sceDisplayIsVblank"},
2012-11-01 15:19:01 +00:00
};
void Register_sceDisplay()
{
RegisterModule("sceDisplay", ARRAY_SIZE(sceDisplay), sceDisplay);
}