Beispiel #1
0
void __KernelTriggerAlarm(u64 userdata, int cyclesLate)
{
	int uid = (int) userdata;

	u32 error;
	Alarm *alarm = kernelObjects.Get<Alarm>(uid, error);
	if (alarm)
		__TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_SYSTIMER0_INTR, uid);
}
Beispiel #2
0
void __KernelTriggerVTimer(u64 userdata, int cyclesLate) {
	SceUID uid = (SceUID) userdata;

	u32 error;
	VTimer *vt = kernelObjects.Get<VTimer>(uid, error);
	if (vt)	{
		vtimers.push_back(uid);
		__TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_SYSTIMER1_INTR);
	}
}
Beispiel #3
0
void hleEnterVblank(u64 userdata, int cyclesLate)
{
	int vbCount = userdata;

	DEBUG_LOG(HLE, "Enter VBlank %i", vbCount);

	isVblank = 1;

	// 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;
	}

	// Yeah, this has to be the right moment to end the frame. Should possibly blit the right buffer
	// depending on what's set in sceDisplaySetFramebuf, in order to support half-framerate games -
	// an initial hack could be to NOT end the frame if the buffer didn't change? that should work okay.
	{
		host->EndFrame();

		host->BeginFrame();
		if (g_Config.bDisplayFramebuffer)
		{
			INFO_LOG(HLE, "Drawing the framebuffer");
			DisplayDrawer_DrawFramebuffer(framebuf.pspframebuf, framebuf.pspFramebufFormat, framebuf.pspFramebufLinesize);
		}

		shaderManager.DirtyShader();
		shaderManager.DirtyUniform(DIRTY_ALL);
	}

	// TODO: Find a way to tell the CPU core to stop emulating here, when running on Android.
}
Beispiel #4
0
void __GeExecuteInterrupt(u64 userdata, int cyclesLate)
{
	__TriggerInterrupt(PSP_INTR_IMMEDIATE, PSP_GE_INTR, PSP_INTR_SUB_NONE);
}
Beispiel #5
0
void hleEnterVblank(u64 userdata, int cyclesLate) {
	int vbCount = userdata;

	DEBUG_LOG(HLE, "Enter VBlank %i", vbCount);

	isVblank = 1;
	vCount++; // // vCount increases at each VBLANK.

	// Fire the vblank listeners before we wake threads.
	__DisplayFireVblank();

	// Wake up threads waiting for VBlank
	for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
		if (--vblankWaitingThreads[i].vcountUnblock == 0) {
			__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
			vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
		}
	}

	// 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);

	// 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);
	}

	gpuStats.numFrames++;

	if (g_Config.bShowFPSCounter) {
		CalculateFPS();
	}

	bool skipFlip = false;

	// This frame was skipped, so no need to flip.
	if (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) {
		skipFlip = true;
	}

	// Draw screen overlays before blitting. Saves and restores the Ge context.
	// 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.
	gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;

	bool throttle, skipFrame;
	
	DoFrameTiming(throttle, skipFrame);

	if (skipFrame) {
		gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
		numSkippedFrames++;
	} else {
		numSkippedFrames = 0;
	}

	if (!skipFlip) {
		// Setting CORE_NEXTFRAME causes a swap.
		// Check first though, might've just quit / been paused.
		if (coreState == CORE_RUNNING && gpu->FramebufferDirty()) {
			coreState = CORE_NEXTFRAME;
		}

		gpu->CopyDisplayToOutput();
	}

	// 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);
}
Beispiel #6
0
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
	for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
		if (--vblankWaitingThreads[i].vcountUnblock == 0) {
			__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
			vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
		}
	}

	// 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);

	// 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);
	}

	gpuStats.numFrames++;

	// Now we can subvert the Ge engine in order to draw custom overlays like stat counters etc.
	if (g_Config.bShowDebugStats && gpuStats.numDrawCalls) {
		DebugStats();
	}

	if (g_Config.bShowFPSCounter) {
		char stats[50];

		sprintf(stats, "%0.1f", calculateFPS());

		#ifdef USING_GLES2
			float zoom = 0.7f; /// g_Config.iWindowZoom;
			float soff = 0.7f;
		#else
			float zoom = 0.5f; /// g_Config.iWindowZoom;
			float soff = 0.5f;
		#endif
		PPGeBegin();
		PPGeDrawText(stats, 476 + soff, 4 + soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000);
		PPGeDrawText(stats, 476 + -soff, 4 -soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000);
		PPGeDrawText(stats, 476, 4, PPGE_ALIGN_RIGHT, zoom, 0xFF30FF30);
		PPGeEnd();
	}

	// Draw screen overlays before blitting. Saves and restores the Ge context.
	// 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.
	gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;

	bool throttle, skipFrame, skipFlip;
	
	DoFrameTiming(throttle, skipFrame, skipFlip);

	// Setting CORE_NEXTFRAME causes a swap.
	if (skipFrame) {
		gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
		numSkippedFrames++;
	} else {
		numSkippedFrames = 0;
	}

	if (!skipFlip) {
		// Might've just quit / been paused.
		if (coreState == CORE_RUNNING) {
			coreState = CORE_NEXTFRAME;
		}
		CoreTiming::ScheduleEvent(0 - cyclesLate, afterFlipEvent, 0);

		gpu->CopyDisplayToOutput();
	}

	// 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.
}
Beispiel #7
0
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;
	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);
			}
			vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
		}
	}

	// 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;
		// 1.001f to compensate for the classic 59.94 NTSC framerate that the PSP seems to have.
		DoFrameTiming(throttle, skipFrame, (float)numVBlanksSinceFlip * (1.001f / 60.0f));

		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;
	}
}
Beispiel #8
0
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
	for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
		if (--vblankWaitingThreads[i].vcountUnblock == 0) {
			__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
			vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
		}
	}

	// 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);

	// 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++;

	// Now we can subvert the Ge engine in order to draw custom overlays like stat counters etc.
	if (g_Config.bShowDebugStats && gpuStats.numDrawCalls) {
		gpu->UpdateStats();
		char stats[2048];
		
		sprintf(stats,
			"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"
			"Num Tracked Vertex Arrays: %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.numFrames,
			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.numTrackedVertexArrays,
			gpuStats.numVertsSubmitted,
			gpuStats.numCachedVertsDrawn,
			gpuStats.numUncachedVertsDrawn,
			gpuStats.numFBOs,
			gpuStats.numTextures,
			gpuStats.numTexturesDecoded,
			gpuStats.numTextureInvalidations,
			gpuStats.numVertexShaders,
			gpuStats.numFragmentShaders,
			gpuStats.numShaders
			);

		float zoom = 0.3f; /// g_Config.iWindowZoom;
		float soff = 0.3f;
		PPGeBegin();
		PPGeDrawText(stats, soff, soff, 0, zoom, 0xCC000000);
		PPGeDrawText(stats, -soff, -soff, 0, zoom, 0xCC000000);
		PPGeDrawText(stats, 0, 0, 0, zoom, 0xFFFFFFFF);
		PPGeEnd();

		gpuStats.resetFrame();
		kernelStats.ResetFrame();
	}

	if (g_Config.bShowFPSCounter) {
		char stats[50];

		sprintf(stats, "%0.1f", calculateFPS());

		#ifdef USING_GLES2
			float zoom = 0.7f; /// g_Config.iWindowZoom;
			float soff = 0.7f;
		#else
			float zoom = 0.5f; /// g_Config.iWindowZoom;
			float soff = 0.5f;
		#endif
		PPGeBegin();
		PPGeDrawText(stats, 476 + soff, 4 + soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000);
		PPGeDrawText(stats, 476 + -soff, 4 -soff, PPGE_ALIGN_RIGHT, zoom, 0xCC000000);
		PPGeDrawText(stats, 476, 4, PPGE_ALIGN_RIGHT, zoom, 0xFF30FF30);
		PPGeEnd();
	}

	// 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();

	host->EndFrame();

