1 // Copyright (c) 2012- PPSSPP Project.
2 
3 // This program is free software: you can redistribute it and/or modify
4 // it under the terms of the GNU General Public License as published by
5 // the Free Software Foundation, version 2.0 or later versions.
6 
7 // This program is distributed in the hope that it will be useful,
8 // but WITHOUT ANY WARRANTY; without even the implied warranty of
9 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10 // GNU General Public License 2.0 for more details.
11 
12 // A copy of the GPL 2.0 should have been included with the program.
13 // If not, see http://www.gnu.org/licenses/
14 
15 // Official git repository and contact information can be found at
16 // https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17 
18 #include "ppsspp_config.h"
19 
20 #include <algorithm>
21 #include <functional>
22 
23 using namespace std::placeholders;
24 
25 #include "Common/Render/TextureAtlas.h"
26 #include "Common/GPU/OpenGL/GLFeatures.h"
27 #include "Common/Render/Text/draw_text.h"
28 
29 #include "Common/UI/Root.h"
30 #include "Common/UI/UI.h"
31 #include "Common/UI/Context.h"
32 #include "Common/UI/Tween.h"
33 #include "Common/UI/View.h"
34 
35 #include "Common/Data/Text/I18n.h"
36 #include "Common/Input/InputState.h"
37 #include "Common/Log.h"
38 #include "Common/System/Display.h"
39 #include "Common/System/System.h"
40 #include "Common/System/NativeApp.h"
41 #include "Common/Profiler/Profiler.h"
42 #include "Common/Math/curves.h"
43 #include "Common/TimeUtil.h"
44 
45 #ifndef MOBILE_DEVICE
46 #include "Core/AVIDump.h"
47 #endif
48 #include "Core/Config.h"
49 #include "Core/ConfigValues.h"
50 #include "Core/CoreTiming.h"
51 #include "Core/CoreParameter.h"
52 #include "Core/Core.h"
53 #include "Core/CwCheat.h"
54 #include "Core/Host.h"
55 #include "Core/KeyMap.h"
56 #include "Core/MemFault.h"
57 #include "Core/Reporting.h"
58 #include "Core/System.h"
59 #include "GPU/GPUState.h"
60 #include "GPU/GPUInterface.h"
61 #include "GPU/Common/FramebufferManagerCommon.h"
62 #if !PPSSPP_PLATFORM(UWP)
63 #include "GPU/Vulkan/DebugVisVulkan.h"
64 #endif
65 #include "Core/HLE/sceCtrl.h"
66 #include "Core/HLE/sceDisplay.h"
67 #include "Core/HLE/sceSas.h"
68 #include "Core/Debugger/SymbolMap.h"
69 #include "Core/SaveState.h"
70 #include "Core/MIPS/MIPS.h"
71 #include "Core/HLE/__sceAudio.h"
72 #include "Core/HLE/proAdhoc.h"
73 #include "Core/HLE/Plugins.h"
74 
75 #include "UI/BackgroundAudio.h"
76 #include "UI/OnScreenDisplay.h"
77 #include "UI/GamepadEmu.h"
78 #include "UI/PauseScreen.h"
79 #include "UI/MainScreen.h"
80 #include "UI/EmuScreen.h"
81 #include "UI/DevScreens.h"
82 #include "UI/GameInfoCache.h"
83 #include "UI/MiscScreens.h"
84 #include "UI/ControlMappingScreen.h"
85 #include "UI/DisplayLayoutScreen.h"
86 #include "UI/GameSettingsScreen.h"
87 #include "UI/ProfilerDraw.h"
88 #include "UI/DiscordIntegration.h"
89 #include "UI/ChatScreen.h"
90 
91 #if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
92 #include "Windows/MainWindow.h"
93 #endif
94 
95 #ifndef MOBILE_DEVICE
96 static AVIDump avi;
97 #endif
98 
99 // TODO: Ugly!
100 static bool frameStep_;
101 static int lastNumFlips;
102 static bool startDumping;
103 
104 extern bool g_TakeScreenshot;
105 
__EmuScreenVblank()106 static void __EmuScreenVblank()
107 {
108 	auto sy = GetI18NCategory("System");
109 
110 	if (frameStep_ && lastNumFlips != gpuStats.numFlips)
111 	{
112 		frameStep_ = false;
113 		Core_EnableStepping(true);
114 		lastNumFlips = gpuStats.numFlips;
115 	}
116 #ifndef MOBILE_DEVICE
117 	if (g_Config.bDumpFrames && !startDumping)
118 	{
119 		avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);
120 		osm.Show(sy->T("AVI Dump started."), 0.5f);
121 		startDumping = true;
122 	}
123 	if (g_Config.bDumpFrames && startDumping)
124 	{
125 		avi.AddFrame();
126 	}
127 	else if (!g_Config.bDumpFrames && startDumping)
128 	{
129 		avi.Stop();
130 		osm.Show(sy->T("AVI Dump stopped."), 1.0f);
131 		startDumping = false;
132 	}
133 #endif
134 }
135 
136 // Handles control rotation due to internal screen rotation.
137 // TODO: This should be a callback too, so we don't actually call the __Ctrl functions
138 // from settings screens, etc.
SetPSPAnalog(int stick,float x,float y)139 static void SetPSPAnalog(int stick, float x, float y) {
140 	switch (g_Config.iInternalScreenRotation) {
141 	case ROTATION_LOCKED_HORIZONTAL:
142 		// Standard rotation. No change.
143 		break;
144 	case ROTATION_LOCKED_HORIZONTAL180:
145 		x = -x;
146 		y = -y;
147 		break;
148 	case ROTATION_LOCKED_VERTICAL:
149 	{
150 		float new_y = -x;
151 		x = y;
152 		y = new_y;
153 		break;
154 	}
155 	case ROTATION_LOCKED_VERTICAL180:
156 	{
157 		float new_y = y = x;
158 		x = -y;
159 		y = new_y;
160 		break;
161 	}
162 	default:
163 		break;
164 	}
165 
166 	__CtrlSetAnalogXY(stick, x, y);
167 }
168 
EmuScreen(const Path & filename)169 EmuScreen::EmuScreen(const Path &filename)
170 	: gamePath_(filename) {
171 	saveStateSlot_ = SaveState::GetCurrentSlot();
172 	__DisplayListenVblank(__EmuScreenVblank);
173 	frameStep_ = false;
174 	lastNumFlips = gpuStats.numFlips;
175 	startDumping = false;
176 	controlMapper_.SetCallbacks(
177 		std::bind(&EmuScreen::onVKeyDown, this, _1),
178 		std::bind(&EmuScreen::onVKeyUp, this, _1),
179 		&SetPSPAnalog);
180 
181 	// Make sure we don't leave it at powerdown after the last game.
182 	// TODO: This really should be handled elsewhere if it isn't.
183 	if (coreState == CORE_POWERDOWN)
184 		coreState = CORE_STEPPING;
185 
186 	OnDevMenu.Handle(this, &EmuScreen::OnDevTools);
187 	OnChatMenu.Handle(this, &EmuScreen::OnChat);
188 
189 	// Usually, we don't want focus movement enabled on this screen, so disable on start.
190 	// Only if you open chat or dev tools do we want it to start working.
191 	UI::EnableFocusMovement(false);
192 }
193 
bootAllowStorage(const Path & filename)194 bool EmuScreen::bootAllowStorage(const Path &filename) {
195 	// No permissions needed.  The easy life.
196 	if (filename.Type() == PathType::HTTP)
197 		return true;
198 
199 	if (!System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS))
200 		return true;
201 
202 	PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);
203 	switch (status) {
204 	case PERMISSION_STATUS_UNKNOWN:
205 		System_AskForPermission(SYSTEM_PERMISSION_STORAGE);
206 		return false;
207 
208 	case PERMISSION_STATUS_DENIED:
209 		stopRender_ = true;
210 		screenManager()->switchScreen(new MainScreen());
211 		System_SendMessage("event", "failstartgame");
212 		return false;
213 
214 	case PERMISSION_STATUS_PENDING:
215 		// Keep waiting.
216 		return false;
217 
218 	case PERMISSION_STATUS_GRANTED:
219 		return true;
220 	}
221 
222 	_assert_(false);
223 	return false;
224 }
225 
bootGame(const Path & filename)226 void EmuScreen::bootGame(const Path &filename) {
227 	if (PSP_IsIniting()) {
228 		std::string error_string;
229 		bootPending_ = !PSP_InitUpdate(&error_string);
230 		if (!bootPending_) {
231 			invalid_ = !PSP_IsInited();
232 			if (invalid_) {
233 				errorMessage_ = error_string;
234 				ERROR_LOG(BOOT, "%s", errorMessage_.c_str());
235 				System_SendMessage("event", "failstartgame");
236 				return;
237 			}
238 			bootComplete();
239 		}
240 		return;
241 	}
242 
243 	g_BackgroundAudio.SetGame(Path());
244 
245 	// Check permission status first, in case we came from a shortcut.
246 	if (!bootAllowStorage(filename))
247 		return;
248 
249 	auto sc = GetI18NCategory("Screen");
250 
251 	invalid_ = true;
252 
253 	// We don't want to boot with the wrong game specific config, so wait until info is ready.
254 	std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, filename, 0);
255 	if (!info || info->pending)
256 		return;
257 
258 	if (!info->id.empty()) {
259 		g_Config.loadGameConfig(info->id, info->GetTitle());
260 		// Reset views in case controls are in a different place.
261 		RecreateViews();
262 
263 		g_Discord.SetPresenceGame(info->GetTitle().c_str());
264 	} else {
265 		g_Discord.SetPresenceGame(sc->T("Untitled PSP game"));
266 	}
267 
268 	CoreParameter coreParam{};
269 	coreParam.cpuCore = (CPUCore)g_Config.iCpuCore;
270 	coreParam.gpuCore = GPUCORE_GLES;
271 	switch (GetGPUBackend()) {
272 	case GPUBackend::DIRECT3D11:
273 		coreParam.gpuCore = GPUCORE_DIRECTX11;
274 		break;
275 #if !PPSSPP_PLATFORM(UWP)
276 #if PPSSPP_API(ANY_GL)
277 	case GPUBackend::OPENGL:
278 		coreParam.gpuCore = GPUCORE_GLES;
279 		break;
280 #endif
281 	case GPUBackend::DIRECT3D9:
282 		coreParam.gpuCore = GPUCORE_DIRECTX9;
283 		break;
284 	case GPUBackend::VULKAN:
285 		coreParam.gpuCore = GPUCORE_VULKAN;
286 		break;
287 #endif
288 	}
289 
290 	// Preserve the existing graphics context.
291 	coreParam.graphicsContext = PSP_CoreParameter().graphicsContext;
292 	coreParam.enableSound = g_Config.bEnableSound;
293 	coreParam.fileToStart = filename;
294 	coreParam.mountIso.clear();
295 	coreParam.mountRoot.clear();
296 	coreParam.startBreak = !g_Config.bAutoRun;
297 	coreParam.printfEmuLog = false;
298 	coreParam.headLess = false;
299 
300 	if (g_Config.iInternalResolution == 0) {
301 		coreParam.renderWidth = pixel_xres;
302 		coreParam.renderHeight = pixel_yres;
303 	} else {
304 		if (g_Config.iInternalResolution < 0)
305 			g_Config.iInternalResolution = 1;
306 		coreParam.renderWidth = 480 * g_Config.iInternalResolution;
307 		coreParam.renderHeight = 272 * g_Config.iInternalResolution;
308 	}
309 	coreParam.pixelWidth = pixel_xres;
310 	coreParam.pixelHeight = pixel_yres;
311 
312 	std::string error_string;
313 	if (!PSP_InitStart(coreParam, &error_string)) {
314 		bootPending_ = false;
315 		invalid_ = true;
316 		errorMessage_ = error_string;
317 		ERROR_LOG(BOOT, "%s", errorMessage_.c_str());
318 		System_SendMessage("event", "failstartgame");
319 	}
320 
321 	if (PSP_CoreParameter().compat.flags().RequireBufferedRendering && g_Config.iRenderingMode == FB_NON_BUFFERED_MODE) {
322 		auto gr = GetI18NCategory("Graphics");
323 		host->NotifyUserMessage(gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 15.0f);
324 	}
325 
326 	if (PSP_CoreParameter().compat.flags().RequireBlockTransfer && g_Config.bBlockTransferGPU == false) {
327 		auto gr = GetI18NCategory("Graphics");
328 		host->NotifyUserMessage(gr->T("BlockTransferRequired", "Warning: This game requires Simulate Block Transfer Mode to be set to On."), 15.0f);
329 	}
330 
331 	if (PSP_CoreParameter().compat.flags().RequireDefaultCPUClock && g_Config.iLockedCPUSpeed != 0) {
332 		auto gr = GetI18NCategory("Graphics");
333 		host->NotifyUserMessage(gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 15.0f);
334 	}
335 
336 	loadingViewColor_->Divert(0xFFFFFFFF, 0.75f);
337 	loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f);
338 }
339 
bootComplete()340 void EmuScreen::bootComplete() {
341 	UpdateUIState(UISTATE_INGAME);
342 	host->BootDone();
343 	host->UpdateDisassembly();
344 
345 	NOTICE_LOG(BOOT, "Loading %s...", PSP_CoreParameter().fileToStart.c_str());
346 	autoLoad();
347 
348 	auto sc = GetI18NCategory("Screen");
349 
350 #ifndef MOBILE_DEVICE
351 	if (g_Config.bFirstRun) {
352 		osm.Show(sc->T("PressESC", "Press ESC to open the pause menu"), 3.0f);
353 	}
354 #endif
355 
356 #if !PPSSPP_PLATFORM(UWP)
357 	if (GetGPUBackend() == GPUBackend::OPENGL) {
358 		const char *renderer = gl_extensions.model;
359 		if (strstr(renderer, "Chainfire3D") != 0) {
360 			osm.Show(sc->T("Chainfire3DWarning", "WARNING: Chainfire3D detected, may cause problems"), 10.0f, 0xFF30a0FF, -1, true);
361 		} else if (strstr(renderer, "GLTools") != 0) {
362 			osm.Show(sc->T("GLToolsWarning", "WARNING: GLTools detected, may cause problems"), 10.0f, 0xFF30a0FF, -1, true);
363 		}
364 
365 		if (g_Config.bGfxDebugOutput) {
366 			osm.Show("WARNING: GfxDebugOutput is enabled via ppsspp.ini. Things may be slow.", 10.0f, 0xFF30a0FF, -1, true);
367 		}
368 	}
369 #endif
370 
371 	if (Core_GetPowerSaving()) {
372 		auto sy = GetI18NCategory("System");
373 #ifdef __ANDROID__
374 		osm.Show(sy->T("WARNING: Android battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving");
375 #else
376 		osm.Show(sy->T("WARNING: Battery save mode is on"), 2.0f, 0xFFFFFF, -1, true, "core_powerSaving");
377 #endif
378 	}
379 
380 	System_SendMessage("event", "startgame");
381 
382 	saveStateSlot_ = SaveState::GetCurrentSlot();
383 
384 	loadingViewColor_->Divert(0x00FFFFFF, 0.2f);
385 	loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);
386 }
387 
~EmuScreen()388 EmuScreen::~EmuScreen() {
389 	if (!invalid_ || bootPending_) {
390 		// If we were invalid, it would already be shutdown.
391 		PSP_Shutdown();
392 	}
393 #ifndef MOBILE_DEVICE
394 	if (g_Config.bDumpFrames && startDumping)
395 	{
396 		avi.Stop();
397 		osm.Show("AVI Dump stopped.", 1.0f);
398 		startDumping = false;
399 	}
400 #endif
401 
402 	if (GetUIState() == UISTATE_EXIT)
403 		g_Discord.ClearPresence();
404 	else
405 		g_Discord.SetPresenceMenu();
406 }
407 
dialogFinished(const Screen * dialog,DialogResult result)408 void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) {
409 	// TODO: improve the way with which we got commands from PauseMenu.
410 	// DR_CANCEL/DR_BACK means clicked on "continue", DR_OK means clicked on "back to menu",
411 	// DR_YES means a message sent to PauseMenu by NativeMessageReceived.
412 	if (result == DR_OK || quit_) {
413 		screenManager()->switchScreen(new MainScreen());
414 		System_SendMessage("event", "exitgame");
415 		quit_ = false;
416 	}
417 	RecreateViews();
418 }
419 
AfterSaveStateAction(SaveState::Status status,const std::string & message,void *)420 static void AfterSaveStateAction(SaveState::Status status, const std::string &message, void *) {
421 	if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {
422 		osm.Show(message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);
423 	}
424 }
425 
AfterStateBoot(SaveState::Status status,const std::string & message,void * ignored)426 static void AfterStateBoot(SaveState::Status status, const std::string &message, void *ignored) {
427 	AfterSaveStateAction(status, message, ignored);
428 	Core_EnableStepping(false);
429 	host->UpdateDisassembly();
430 }
431 
sendMessage(const char * message,const char * value)432 void EmuScreen::sendMessage(const char *message, const char *value) {
433 	// External commands, like from the Windows UI.
434 	if (!strcmp(message, "pause") && screenManager()->topScreen() == this) {
435 		screenManager()->push(new GamePauseScreen(gamePath_));
436 	} else if (!strcmp(message, "stop")) {
437 		// We will push MainScreen in update().
438 		PSP_Shutdown();
439 		bootPending_ = false;
440 		stopRender_ = true;
441 		invalid_ = true;
442 		host->UpdateDisassembly();
443 	} else if (!strcmp(message, "reset")) {
444 		PSP_Shutdown();
445 		bootPending_ = true;
446 		invalid_ = true;
447 		host->UpdateDisassembly();
448 
449 		std::string resetError;
450 		if (!PSP_InitStart(PSP_CoreParameter(), &resetError)) {
451 			ERROR_LOG(LOADER, "Error resetting: %s", resetError.c_str());
452 			stopRender_ = true;
453 			screenManager()->switchScreen(new MainScreen());
454 			System_SendMessage("event", "failstartgame");
455 			return;
456 		}
457 	} else if (!strcmp(message, "boot")) {
458 		const char *ext = strrchr(value, '.');
459 		if (ext != nullptr && !strcmp(ext, ".ppst")) {
460 			SaveState::Load(Path(value), -1, &AfterStateBoot);
461 		} else {
462 			PSP_Shutdown();
463 			bootPending_ = true;
464 			gamePath_ = Path(value);
465 			// Don't leave it on CORE_POWERDOWN, we'll sometimes aggressively bail.
466 			Core_UpdateState(CORE_POWERUP);
467 		}
468 	} else if (!strcmp(message, "config_loaded")) {
469 		// In case we need to position touch controls differently.
470 		RecreateViews();
471 	} else if (!strcmp(message, "control mapping") && screenManager()->topScreen() == this) {
472 		UpdateUIState(UISTATE_PAUSEMENU);
473 		screenManager()->push(new ControlMappingScreen());
474 	} else if (!strcmp(message, "display layout editor") && screenManager()->topScreen() == this) {
475 		UpdateUIState(UISTATE_PAUSEMENU);
476 		screenManager()->push(new DisplayLayoutScreen());
477 	} else if (!strcmp(message, "settings") && screenManager()->topScreen() == this) {
478 		UpdateUIState(UISTATE_PAUSEMENU);
479 		screenManager()->push(new GameSettingsScreen(gamePath_));
480 	} else if (!strcmp(message, "gpu dump next frame")) {
481 		if (gpu)
482 			gpu->DumpNextFrame();
483 	} else if (!strcmp(message, "clear jit")) {
484 		currentMIPS->ClearJitCache();
485 		if (PSP_IsInited()) {
486 			currentMIPS->UpdateCore((CPUCore)g_Config.iCpuCore);
487 		}
488 	} else if (!strcmp(message, "window minimized")) {
489 		if (!strcmp(value, "true")) {
490 			gstate_c.skipDrawReason |= SKIPDRAW_WINDOW_MINIMIZED;
491 		} else {
492 			gstate_c.skipDrawReason &= ~SKIPDRAW_WINDOW_MINIMIZED;
493 		}
494 	} else if (!strcmp(message, "chat screen")) {
495 		if (g_Config.bEnableNetworkChat) {
496 			if (!chatButton_)
497 				RecreateViews();
498 
499 #if defined(USING_WIN_UI)
500 			//temporary workaround for hotkey its freeze the ui when open chat screen using hotkey and native keyboard is enable
501 			if (g_Config.bBypassOSKWithKeyboard) {
502 				osm.Show("Disable windows native keyboard options to use ctrl + c hotkey", 2.0f);
503 			} else {
504 				UI::EventParams e{};
505 				OnChatMenu.Trigger(e);
506 			}
507 #else
508 			UI::EventParams e{};
509 			OnChatMenu.Trigger(e);
510 #endif
511 		}
512 	} else if (!strcmp(message, "app_resumed") && screenManager()->topScreen() == this) {
513 		if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) {
514 			if (!KeyMap::IsKeyMapped(DEVICE_ID_PAD_0, VIRTKEY_PAUSE) || !KeyMap::IsKeyMapped(DEVICE_ID_PAD_1, VIRTKEY_PAUSE)) {
515 				// If it's a TV (so no built-in back button), and there's no back button mapped to a pad,
516 				// use this as the fallback way to get into the menu.
517 
518 				screenManager()->push(new GamePauseScreen(gamePath_));
519 			}
520 		}
521 	}
522 }
523 
524 //tiltInputCurve implements a smooth deadzone as described here:
525 //http://www.gamasutra.com/blogs/JoshSutphin/20130416/190541/Doing_Thumbstick_Dead_Zones_Right.php
tiltInputCurve(float x)526 inline float tiltInputCurve(float x) {
527 	const float deadzone = g_Config.fDeadzoneRadius;
528 	const float factor = 1.0f / (1.0f - deadzone);
529 
530 	if (x > deadzone) {
531 		return (x - deadzone) * (x - deadzone) * factor;
532 	} else if (x < -deadzone) {
533 		return -(x + deadzone) * (x + deadzone) * factor;
534 	} else {
535 		return 0.0f;
536 	}
537 }
538 
clamp1(float x)539 inline float clamp1(float x) {
540 	if (x > 1.0f) return 1.0f;
541 	if (x < -1.0f) return -1.0f;
542 	return x;
543 }
544 
touch(const TouchInput & touch)545 bool EmuScreen::touch(const TouchInput &touch) {
546 	Core_NotifyActivity();
547 
548 	if (chatMenu_ && (touch.flags & TOUCH_DOWN) != 0 && !chatMenu_->Contains(touch.x, touch.y)) {
549 		chatMenu_->Close();
550 		if (chatButton_)
551 			chatButton_->SetVisibility(UI::V_VISIBLE);
552 		UI::EnableFocusMovement(false);
553 	}
554 
555 	if (root_) {
556 		root_->Touch(touch);
557 		return true;
558 	} else {
559 		return false;
560 	}
561 }
562 
onVKeyDown(int virtualKeyCode)563 void EmuScreen::onVKeyDown(int virtualKeyCode) {
564 	auto sc = GetI18NCategory("Screen");
565 
566 	switch (virtualKeyCode) {
567 	case VIRTKEY_FASTFORWARD:
568 		if (coreState == CORE_STEPPING) {
569 			Core_EnableStepping(false);
570 		}
571 		PSP_CoreParameter().fastForward = true;
572 		break;
573 
574 	case VIRTKEY_SPEED_TOGGLE:
575 		// Cycle through enabled speeds.
576 		if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) {
577 			PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
578 			osm.Show(sc->T("fixed", "Speed: alternate"), 1.0);
579 		} else if (PSP_CoreParameter().fpsLimit != FPSLimit::CUSTOM2 && g_Config.iFpsLimit2 >= 0) {
580 			PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
581 			osm.Show(sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0);
582 		} else if (PSP_CoreParameter().fpsLimit != FPSLimit::NORMAL) {
583 			PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
584 			osm.Show(sc->T("standard", "Speed: standard"), 1.0);
585 		}
586 		break;
587 
588 	case VIRTKEY_SPEED_CUSTOM1:
589 		if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
590 			PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
591 			osm.Show(sc->T("fixed", "Speed: alternate"), 1.0);
592 		}
593 		break;
594 	case VIRTKEY_SPEED_CUSTOM2:
595 		if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
596 			PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
597 			osm.Show(sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0);
598 		}
599 		break;
600 
601 	case VIRTKEY_PAUSE:
602 		pauseTrigger_ = true;
603 		break;
604 
605 	case VIRTKEY_FRAME_ADVANCE:
606 		// If game is running, pause emulation immediately. Otherwise, advance a single frame.
607 		if (Core_IsStepping())
608 		{
609 			frameStep_ = true;
610 			Core_EnableStepping(false);
611 		}
612 		else if (!frameStep_)
613 		{
614 			Core_EnableStepping(true);
615 		}
616 		break;
617 
618 	case VIRTKEY_OPENCHAT:
619 		if (g_Config.bEnableNetworkChat) {
620 			UI::EventParams e{};
621 			OnChatMenu.Trigger(e);
622 		}
623 		break;
624 
625 	case VIRTKEY_AXIS_SWAP:
626 		KeyMap::SwapAxis();
627 		break;
628 
629 	case VIRTKEY_DEVMENU:
630 	{
631 		UI::EventParams e{};
632 		OnDevMenu.Trigger(e);
633 		break;
634 	}
635 
636 #ifndef MOBILE_DEVICE
637 	case VIRTKEY_RECORD:
638 	{
639 		if (g_Config.bDumpFrames == g_Config.bDumpAudio) {
640 			g_Config.bDumpFrames = !g_Config.bDumpFrames;
641 			g_Config.bDumpAudio = !g_Config.bDumpAudio;
642 		} else {
643 			// This hotkey should always toggle both audio and video together.
644 			// So let's make sure that's the only outcome even if video OR audio was already being dumped.
645 			if (g_Config.bDumpFrames) {
646 				AVIDump::Stop();
647 				AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);
648 				g_Config.bDumpAudio = true;
649 			} else {
650 				WAVDump::Reset();
651 				g_Config.bDumpFrames = true;
652 			}
653 		}
654 		break;
655 	}
656 #endif
657 
658 	case VIRTKEY_REWIND:
659 		if (SaveState::CanRewind()) {
660 			SaveState::Rewind(&AfterSaveStateAction);
661 		} else {
662 			osm.Show(sc->T("norewind", "No rewind save states available"), 2.0);
663 		}
664 		break;
665 	case VIRTKEY_SAVE_STATE:
666 		SaveState::SaveSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);
667 		break;
668 	case VIRTKEY_LOAD_STATE:
669 		SaveState::LoadSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);
670 		break;
671 	case VIRTKEY_NEXT_SLOT:
672 		SaveState::NextSlot();
673 		NativeMessageReceived("savestate_displayslot", "");
674 		break;
675 	case VIRTKEY_TOGGLE_FULLSCREEN:
676 		System_SendMessage("toggle_fullscreen", "");
677 		break;
678 
679 	case VIRTKEY_SCREENSHOT:
680 		g_TakeScreenshot = true;
681 		break;
682 
683 	case VIRTKEY_TEXTURE_DUMP:
684 		g_Config.bSaveNewTextures = !g_Config.bSaveNewTextures;
685 		if (g_Config.bSaveNewTextures) {
686 			osm.Show(sc->T("saveNewTextures_true", "Textures will now be saved to your storage"), 2.0);
687 			NativeMessageReceived("gpu_clearCache", "");
688 		} else {
689 			osm.Show(sc->T("saveNewTextures_false", "Texture saving was disabled"), 2.0);
690 		}
691 		break;
692 	case VIRTKEY_TEXTURE_REPLACE:
693 		g_Config.bReplaceTextures = !g_Config.bReplaceTextures;
694 		if (g_Config.bReplaceTextures)
695 			osm.Show(sc->T("replaceTextures_true", "Texture replacement enabled"), 2.0);
696 		else
697 			osm.Show(sc->T("replaceTextures_false", "Textures no longer are being replaced"), 2.0);
698 		NativeMessageReceived("gpu_clearCache", "");
699 		break;
700 	case VIRTKEY_RAPID_FIRE:
701 		__CtrlSetRapidFire(true);
702 		break;
703 	case VIRTKEY_MUTE_TOGGLE:
704 		g_Config.bEnableSound = !g_Config.bEnableSound;
705 		break;
706 	}
707 }
708 
onVKeyUp(int virtualKeyCode)709 void EmuScreen::onVKeyUp(int virtualKeyCode) {
710 	auto sc = GetI18NCategory("Screen");
711 
712 	switch (virtualKeyCode) {
713 	case VIRTKEY_FASTFORWARD:
714 		PSP_CoreParameter().fastForward = false;
715 		break;
716 
717 	case VIRTKEY_SPEED_CUSTOM1:
718 		if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) {
719 			PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
720 			osm.Show(sc->T("standard", "Speed: standard"), 1.0);
721 		}
722 		break;
723 	case VIRTKEY_SPEED_CUSTOM2:
724 		if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
725 			PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
726 			osm.Show(sc->T("standard", "Speed: standard"), 1.0);
727 		}
728 		break;
729 
730 	case VIRTKEY_RAPID_FIRE:
731 		__CtrlSetRapidFire(false);
732 		break;
733 
734 	default:
735 		break;
736 	}
737 }
738 
key(const KeyInput & key)739 bool EmuScreen::key(const KeyInput &key) {
740 	Core_NotifyActivity();
741 
742 	if (UI::IsFocusMovementEnabled()) {
743 		if (UIScreen::key(key)) {
744 			return true;
745 		} else if ((key.flags & KEY_DOWN) != 0 && UI::IsEscapeKey(key)) {
746 			if (chatMenu_)
747 				chatMenu_->Close();
748 			if (chatButton_)
749 				chatButton_->SetVisibility(UI::V_VISIBLE);
750 			UI::EnableFocusMovement(false);
751 			return true;
752 		}
753 	}
754 
755 	return controlMapper_.Key(key, &pauseTrigger_);
756 }
757 
axis(const AxisInput & axis)758 bool EmuScreen::axis(const AxisInput &axis) {
759 	Core_NotifyActivity();
760 
761 	return controlMapper_.Axis(axis);
762 }
763 
764 class GameInfoBGView : public UI::InertView {
765 public:
GameInfoBGView(const Path & gamePath,UI::LayoutParams * layoutParams)766 	GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {
767 	}
768 
Draw(UIContext & dc)769 	void Draw(UIContext &dc) override {
770 		// Should only be called when visible.
771 		std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GAMEINFO_WANTBG);
772 		dc.Flush();
773 
774 		// PIC1 is the loading image, so let's only draw if it's available.
775 		if (ginfo && ginfo->pic1.texture) {
776 			Draw::Texture *texture = ginfo->pic1.texture->GetTexture();
777 			if (texture) {
778 				dc.GetDrawContext()->BindTexture(0, texture);
779 
780 				double loadTime = ginfo->pic1.timeLoaded;
781 				uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3));
782 				dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color);
783 				dc.Flush();
784 				dc.RebindTexture();
785 			}
786 		}
787 	}
788 
DescribeText() const789 	std::string DescribeText() const override {
790 		return "";
791 	}
792 
SetColor(uint32_t c)793 	void SetColor(uint32_t c) {
794 		color_ = c;
795 	}
796 
797 protected:
798 	Path gamePath_;
799 	uint32_t color_ = 0xFFC0C0C0;
800 };
801 
CreateViews()802 void EmuScreen::CreateViews() {
803 	using namespace UI;
804 
805 	auto dev = GetI18NCategory("Developer");
806 	auto n = GetI18NCategory("Networking");
807 	auto sc = GetI18NCategory("Screen");
808 
809 	const Bounds &bounds = screenManager()->getUIContext()->GetLayoutBounds();
810 	InitPadLayout(bounds.w, bounds.h);
811 
812 	// Devices without a back button like iOS need an on-screen touch back button.
813 	bool showPauseButton = !System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON) || g_Config.bShowTouchPause;
814 
815 	root_ = CreatePadLayout(bounds.w, bounds.h, &pauseTrigger_, showPauseButton, &controlMapper_);
816 	if (g_Config.bShowDeveloperMenu) {
817 		root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools);
818 	}
819 
820 	LinearLayout *buttons = new LinearLayout(Orientation::ORIENT_HORIZONTAL, new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 60, true));
821 	buttons->SetSpacing(20.0f);
822 	root_->Add(buttons);
823 
824 	resumeButton_ = buttons->Add(new Button(dev->T("Resume")));
825 	resumeButton_->OnClick.Handle(this, &EmuScreen::OnResume);
826 	resumeButton_->SetVisibility(V_GONE);
827 
828 	resetButton_ = buttons->Add(new Button(dev->T("Reset")));
829 	resetButton_->OnClick.Handle(this, &EmuScreen::OnReset);
830 	resetButton_->SetVisibility(V_GONE);
831 
832 	cardboardDisableButton_ = root_->Add(new Button(sc->T("Cardboard VR OFF"), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 30, true)));
833 	cardboardDisableButton_->OnClick.Handle(this, &EmuScreen::OnDisableCardboard);
834 	cardboardDisableButton_->SetVisibility(V_GONE);
835 	cardboardDisableButton_->SetScale(0.65f);  // make it smaller - this button can be in the way otherwise.
836 
837 	if (g_Config.bEnableNetworkChat && g_Config.iChatButtonPosition != 8) {
838 		AnchorLayoutParams *layoutParams = nullptr;
839 		switch (g_Config.iChatButtonPosition) {
840 		case 0:
841 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, NONE, NONE, 50, true);
842 			break;
843 		case 1:
844 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), NONE, NONE, 50, true);
845 			break;
846 		case 2:
847 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, NONE, 80, 50, true);
848 			break;
849 		case 3:
850 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, 50, NONE, NONE, true);
851 			break;
852 		case 4:
853 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), 50, NONE, NONE, true);
854 			break;
855 		case 5:
856 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, 50, 80, NONE, true);
857 			break;
858 		case 6:
859 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, bounds.centerY(), NONE, NONE, true);
860 			break;
861 		case 7:
862 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, bounds.centerY(), 80, NONE, true);
863 			break;
864 		default:
865 			layoutParams = new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, 80, NONE, NONE, 50, true);
866 			break;
867 		}
868 
869 		ChoiceWithValueDisplay *btn = new ChoiceWithValueDisplay(&newChatMessages_, n->T("Chat"), layoutParams);
870 		root_->Add(btn)->OnClick.Handle(this, &EmuScreen::OnChat);
871 		chatButton_ = btn;
872 		chatMenu_ = root_->Add(new ChatMenu(screenManager()->getUIContext()->GetBounds(), new LayoutParams(FILL_PARENT, FILL_PARENT)));
873 		chatMenu_->SetVisibility(UI::V_GONE);
874 	} else {
875 		chatButton_ = nullptr;
876 		chatMenu_ = nullptr;
877 	}
878 
879 	saveStatePreview_ = new AsyncImageFileView(Path(), IS_FIXED, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, true));
880 	saveStatePreview_->SetFixedSize(160, 90);
881 	saveStatePreview_->SetColor(0x90FFFFFF);
882 	saveStatePreview_->SetVisibility(V_GONE);
883 	saveStatePreview_->SetCanBeFocused(false);
884 	root_->Add(saveStatePreview_);
885 	onScreenMessagesView_ = root_->Add(new OnScreenMessagesView(new AnchorLayoutParams((Size)bounds.w, (Size)bounds.h)));
886 
887 	GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)));
888 	TextView *loadingTextView = root_->Add(new TextView(sc->T(PSP_GetLoading()), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 40, true)));
889 	loadingTextView_ = loadingTextView;
890 
891 	static const ImageID symbols[4] = {
892 		ImageID("I_CROSS"),
893 		ImageID("I_CIRCLE"),
894 		ImageID("I_SQUARE"),
895 		ImageID("I_TRIANGLE"),
896 	};
897 
898 	Spinner *loadingSpinner = root_->Add(new Spinner(symbols, ARRAY_SIZE(symbols), new AnchorLayoutParams(NONE, NONE, 45, 45, true)));
899 	loadingSpinner_ = loadingSpinner;
900 
901 	loadingBG->SetTag("LoadingBG");
902 	loadingTextView->SetTag("LoadingText");
903 	loadingSpinner->SetTag("LoadingSpinner");
904 
905 	// Don't really need this, and it creates a lot of strings to translate...
906 	loadingTextView->SetVisibility(V_GONE);
907 	loadingTextView->SetShadow(true);
908 
909 	loadingViewColor_ = loadingSpinner->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut));
910 	loadingViewColor_->SetCallback([loadingBG, loadingTextView, loadingSpinner](View *v, uint32_t c) {
911 		loadingBG->SetColor(c & 0xFFC0C0C0);
912 		loadingTextView->SetTextColor(c);
913 		loadingSpinner->SetColor(alphaMul(c, 0.7f));
914 	});
915 	loadingViewColor_->Persist();
916 
917 	// We start invisible here, in case of recreated views.
918 	loadingViewVisible_ = loadingSpinner->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut));
919 	loadingViewVisible_->Persist();
920 	loadingViewVisible_->Finish.Add([loadingBG, loadingSpinner](EventParams &p) {
921 		loadingBG->SetVisibility(p.v->GetVisibility());
922 
923 		// If we just became invisible, flush BGs since we don't need them anymore.
924 		// Saves some VRAM for the game, but don't do it before we fade out...
925 		if (p.v->GetVisibility() == V_INVISIBLE) {
926 			g_gameInfoCache->FlushBGs();
927 			// And we can go away too.  This means the tween will never run again.
928 			loadingBG->SetVisibility(V_GONE);
929 			loadingSpinner->SetVisibility(V_GONE);
930 		}
931 		return EVENT_DONE;
932 	});
933 }
934 
OnDevTools(UI::EventParams & params)935 UI::EventReturn EmuScreen::OnDevTools(UI::EventParams &params) {
936 	auto dev = GetI18NCategory("Developer");
937 	DevMenu *devMenu = new DevMenu(dev);
938 	if (params.v)
939 		devMenu->SetPopupOrigin(params.v);
940 	screenManager()->push(devMenu);
941 	return UI::EVENT_DONE;
942 }
943 
OnDisableCardboard(UI::EventParams & params)944 UI::EventReturn EmuScreen::OnDisableCardboard(UI::EventParams &params) {
945 	g_Config.bEnableCardboardVR = false;
946 	return UI::EVENT_DONE;
947 }
948 
OnChat(UI::EventParams & params)949 UI::EventReturn EmuScreen::OnChat(UI::EventParams &params) {
950 	if (chatButton_ != nullptr && chatButton_->GetVisibility() == UI::V_VISIBLE) {
951 		chatButton_->SetVisibility(UI::V_GONE);
952 	}
953 	if (chatMenu_ != nullptr) {
954 		chatMenu_->SetVisibility(UI::V_VISIBLE);
955 
956 #if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(SDL)
957 		UI::EnableFocusMovement(true);
958 		root_->SetDefaultFocusView(chatMenu_);
959 
960 		chatMenu_->SetFocus();
961 		UI::View *focused = UI::GetFocusedView();
962 		if (focused) {
963 			root_->SubviewFocused(focused);
964 		}
965 #endif
966 	}
967 	return UI::EVENT_DONE;
968 }
969 
OnResume(UI::EventParams & params)970 UI::EventReturn EmuScreen::OnResume(UI::EventParams &params) {
971 	if (coreState == CoreState::CORE_RUNTIME_ERROR) {
972 		// Force it!
973 		Memory::MemFault_IgnoreLastCrash();
974 		coreState = CoreState::CORE_RUNNING;
975 	}
976 	return UI::EVENT_DONE;
977 }
978 
OnReset(UI::EventParams & params)979 UI::EventReturn EmuScreen::OnReset(UI::EventParams &params) {
980 	if (coreState == CoreState::CORE_RUNTIME_ERROR) {
981 		NativeMessageReceived("reset", "");
982 	}
983 	return UI::EVENT_DONE;
984 }
985 
update()986 void EmuScreen::update() {
987 	using namespace UI;
988 
989 	UIScreen::update();
990 	onScreenMessagesView_->SetVisibility(g_Config.bShowOnScreenMessages ? V_VISIBLE : V_GONE);
991 	resumeButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR && Memory::MemFault_MayBeResumable() ? V_VISIBLE : V_GONE);
992 	resetButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);
993 
994 	if (chatButton_ && chatMenu_) {
995 		if (chatMenu_->GetVisibility() != V_GONE) {
996 			chatMessages_ = GetChatMessageCount();
997 			newChatMessages_ = 0;
998 		} else {
999 			int diff = GetChatMessageCount() - chatMessages_;
1000 			// Cap the count at 50.
1001 			newChatMessages_ = diff > 50 ? 50 : diff;
1002 		}
1003 	}
1004 
1005 	if (bootPending_) {
1006 		bootGame(gamePath_);
1007 	}
1008 
1009 	// Simply forcibly update to the current screen size every frame. Doesn't cost much.
1010 	// If bounds is set to be smaller than the actual pixel resolution of the display, respect that.
1011 	// TODO: Should be able to use g_dpi_scale here instead. Might want to store the dpi scale in the UI context too.
1012 
1013 #ifndef _WIN32
1014 	const Bounds &bounds = screenManager()->getUIContext()->GetBounds();
1015 	PSP_CoreParameter().pixelWidth = pixel_xres * bounds.w / dp_xres;
1016 	PSP_CoreParameter().pixelHeight = pixel_yres * bounds.h / dp_yres;
1017 #endif
1018 
1019 	if (!invalid_) {
1020 		UpdateUIState(coreState != CORE_RUNTIME_ERROR ? UISTATE_INGAME : UISTATE_EXCEPTION);
1021 	}
1022 
1023 	if (errorMessage_.size()) {
1024 		auto err = GetI18NCategory("Error");
1025 		std::string errLoadingFile = gamePath_.ToVisualString() + "\n";
1026 		errLoadingFile.append(err->T("Error loading file", "Could not load game"));
1027 		errLoadingFile.append(" ");
1028 		errLoadingFile.append(err->T(errorMessage_.c_str()));
1029 
1030 		screenManager()->push(new PromptScreen(errLoadingFile, "OK", ""));
1031 		errorMessage_ = "";
1032 		quit_ = true;
1033 		return;
1034 	}
1035 
1036 	if (invalid_)
1037 		return;
1038 
1039 	controlMapper_.Update();
1040 
1041 	if (pauseTrigger_) {
1042 		pauseTrigger_ = false;
1043 		screenManager()->push(new GamePauseScreen(gamePath_));
1044 	}
1045 
1046 	if (saveStatePreview_ && !bootPending_) {
1047 		int currentSlot = SaveState::GetCurrentSlot();
1048 		if (saveStateSlot_ != currentSlot) {
1049 			saveStateSlot_ = currentSlot;
1050 
1051 			Path fn;
1052 			if (SaveState::HasSaveInSlot(gamePath_, currentSlot)) {
1053 				fn = SaveState::GenerateSaveSlotFilename(gamePath_, currentSlot, SaveState::SCREENSHOT_EXTENSION);
1054 			}
1055 
1056 			saveStatePreview_->SetFilename(fn);
1057 			if (!fn.empty()) {
1058 				saveStatePreview_->SetVisibility(UI::V_VISIBLE);
1059 				saveStatePreviewShownTime_ = time_now_d();
1060 			} else {
1061 				saveStatePreview_->SetVisibility(UI::V_GONE);
1062 			}
1063 		}
1064 
1065 		if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {
1066 			double endTime = saveStatePreviewShownTime_ + 2.0;
1067 			float alpha = clamp_value((endTime - time_now_d()) * 4.0, 0.0, 1.0);
1068 			saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha));
1069 
1070 			if (time_now_d() - saveStatePreviewShownTime_ > 2) {
1071 				saveStatePreview_->SetVisibility(UI::V_GONE);
1072 			}
1073 		}
1074 	}
1075 
1076 }
1077 
checkPowerDown()1078 void EmuScreen::checkPowerDown() {
1079 	if (coreState == CORE_POWERDOWN && !PSP_IsIniting()) {
1080 		if (PSP_IsInited()) {
1081 			PSP_Shutdown();
1082 		}
1083 		INFO_LOG(SYSTEM, "SELF-POWERDOWN!");
1084 		screenManager()->switchScreen(new MainScreen());
1085 		bootPending_ = false;
1086 		invalid_ = true;
1087 	}
1088 }
1089 
DrawDebugStats(DrawBuffer * draw2d,const Bounds & bounds)1090 static void DrawDebugStats(DrawBuffer *draw2d, const Bounds &bounds) {
1091 	FontID ubuntu24("UBUNTU24");
1092 
1093 	float left = std::max(bounds.w / 2 - 20.0f, 550.0f);
1094 	float right = bounds.w - left - 20.0f;
1095 
1096 	char statbuf[4096];
1097 	__DisplayGetDebugStats(statbuf, sizeof(statbuf));
1098 	draw2d->SetFontScale(.7f, .7f);
1099 	draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + 11, bounds.y + 31, left, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT);
1100 	draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + 10, bounds.y + 30, left, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT);
1101 
1102 	__SasGetDebugStats(statbuf, sizeof(statbuf));
1103 	draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + left + 21, bounds.y + 31, right, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT);
1104 	draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + left + 20, bounds.y + 30, right, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT);
1105 	draw2d->SetFontScale(1.0f, 1.0f);
1106 }
1107 
CPUCoreAsString(int core)1108 static const char *CPUCoreAsString(int core) {
1109 	switch (core) {
1110 	case 0: return "Interpreter";
1111 	case 1: return "JIT";
1112 	case 2: return "IR Interpreter";
1113 	default: return "N/A";
1114 	}
1115 }
1116 
DrawCrashDump(UIContext * ctx)1117 static void DrawCrashDump(UIContext *ctx) {
1118 	const ExceptionInfo &info = Core_GetExceptionInfo();
1119 
1120 	FontID ubuntu24("UBUNTU24");
1121 	char statbuf[4096];
1122 	char versionString[256];
1123 	snprintf(versionString, sizeof(versionString), "%s", PPSSPP_GIT_VERSION);
1124 
1125 	// TODO: Draw a lot more information. Full register set, and so on.
1126 
1127 #ifdef _DEBUG
1128 	char build[] = "debug";
1129 #else
1130 	char build[] = "release";
1131 #endif
1132 
1133 	std::string sysName = System_GetProperty(SYSPROP_NAME);
1134 	int sysVersion = System_GetPropertyInt(SYSPROP_SYSTEMVERSION);
1135 
1136 	// First column
1137 	ctx->Flush();
1138 	int x = 20 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT);
1139 	int y = 50 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP);
1140 
1141 	int columnWidth = (ctx->GetBounds().w - x - 10) / 2;
1142 	int height = ctx->GetBounds().h;
1143 
1144 	ctx->PushScissor(Bounds(x, y, columnWidth, height));
1145 
1146 	snprintf(statbuf, sizeof(statbuf), R"(%s
1147 %s (%s)
1148 %s (%s)
1149 %s v%d (%s)
1150 )",
1151 		ExceptionTypeAsString(info.type),
1152 		g_paramSFO.GetDiscID().c_str(), g_paramSFO.GetValueString("TITLE").c_str(),
1153 		versionString, build,
1154 		sysName.c_str(), sysVersion, GetCompilerABI()
1155 	);
1156 
1157 	ctx->Draw()->SetFontScale(.7f, .7f);
1158 	ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
1159 	y += 140;
1160 
1161 	if (info.type == ExceptionType::MEMORY) {
1162 		snprintf(statbuf, sizeof(statbuf), R"(
1163 Access: %s at %08x
1164 PC: %08x
1165 %s)",
1166 			MemoryExceptionTypeAsString(info.memory_type),
1167 			info.address,
1168 			info.pc,
1169 			info.info.c_str());
1170 		ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
1171 		y += 180;
1172 	} else if (info.type == ExceptionType::BAD_EXEC_ADDR) {
1173 		snprintf(statbuf, sizeof(statbuf), R"(
1174 Destination: %s to %08x
1175 PC: %08x)",
1176 			ExecExceptionTypeAsString(info.exec_type),
1177 			info.address,
1178 			info.pc);
1179 		ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
1180 		y += 180;
1181 	} else {
1182 		snprintf(statbuf, sizeof(statbuf), R"(
1183 BREAK
1184 )");
1185 		ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
1186 		y += 180;
1187 	}
1188 
1189 	std::string kernelState = __KernelStateSummary();
1190 
1191 	ctx->Draw()->DrawTextShadow(ubuntu24, kernelState.c_str(), x, y, 0xFFFFFFFF);
1192 
1193 	ctx->PopScissor();
1194 
1195 	// Draw some additional stuff to the right.
1196 
1197 	x += columnWidth + 10;
1198 	y = 50;
1199 	snprintf(statbuf, sizeof(statbuf),
1200 		"CPU Core: %s (flags: %08x)\n"
1201 		"Locked CPU freq: %d MHz\n"
1202 		"Cheats: %s, Plugins: %s\n",
1203 		CPUCoreAsString(g_Config.iCpuCore), g_Config.uJitDisableFlags,
1204 		g_Config.iLockedCPUSpeed,
1205 		CheatsInEffect() ? "Y" : "N", HLEPlugins::HasEnabled() ? "Y" : "N");
1206 
1207 	ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
1208 }
1209 
DrawAudioDebugStats(DrawBuffer * draw2d,const Bounds & bounds)1210 static void DrawAudioDebugStats(DrawBuffer *draw2d, const Bounds &bounds) {
1211 	FontID ubuntu24("UBUNTU24");
1212 	char statbuf[4096] = { 0 };
1213 	__AudioGetDebugStats(statbuf, sizeof(statbuf));
1214 	draw2d->SetFontScale(0.7f, 0.7f);
1215 	draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT);
1216 	draw2d->DrawTextRect(ubuntu24, statbuf, bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII | FLAG_WRAP_TEXT);
1217 	draw2d->SetFontScale(1.0f, 1.0f);
1218 }
1219 
DrawFPS(DrawBuffer * draw2d,const Bounds & bounds)1220 static void DrawFPS(DrawBuffer *draw2d, const Bounds &bounds) {
1221 	FontID ubuntu24("UBUNTU24");
1222 	float vps, fps, actual_fps;
1223 	__DisplayGetFPS(&vps, &fps, &actual_fps);
1224 	char fpsbuf[256];
1225 	switch (g_Config.iShowFPSCounter) {
1226 	case 1:
1227 		snprintf(fpsbuf, sizeof(fpsbuf), "Speed: %0.1f%%", vps / (59.94f / 100.0f)); break;
1228 	case 2:
1229 		snprintf(fpsbuf, sizeof(fpsbuf), "FPS: %0.1f", actual_fps); break;
1230 	case 3:
1231 		snprintf(fpsbuf, sizeof(fpsbuf), "%0.0f/%0.0f (%0.1f%%)", actual_fps, fps, vps / (59.94f / 100.0f)); break;
1232 	default:
1233 		return;
1234 	}
1235 
1236 	draw2d->SetFontScale(0.7f, 0.7f);
1237 	draw2d->DrawText(ubuntu24, fpsbuf, bounds.x2() - 8, 12, 0xc0000000, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII);
1238 	draw2d->DrawText(ubuntu24, fpsbuf, bounds.x2() - 10, 10, 0xFF3fFF3f, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII);
1239 	draw2d->SetFontScale(1.0f, 1.0f);
1240 }
1241 
DrawFrameTimes(UIContext * ctx,const Bounds & bounds)1242 static void DrawFrameTimes(UIContext *ctx, const Bounds &bounds) {
1243 	FontID ubuntu24("UBUNTU24");
1244 	int valid, pos;
1245 	double *sleepHistory;
1246 	double *history = __DisplayGetFrameTimes(&valid, &pos, &sleepHistory);
1247 	int scale = 7000;
1248 	int width = 600;
1249 
1250 	ctx->Flush();
1251 	ctx->BeginNoTex();
1252 	int bottom = bounds.y2();
1253 	for (int i = 0; i < valid; ++i) {
1254 		double activeTime = history[i] - sleepHistory[i];
1255 		ctx->Draw()->vLine(bounds.x + i, bottom, bottom - activeTime * scale, 0xFF3FFF3F);
1256 		ctx->Draw()->vLine(bounds.x + i, bottom - activeTime * scale, bottom - history[i] * scale, 0x7F3FFF3F);
1257 	}
1258 	ctx->Draw()->vLine(bounds.x + pos, bottom, bottom - 512, 0xFFff3F3f);
1259 
1260 	ctx->Draw()->hLine(bounds.x, bottom - 0.0333 * scale, bounds.x + width, 0xFF3f3Fff);
1261 	ctx->Draw()->hLine(bounds.x, bottom - 0.0167 * scale, bounds.x + width, 0xFF3f3Fff);
1262 
1263 	ctx->Flush();
1264 	ctx->Begin();
1265 	ctx->Draw()->SetFontScale(0.5f, 0.5f);
1266 	ctx->Draw()->DrawText(ubuntu24, "33.3ms", bounds.x + width, bottom - 0.0333 * scale, 0xFF3f3Fff, ALIGN_BOTTOMLEFT | FLAG_DYNAMIC_ASCII);
1267 	ctx->Draw()->DrawText(ubuntu24, "16.7ms", bounds.x + width, bottom - 0.0167 * scale, 0xFF3f3Fff, ALIGN_BOTTOMLEFT | FLAG_DYNAMIC_ASCII);
1268 	ctx->Draw()->SetFontScale(1.0f, 1.0f);
1269 }
1270 
preRender()1271 void EmuScreen::preRender() {
1272 	using namespace Draw;
1273 	DrawContext *draw = screenManager()->getDrawContext();
1274 	draw->BeginFrame();
1275 	// Here we do NOT bind the backbuffer or clear the screen, unless non-buffered.
1276 	// The emuscreen is different than the others - we really want to allow the game to render to framebuffers
1277 	// before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render
1278 	// targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here.
1279 	// We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)...
1280 	// We do, however, start the frame in other ways.
1281 
1282 	bool useBufferedRendering = g_Config.iRenderingMode != FB_NON_BUFFERED_MODE;
1283 	if ((!useBufferedRendering && !g_Config.bSoftwareRendering) || Core_IsStepping()) {
1284 		// We need to clear here already so that drawing during the frame is done on a clean slate.
1285 		if (Core_IsStepping() && gpuStats.numFlips != 0) {
1286 			draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_BackBuffer");
1287 		} else {
1288 			draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "EmuScreen_BackBuffer");
1289 		}
1290 
1291 		Viewport viewport;
1292 		viewport.TopLeftX = 0;
1293 		viewport.TopLeftY = 0;
1294 		viewport.Width = pixel_xres;
1295 		viewport.Height = pixel_yres;
1296 		viewport.MaxDepth = 1.0;
1297 		viewport.MinDepth = 0.0;
1298 		draw->SetViewports(1, &viewport);
1299 	}
1300 	draw->SetTargetSize(pixel_xres, pixel_yres);
1301 }
1302 
postRender()1303 void EmuScreen::postRender() {
1304 	Draw::DrawContext *draw = screenManager()->getDrawContext();
1305 	if (!draw)
1306 		return;
1307 	if (stopRender_)
1308 		draw->WipeQueue();
1309 	draw->EndFrame();
1310 }
1311 
render()1312 void EmuScreen::render() {
1313 	using namespace Draw;
1314 
1315 	DrawContext *thin3d = screenManager()->getDrawContext();
1316 	if (!thin3d)
1317 		return;  // shouldn't really happen but I've seen a suspicious stack trace..
1318 
1319 	if (invalid_) {
1320 		// Loading, or after shutdown?
1321 		if (loadingTextView_->GetVisibility() == UI::V_VISIBLE)
1322 			loadingTextView_->SetText(PSP_GetLoading());
1323 
1324 		// It's possible this might be set outside PSP_RunLoopFor().
1325 		// In this case, we need to double check it here.
1326 		checkPowerDown();
1327 		thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_Invalid");
1328 		renderUI();
1329 		return;
1330 	}
1331 
1332 	// Freeze-frame functionality (loads a savestate on every frame).
1333 	if (PSP_CoreParameter().freezeNext) {
1334 		PSP_CoreParameter().frozen = true;
1335 		PSP_CoreParameter().freezeNext = false;
1336 		SaveState::SaveToRam(freezeState_);
1337 	} else if (PSP_CoreParameter().frozen) {
1338 		std::string errorString;
1339 		if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {
1340 			ERROR_LOG(SAVESTATE, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());
1341 			PSP_CoreParameter().frozen = false;
1342 		}
1343 	}
1344 
1345 	Core_UpdateDebugStats(g_Config.bShowDebugStats || g_Config.bLogFrameDrops);
1346 
1347 	PSP_BeginHostFrame();
1348 
1349 	PSP_RunLoopWhileState();
1350 
1351 	// Hopefully coreState is now CORE_NEXTFRAME
1352 	switch (coreState) {
1353 	case CORE_NEXTFRAME:
1354 		// Reached the end of the frame, all good. Set back to running for the next frame
1355 		coreState = CORE_RUNNING;
1356 		break;
1357 	case CORE_STEPPING:
1358 	case CORE_RUNTIME_ERROR:
1359 	{
1360 		// If there's an exception, display information.
1361 		const ExceptionInfo &info = Core_GetExceptionInfo();
1362 		if (info.type != ExceptionType::NONE) {
1363 			// Clear to blue background screen
1364 			bool dangerousSettings = !Reporting::IsSupported();
1365 			uint32_t color = dangerousSettings ? 0xFF900050 : 0xFF900000;
1366 			thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE, color }, "EmuScreen_RuntimeError");
1367 			// The info is drawn later in renderUI
1368 		} else {
1369 			// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.
1370 			// This won't work in non-buffered, but that's fine.
1371 			thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_Stepping");
1372 			// Just to make sure.
1373 			if (PSP_IsInited()) {
1374 				gpu->CopyDisplayToOutput(true);
1375 			}
1376 		}
1377 		break;
1378 	}
1379 	default:
1380 		// Didn't actually reach the end of the frame, ran out of the blockTicks cycles.
1381 		// In this case we need to bind and wipe the backbuffer, at least.
1382 		// It's possible we never ended up outputted anything - make sure we have the backbuffer cleared
1383 		thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_NoFrame");
1384 		break;
1385 	}
1386 
1387 	checkPowerDown();
1388 
1389 	PSP_EndHostFrame();
1390 	if (invalid_)
1391 		return;
1392 
1393 	if (hasVisibleUI()) {
1394 		// In most cases, this should already be bound and a no-op.
1395 		thin3d->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::DONT_CARE, RPAction::DONT_CARE }, "EmuScreen_UI");
1396 		cardboardDisableButton_->SetVisibility(g_Config.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE);
1397 		screenManager()->getUIContext()->BeginFrame();
1398 		renderUI();
1399 	}
1400 }
1401 
hasVisibleUI()1402 bool EmuScreen::hasVisibleUI() {
1403 	// Regular but uncommon UI.
1404 	if (saveStatePreview_->GetVisibility() != UI::V_GONE || loadingSpinner_->GetVisibility() == UI::V_VISIBLE)
1405 		return true;
1406 	if (!osm.IsEmpty() || g_Config.bShowTouchControls || g_Config.iShowFPSCounter != 0)
1407 		return true;
1408 	if (g_Config.bEnableCardboardVR || g_Config.bEnableNetworkChat)
1409 		return true;
1410 	// Debug UI.
1411 	if (g_Config.bShowDebugStats || g_Config.bShowDeveloperMenu || g_Config.bShowAudioDebug || g_Config.bShowFrameProfiler)
1412 		return true;
1413 
1414 	// Exception information.
1415 	if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) {
1416 		return true;
1417 	}
1418 
1419 	return false;
1420 }
1421 
renderUI()1422 void EmuScreen::renderUI() {
1423 	using namespace Draw;
1424 
1425 	DrawContext *thin3d = screenManager()->getDrawContext();
1426 	UIContext *ctx = screenManager()->getUIContext();
1427 	ctx->BeginFrame();
1428 	// This sets up some important states but not the viewport.
1429 	ctx->Begin();
1430 
1431 	Viewport viewport;
1432 	viewport.TopLeftX = 0;
1433 	viewport.TopLeftY = 0;
1434 	viewport.Width = pixel_xres;
1435 	viewport.Height = pixel_yres;
1436 	viewport.MaxDepth = 1.0;
1437 	viewport.MinDepth = 0.0;
1438 	thin3d->SetViewports(1, &viewport);
1439 
1440 	if (root_) {
1441 		UI::LayoutViewHierarchy(*ctx, root_, false);
1442 		root_->Draw(*ctx);
1443 	}
1444 
1445 	DrawBuffer *draw2d = ctx->Draw();
1446 
1447 	if (g_Config.bShowDebugStats && !invalid_) {
1448 		DrawDebugStats(draw2d, ctx->GetLayoutBounds());
1449 	}
1450 
1451 	if (g_Config.bShowAudioDebug && !invalid_) {
1452 		DrawAudioDebugStats(draw2d, ctx->GetLayoutBounds());
1453 	}
1454 
1455 	if (g_Config.iShowFPSCounter && !invalid_) {
1456 		DrawFPS(draw2d, ctx->GetLayoutBounds());
1457 	}
1458 
1459 	if (g_Config.bDrawFrameGraph && !invalid_) {
1460 		DrawFrameTimes(ctx, ctx->GetLayoutBounds());
1461 	}
1462 
1463 #if !PPSSPP_PLATFORM(UWP)
1464 	if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && g_Config.bShowAllocatorDebug) {
1465 		DrawAllocatorVis(ctx, gpu);
1466 	}
1467 
1468 	if (g_Config.iGPUBackend == (int)GPUBackend::VULKAN && g_Config.bShowGpuProfile) {
1469 		DrawGPUProfilerVis(ctx, gpu);
1470 	}
1471 
1472 #endif
1473 
1474 #ifdef USE_PROFILER
1475 	if (g_Config.bShowFrameProfiler && !invalid_) {
1476 		DrawProfile(*ctx);
1477 	}
1478 #endif
1479 
1480 	if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) {
1481 		const ExceptionInfo &info = Core_GetExceptionInfo();
1482 		if (info.type != ExceptionType::NONE) {
1483 			DrawCrashDump(ctx);
1484 		}
1485 	}
1486 
1487 	ctx->Flush();
1488 }
1489 
autoLoad()1490 void EmuScreen::autoLoad() {
1491 	int autoSlot = -1;
1492 
1493 	//check if save state has save, if so, load
1494 	switch (g_Config.iAutoLoadSaveState) {
1495 	case (int)AutoLoadSaveState::OFF: // "AutoLoad Off"
1496 		return;
1497 	case (int)AutoLoadSaveState::OLDEST: // "Oldest Save"
1498 		autoSlot = SaveState::GetOldestSlot(gamePath_);
1499 		break;
1500 	case (int)AutoLoadSaveState::NEWEST: // "Newest Save"
1501 		autoSlot = SaveState::GetNewestSlot(gamePath_);
1502 		break;
1503 	default: // try the specific save state slot specified
1504 		autoSlot = (SaveState::HasSaveInSlot(gamePath_, g_Config.iAutoLoadSaveState - 3)) ? (g_Config.iAutoLoadSaveState - 3) : -1;
1505 		break;
1506 	}
1507 
1508 	if (g_Config.iAutoLoadSaveState && autoSlot != -1) {
1509 		SaveState::LoadSlot(gamePath_, autoSlot, &AfterSaveStateAction);
1510 		g_Config.iCurrentStateSlot = autoSlot;
1511 	}
1512 }
1513 
resized()1514 void EmuScreen::resized() {
1515 	RecreateViews();
1516 }
1517