// 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 #include // 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" #include "Thread.h" #include "../Core/CoreTiming.h" #include "../MIPS/MIPS.h" #include "../HLE/HLE.h" #include "sceAudio.h" #include "../Host.h" #include "../Config.h" #include "../System.h" #include "../Core/Core.h" #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" #include "../../GPU/GPUState.h" #include "../../GPU/GPUInterface.h" // Internal drawing library #include "../Util/PPGeDraw.h" 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 static int vCount = 0; static int isVblank = 0; static bool hasSetMode = false; double lastFrameTime = 0; // STATE END std::vector vblankListeners; // 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, PSP_DISPLAY_SETBUF_NEXTFRAME = 1 }; struct WaitVBlankInfo { u32 threadID; int vcountUnblock; }; std::vector vblankWaitingThreads; void hleEnterVblank(u64 userdata, int cyclesLate); void hleLeaveVblank(u64 userdata, int cyclesLate); void __DisplayInit() { gpuStats.reset(); hasSetMode = false; framebufIsLatched = false; framebuf.topaddr = 0x04000000; framebuf.pspFramebufFormat = PSP_DISPLAY_PIXEL_FORMAT_8888; framebuf.pspFramebufLinesize = 480; // ?? enterVblankEvent = CoreTiming::RegisterEvent("EnterVBlank", &hleEnterVblank); leaveVblankEvent = CoreTiming::RegisterEvent("LeaveVBlank", &hleLeaveVblank); CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs), enterVblankEvent, 0); isVblank = 0; vCount = 0; InitGfxState(); } void __DisplayShutdown() { ShutdownGfxState(); } void __DisplayListenVblank(VblankCallback callback) { vblankListeners.push_back(callback); } void __DisplayFireVblank() { for (std::vector::iterator iter = vblankListeners.begin(), end = vblankListeners.end(); iter != end; ++iter) { VblankCallback cb = *iter; cb(); } } void hleEnterVblank(u64 userdata, int cyclesLate) { int vbCount = userdata; DEBUG_LOG(HLE, "Enter VBlank %i", vbCount); isVblank = 1; // Fire the vblank listeners before we wake threads. __DisplayFireVblank(); // Wake up threads waiting for VBlank __KernelTriggerWait(WAITTYPE_VBLANK, 0, true); // Trigger VBlank interrupt handlers. __TriggerInterrupt(PSP_VBLANK_INTR); CoreTiming::ScheduleEvent(msToCycles(vblankMs) - cyclesLate, leaveVblankEvent, vbCount+1); // TODO: Should this be done here or in hleLeaveVblank? if (framebufIsLatched) { DEBUG_LOG(HLE, "Setting latched framebuffer %08x (prev: %08x)", latchedFramebuf.topaddr, framebuf.topaddr); framebuf = latchedFramebuf; framebufIsLatched = false; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } // Draw screen overlays before blitting. Saves and restores the Ge context. gpuStats.numFrames++; // Yeah, this has to be the right moment to end the frame. Give the graphics backend opportunity // to blit the framebuffer, in order to support half-framerate games that otherwise wouldn't have // anything to draw here. gpu->CopyDisplayToOutput(); // Now we can subvert the Ge engine in order to draw custom overlays like stat counters etc. // Here we will be drawing to the non buffered front surface. if (g_Config.bShowDebugStats) { gpu->UpdateStats(); 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(); } 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); // Tell the emu core that it's time to stop emulating // Win32 doesn't need this. #ifndef _WIN32 coreState = CORE_NEXTFRAME; #endif } void hleLeaveVblank(u64 userdata, int cyclesLate) { isVblank = 0; DEBUG_LOG(HLE,"Leave VBlank %i", (int)userdata - 1); vCount++; hCount = 0; CoreTiming::ScheduleEvent(msToCycles(frameMs - vblankMs) - cyclesLate, enterVblankEvent, userdata); } void sceDisplayIsVblank() { DEBUG_LOG(HLE,"%i=sceDisplayIsVblank()",isVblank); RETURN(isVblank); } u32 sceDisplaySetMode(u32 unknown, u32 xres, u32 yres) { DEBUG_LOG(HLE,"sceDisplaySetMode(%d,%d,%d)",unknown,xres,yres); host->BeginFrame(); if (!hasSetMode) { gpu->InitClear(); hasSetMode = true; } return 0; } void sceDisplaySetFramebuf() { //host->EndFrame(); u32 topaddr = PARAM(0); int linesize = PARAM(1); int pixelformat = PARAM(2); int sync = PARAM(3); FrameBufferState fbstate; DEBUG_LOG(HLE,"sceDisplaySetFramebuf(topaddr=%08x,linesize=%d,pixelsize=%d,sync=%d)",topaddr,linesize,pixelformat,sync); if (topaddr == 0) { DEBUG_LOG(HLE,"- screen off"); } else { fbstate.topaddr = topaddr; fbstate.pspFramebufFormat = (PspDisplayPixelFormat)pixelformat; fbstate.pspFramebufLinesize = linesize; } if (sync == PSP_DISPLAY_SETBUF_IMMEDIATE) { // Write immediately to the current framebuffer parameters framebuf = fbstate; gpu->SetDisplayFramebuffer(framebuf.topaddr, framebuf.pspFramebufLinesize, framebuf.pspFramebufFormat); } else if (topaddr != 0) { // Delay the write until vblank latchedFramebuf = fbstate; framebufIsLatched = true; } RETURN(0); } u32 sceDisplayGetFramebuf(u32 topaddrPtr, u32 linesizePtr, u32 pixelFormatPtr, int mode) { const FrameBufferState &fbState = mode == 1 ? latchedFramebuf : framebuf; 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; } void sceDisplayWaitVblankStart() { DEBUG_LOG(HLE,"sceDisplayWaitVblankStart()"); __KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, false); } void sceDisplayWaitVblank() { DEBUG_LOG(HLE,"sceDisplayWaitVblank()"); __KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, false); } void sceDisplayWaitVblankCB() { DEBUG_LOG(HLE,"sceDisplayWaitVblankCB()"); __KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true); __KernelCheckCallbacks(); } void sceDisplayWaitVblankStartCB() { DEBUG_LOG(HLE,"sceDisplayWaitVblankStartCB()"); __KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true); __KernelCheckCallbacks(); } void sceDisplayWaitVblankStartMultiCB() { DEBUG_LOG(HLE,"sceDisplayWaitVblankStartMultiCB()"); __KernelWaitCurThread(WAITTYPE_VBLANK, 0, 0, 0, true); __KernelCheckCallbacks(); } void sceDisplayGetVcount() { // 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); } void sceDisplayGetCurrentHcount() { RETURN(hCount++); } void sceDisplayGetAccumulatedHcount() { // Just do an estimate u32 accumHCount = CoreTiming::GetTicks() / (222000000 / 60 / 272); DEBUG_LOG(HLE,"%i=sceDisplayGetAccumulatedHcount()", accumHCount); RETURN(accumHCount); } float sceDisplayGetFramePerSec() { float fps = 59.9400599f; DEBUG_LOG(HLE,"%f=sceDisplayGetFramePerSec()", fps); return fps; // (9MHz * 1)/(525 * 286) } const HLEFunction sceDisplay[] = { {0x0E20F177,&WrapU_UUU, "sceDisplaySetMode"}, {0x289D82FE,sceDisplaySetFramebuf, "sceDisplaySetFramebuf"}, {0xEEDA2E54,&WrapU_UUUI,"sceDisplayGetFrameBuf"}, {0x36CDFADE,sceDisplayWaitVblank, "sceDisplayWaitVblank"}, {0x984C27E7,sceDisplayWaitVblankStart, "sceDisplayWaitVblankStart"}, {0x8EB9EC49,sceDisplayWaitVblankCB, "sceDisplayWaitVblankCB"}, {0x46F186C3,sceDisplayWaitVblankStartCB, "sceDisplayWaitVblankStartCB"}, {0x77ed8b3a,sceDisplayWaitVblankStartMultiCB,"sceDisplayWaitVblankStartMultiCB"}, {0xdba6c4c4,&WrapF_V,"sceDisplayGetFramePerSec"}, {0x773dd3a3,sceDisplayGetCurrentHcount,"sceDisplayGetCurrentHcount"}, {0x210eab3a,sceDisplayGetAccumulatedHcount,"sceDisplayGetAccumulatedHcount"}, {0x9C6EAAD7,sceDisplayGetVcount,"sceDisplayGetVcount"}, {0xDEA197D4,0,"sceDisplayGetMode"}, {0x7ED59BC4,0,"sceDisplaySetHoldMode"}, {0xA544C486,0,"sceDisplaySetResumeMode"}, {0xB4F378FA,0,"sceDisplayIsForeground"}, {0x31C4BAA8,0,"sceDisplayGetBrightness"}, {0x4D4E10EC,sceDisplayIsVblank,"sceDisplayIsVblank"}, }; void Register_sceDisplay() { RegisterModule("sceDisplay", ARRAY_SIZE(sceDisplay), sceDisplay); }