#ifdef _WIN32
	// 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) && !PSP_CoreParameter().headLess) {
		while (time_now_d() < lastFrameTime + 1.0 / 60.0) {
			Common::SleepCurrentThread(1);
			time_update();
		}
		// Advance lastFrameTime by a constant amount each frame,
		// but don't let it get too far behind.
		lastFrameTime = std::max(lastFrameTime + 1.0 / 60.0, time_now_d() - 1.5 / 60.0);
	}

	// We are going to have to do something about audio timing for platforms that
	// are vsynced to something that's not exactly 60fps..

#endif

	host->BeginFrame();
	gpu->BeginFrame();

	// Tell the emu core that it's time to stop emulating
	// Win32 doesn't need this.
#ifndef _WIN32
	coreState = CORE_NEXTFRAME;
#endif
}
Beispiel #9
0
void hleEnterVblank(u64 userdata, int cyclesLate) {
	int vbCount = userdata;

	DEBUG_LOG(HLE, "Enter VBlank %i", vbCount);

	isVblank = 1;
	vCount++; // // vCount increases at each VBLANK.

	// Fire the vblank listeners before we wake threads.
	__DisplayFireVblank();

	// Wake up threads waiting for VBlank
	for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
		if (--vblankWaitingThreads[i].vcountUnblock == 0) {
			__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
			vblankWaitingThreads.erase(vblankWaitingThreads.begin() + i--);
		}
	}

	// 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);

	// 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);
	}

	gpuStats.numVBlanks++;

	numVBlanksSinceFlip++;

	if (g_Config.iShowFPSCounter) {
		CalculateFPS();
	}

	// 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.
	if (gpu->FramebufferDirty()) {
		gpuStats.numFlips++;

		bool wasSkipped = (gstate_c.skipDrawReason & SKIPDRAW_SKIPFRAME) != 0;
		gstate_c.skipDrawReason &= ~SKIPDRAW_SKIPFRAME;

		bool throttle, skipFrame;
		DoFrameTiming(throttle, skipFrame, (float)numVBlanksSinceFlip * (1.0f / 60.0f));

		if (skipFrame) {
			gstate_c.skipDrawReason |= SKIPDRAW_SKIPFRAME;
			numSkippedFrames++;
		} else {
			numSkippedFrames = 0;
		}

		// Setting CORE_NEXTFRAME causes a swap.
		// Check first though, might've just quit / been paused.
		if (!wasSkipped) {
			if (coreState == CORE_RUNNING) {
				coreState = CORE_NEXTFRAME;
				gpu->CopyDisplayToOutput();
			}
		}

		// 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;
	}
}
Beispiel #10
0
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
	for (size_t i = 0; i < vblankWaitingThreads.size(); i++) {
		__KernelResumeThreadFromWait(vblankWaitingThreads[i].threadID, 0);
	}
	vblankWaitingThreads.clear();

	// Trigger VBlank interrupt handlers.
	__TriggerInterrupt(PSP_INTR_IMMEDIATE | PSP_INTR_ONLY_IF_ENABLED, 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 && gpuStats.numDrawCalls) {
		gpu->UpdateStats();
		char stats[512];
		sprintf(stats,
			"Frames: %i\n"
			"Draw calls: %i\n"
			"Draw flushes: %i\n"
			"Vertices Transformed: %i\n"
			"Textures active: %i\n"
			"Textures decoded: %i\n"
			"Vertex shaders loaded: %i\n"
			"Fragment shaders loaded: %i\n"
			"Combined shaders loaded: %i\n",
			gpuStats.numFrames,
			gpuStats.numDrawCalls,
			gpuStats.numFlushes,
			gpuStats.numVertsTransformed,
			gpuStats.numTextures,
			gpuStats.numTexturesDecoded,
			gpuStats.numVertexShaders,
			gpuStats.numFragmentShaders,
			gpuStats.numShaders
			);

		float zoom = 0.7f; /// g_Config.iWindowZoom;
		PPGeBegin();
		PPGeDrawText(stats, 0, 0, 0, zoom, 0xFFc0c0c0);
		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.0) {
			Common::SleepCurrentThread(1);
			time_update();
		}
		// Advance lastFrameTime by a constant amount each frame,
		// but don't let it get too far behind.
		lastFrameTime = std::max(lastFrameTime + 1.0 / 60.0, time_now_d() - 1.5 / 60.0);
	}

	// We are going to have to do something about audio timing for platforms that
	// are vsynced to something that's not exactly 60fps..

#endif

	host->BeginFrame();
	gpu->BeginFrame();

	// Tell the emu core that it's time to stop emulating
	// Win32 doesn't need this.
#ifndef _WIN32
	coreState = CORE_NEXTFRAME;
#endif
}