Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
hrydgard
GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/EmuScreen.cpp
5664 views
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 <functional>
21
22
using namespace std::placeholders;
23
24
#include "Common/Render/TextureAtlas.h"
25
#include "Common/GPU/OpenGL/GLFeatures.h"
26
#include "Common/File/FileUtil.h"
27
#include "Common/File/VFS/VFS.h"
28
#include "Common/Log/LogManager.h"
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
#include "Common/UI/AsyncImageFileView.h"
35
#include "Common/VR/PPSSPPVR.h"
36
37
#include "Common/Data/Text/I18n.h"
38
#include "Common/Input/InputState.h"
39
#include "Common/Log.h"
40
#include "Common/System/Display.h"
41
#include "Common/System/System.h"
42
#include "Common/System/Request.h"
43
#include "Common/System/OSD.h"
44
#include "Common/Profiler/Profiler.h"
45
#include "Common/Math/curves.h"
46
#include "Common/StringUtils.h"
47
#include "Common/TimeUtil.h"
48
49
#ifndef MOBILE_DEVICE
50
#include "Core/AVIDump.h"
51
#endif
52
#include "Core/Config.h"
53
#include "Core/ConfigValues.h"
54
#include "Core/CoreTiming.h"
55
#include "Core/CoreParameter.h"
56
#include "Core/Core.h"
57
#include "Core/KeyMap.h"
58
#include "Core/MemFault.h"
59
#include "Core/Reporting.h"
60
#include "Core/System.h"
61
#include "GPU/Common/PresentationCommon.h"
62
#include "Core/FileSystems/VirtualDiscFileSystem.h"
63
#include "GPU/GPUState.h"
64
#include "GPU/GPUCommon.h"
65
#include "GPU/Common/FramebufferManagerCommon.h"
66
#if !PPSSPP_PLATFORM(UWP)
67
#include "GPU/Vulkan/DebugVisVulkan.h"
68
#endif
69
#include "Core/MIPS/MIPS.h"
70
#include "Core/HLE/sceCtrl.h"
71
#include "Core/HLE/sceSas.h"
72
#include "Core/HLE/sceNet.h"
73
#include "Core/HLE/sceDisplay.h"
74
#include "Core/HLE/sceNetAdhoc.h"
75
#include "Core/Debugger/SymbolMap.h"
76
#include "Core/RetroAchievements.h"
77
#include "Core/SaveState.h"
78
#include "UI/ImDebugger/ImDebugger.h"
79
#include "Core/HLE/__sceAudio.h"
80
// #include "Core/HLE/proAdhoc.h"
81
#include "Core/HW/Display.h"
82
83
#include "UI/BackgroundAudio.h"
84
#include "UI/OnScreenDisplay.h"
85
#include "UI/GamepadEmu.h"
86
#include "UI/PauseScreen.h"
87
#include "UI/MainScreen.h"
88
#include "UI/Background.h"
89
#include "UI/EmuScreen.h"
90
#include "UI/DevScreens.h"
91
#include "UI/GameInfoCache.h"
92
#include "UI/BaseScreens.h"
93
#include "UI/ControlMappingScreen.h"
94
#include "UI/DisplayLayoutScreen.h"
95
#include "UI/GameSettingsScreen.h"
96
#include "UI/ProfilerDraw.h"
97
#include "UI/DiscordIntegration.h"
98
#include "UI/ChatScreen.h"
99
#include "UI/DebugOverlay.h"
100
101
#include "ext/imgui/imgui.h"
102
#include "ext/imgui/imgui_internal.h"
103
#include "ext/imgui/imgui_impl_thin3d.h"
104
#include "ext/imgui/imgui_impl_platform.h"
105
106
#include "Core/Reporting.h"
107
108
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
109
#include "Windows/MainWindow.h"
110
#endif
111
112
#ifndef MOBILE_DEVICE
113
static AVIDump avi;
114
#endif
115
116
extern bool g_TakeScreenshot;
117
118
static void AssertCancelCallback(const char *message, void *userdata) {
119
NOTICE_LOG(Log::CPU, "Broke after assert: %s", message);
120
Core_Break(BreakReason::AssertChoice);
121
g_Config.bShowImDebugger = true;
122
123
EmuScreen *emuScreen = (EmuScreen *)userdata;
124
emuScreen->SendImDebuggerCommand(ImCommand{ ImCmd::SHOW_IN_CPU_DISASM, currentMIPS->pc });
125
}
126
127
// Handles control rotation due to internal screen rotation.
128
static void SetPSPAnalog(int iInternalScreenRotation, int stick, float x, float y) {
129
switch (iInternalScreenRotation) {
130
case ROTATION_LOCKED_HORIZONTAL:
131
// Standard rotation. No change.
132
break;
133
case ROTATION_LOCKED_HORIZONTAL180:
134
x = -x;
135
y = -y;
136
break;
137
case ROTATION_LOCKED_VERTICAL:
138
{
139
float new_y = y;
140
x = -y;
141
y = new_y;
142
break;
143
}
144
case ROTATION_LOCKED_VERTICAL180:
145
{
146
float new_y = -x;
147
x = y;
148
y = new_y;
149
break;
150
}
151
default:
152
break;
153
}
154
__CtrlSetAnalogXY(stick, x, y);
155
}
156
157
EmuScreen::EmuScreen(const Path &filename)
158
: gamePath_(filename) {
159
saveStateSlot_ = SaveState::GetCurrentSlot();
160
controlMapper_.SetCallbacks(
161
std::bind(&EmuScreen::onVKey, this, _1, _2),
162
std::bind(&EmuScreen::onVKeyAnalog, this, _1, _2),
163
[](uint32_t bitsToSet, uint32_t bitsToClear) {
164
__CtrlUpdateButtons(bitsToSet, bitsToClear);
165
},
166
&SetPSPAnalog,
167
nullptr);
168
169
_dbg_assert_(coreState == CORE_POWERDOWN);
170
171
OnDevMenu.Handle(this, &EmuScreen::OnDevTools);
172
OnChatMenu.Handle(this, &EmuScreen::OnChat);
173
174
// Usually, we don't want focus movement enabled on this screen, so disable on start.
175
// Only if you open chat or dev tools do we want it to start working.
176
UI::EnableFocusMovement(false);
177
}
178
179
bool EmuScreen::bootAllowStorage(const Path &filename) {
180
// No permissions needed. The easy life.
181
if (filename.Type() == PathType::HTTP)
182
return true;
183
184
if (!System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS))
185
return true;
186
187
PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);
188
switch (status) {
189
case PERMISSION_STATUS_UNKNOWN:
190
System_AskForPermission(SYSTEM_PERMISSION_STORAGE);
191
return false;
192
193
case PERMISSION_STATUS_DENIED:
194
if (!bootPending_) {
195
screenManager()->switchScreen(new MainScreen());
196
}
197
return false;
198
199
case PERMISSION_STATUS_PENDING:
200
// Keep waiting.
201
return false;
202
203
case PERMISSION_STATUS_GRANTED:
204
return true;
205
}
206
207
_assert_(false);
208
return false;
209
}
210
211
void EmuScreen::ProcessGameBoot(const Path &filename) {
212
if (!bootPending_ && !readyToFinishBoot_) {
213
// Nothing to do.
214
return;
215
}
216
217
if (!root_) {
218
// Views not created yet, wait until they are. Not sure if this can actually happen
219
// but crash reports seem to indicate it.
220
return;
221
}
222
223
// Check permission status first, in case we came from a shortcut.
224
if (!bootAllowStorage(filename)) {
225
return;
226
}
227
228
if (Achievements::IsBlockingExecution()) {
229
// Keep waiting.
230
return;
231
}
232
233
if (readyToFinishBoot_) {
234
// Finish booting.
235
bootComplete();
236
readyToFinishBoot_ = false;
237
return;
238
}
239
240
std::string error_string = "(unknown error)";
241
const BootState state = PSP_InitUpdate(&error_string);
242
243
if (state == BootState::Off && screenManager()->topScreen() != this) {
244
// Don't kick off a new boot if we're not on top.
245
return;
246
}
247
248
switch (state) {
249
case BootState::Booting:
250
// Keep trying.
251
return;
252
case BootState::Failed:
253
// Failure.
254
_dbg_assert_(!error_string.empty());
255
g_BackgroundAudio.SetGame(Path());
256
bootPending_ = false;
257
errorMessage_ = error_string;
258
ERROR_LOG(Log::Boot, "Boot failed: %s", errorMessage_.c_str());
259
return;
260
case BootState::Complete:
261
// Done booting!
262
g_BackgroundAudio.SetGame(Path());
263
bootPending_ = false;
264
errorMessage_.clear();
265
266
if (PSP_CoreParameter().startBreak) {
267
coreState = CORE_STEPPING_CPU;
268
System_Notify(SystemNotification::DEBUG_MODE_CHANGE);
269
} else {
270
coreState = CORE_RUNNING_CPU;
271
}
272
273
Achievements::Initialize();
274
275
readyToFinishBoot_ = true;
276
return;
277
case BootState::Off:
278
// Gotta start the boot process! Continue below.
279
break;
280
}
281
282
SetAssertCancelCallback(&AssertCancelCallback, this);
283
284
if (!g_Config.bShaderCache) {
285
// Only developers should ever see this.
286
g_OSD.Show(OSDType::MESSAGE_WARNING, "Shader cache is disabled (developer)");
287
}
288
289
if (g_Config.bTiltInputEnabled && g_Config.iTiltInputType != 0) {
290
auto co = GetI18NCategory(I18NCat::CONTROLS);
291
auto di = GetI18NCategory(I18NCat::DIALOG);
292
g_OSD.Show(OSDType::MESSAGE_INFO, ApplySafeSubstitutions("%1: %2", co->T("Tilt control"), di->T("Enabled")), "", "I_CONTROLLER", 2.5f, "tilt");
293
}
294
295
currentMIPS = &mipsr4k;
296
297
CoreParameter coreParam{};
298
coreParam.cpuCore = (CPUCore)g_Config.iCpuCore;
299
coreParam.gpuCore = GPUCORE_GLES;
300
switch (GetGPUBackend()) {
301
case GPUBackend::DIRECT3D11:
302
coreParam.gpuCore = GPUCORE_DIRECTX11;
303
break;
304
#if !PPSSPP_PLATFORM(UWP)
305
#if PPSSPP_API(ANY_GL)
306
case GPUBackend::OPENGL:
307
coreParam.gpuCore = GPUCORE_GLES;
308
break;
309
#endif
310
case GPUBackend::VULKAN:
311
coreParam.gpuCore = GPUCORE_VULKAN;
312
break;
313
#endif
314
}
315
316
// Preserve the existing graphics context.
317
coreParam.graphicsContext = PSP_CoreParameter().graphicsContext;
318
coreParam.enableSound = g_Config.bEnableSound;
319
coreParam.fileToStart = filename;
320
coreParam.mountIso.clear();
321
coreParam.mountRoot = g_Config.mountRoot;
322
coreParam.startBreak = !g_Config.bAutoRun;
323
coreParam.headLess = false;
324
325
if (g_Config.iInternalResolution == 0) {
326
coreParam.renderWidth = g_display.pixel_xres;
327
coreParam.renderHeight = g_display.pixel_yres;
328
} else {
329
if (g_Config.iInternalResolution < 0)
330
g_Config.iInternalResolution = 1;
331
coreParam.renderWidth = 480 * g_Config.iInternalResolution;
332
coreParam.renderHeight = 272 * g_Config.iInternalResolution;
333
}
334
coreParam.pixelWidth = g_display.pixel_xres;
335
coreParam.pixelHeight = g_display.pixel_yres;
336
337
// PSP_InitStart can't really fail anymore, unless it's called at the wrong time. It just starts the loader thread.
338
if (!PSP_InitStart(coreParam)) {
339
bootPending_ = false;
340
ERROR_LOG(Log::Boot, "InitStart ProcessGameBoot error: %s", errorMessage_.c_str());
341
return;
342
}
343
344
_dbg_assert_(loadingViewVisible_);
345
_dbg_assert_(loadingViewColor_);
346
347
if (loadingViewColor_)
348
loadingViewColor_->Divert(0xFFFFFFFF, 0.75f);
349
if (loadingViewVisible_)
350
loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f);
351
352
screenManager()->getDrawContext()->ResetStats();
353
354
System_PostUIMessage(UIMessage::GAME_SELECTED, filename.c_str());
355
}
356
357
// Only call this on successful boot.
358
void EmuScreen::bootComplete() {
359
__DisplayListenFlip([](void *userdata) {
360
EmuScreen *scr = (EmuScreen *)userdata;
361
scr->HandleFlip();
362
}, (void *)this);
363
364
// Initialize retroachievements, now that we're on the right thread.
365
if (g_Config.bAchievementsEnable) {
366
std::string errorString;
367
Achievements::SetGame(PSP_CoreParameter().fileToStart, PSP_CoreParameter().fileType, PSP_LoadedFile());
368
}
369
370
// We don't want to boot with the wrong game specific config, so wait until info is ready.
371
// TODO: Actually, we read this info again during bootup, so this is not really necessary.
372
auto sc = GetI18NCategory(I18NCat::SCREEN);
373
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
374
375
if (g_paramSFO.IsValid()) {
376
g_Discord.SetPresenceGame(SanitizeString(g_paramSFO.GetValueString("TITLE"), StringRestriction::NoLineBreaksOrSpecials));
377
std::string gameTitle = SanitizeString(g_paramSFO.GetValueString("TITLE"), StringRestriction::NoLineBreaksOrSpecials, 0, 32);
378
std::string id = g_paramSFO.GetValueString("DISC_ID");
379
extraAssertInfoStr_ = id + " " + gameTitle;
380
SetExtraAssertInfo(extraAssertInfoStr_.c_str());
381
SaveState::Rescan(SaveState::GetGamePrefix(g_paramSFO));
382
} else {
383
g_Discord.SetPresenceGame(sc->T("Untitled PSP game"));
384
}
385
386
UpdateUIState(UISTATE_INGAME);
387
System_Notify(SystemNotification::BOOT_DONE);
388
System_Notify(SystemNotification::DISASSEMBLY);
389
390
NOTICE_LOG(Log::Boot, "Booted %s...", PSP_CoreParameter().fileToStart.c_str());
391
if (!Achievements::HardcoreModeActive() && !bootIsReset_) {
392
// Don't auto-load savestates in hardcore mode.
393
AutoLoadSaveState();
394
}
395
396
#ifndef MOBILE_DEVICE
397
if (g_Config.bFirstRun) {
398
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("PressESC", "Press ESC to open the pause menu"));
399
}
400
#endif
401
402
#if !PPSSPP_PLATFORM(UWP)
403
if (GetGPUBackend() == GPUBackend::OPENGL) {
404
const char *renderer = gl_extensions.model;
405
if (strstr(renderer, "Chainfire3D") != 0) {
406
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("Chainfire3DWarning", "WARNING: Chainfire3D detected, may cause problems"), 10.0f);
407
} else if (strstr(renderer, "GLTools") != 0) {
408
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("GLToolsWarning", "WARNING: GLTools detected, may cause problems"), 10.0f);
409
}
410
411
if (g_Config.bGfxDebugOutput) {
412
g_OSD.Show(OSDType::MESSAGE_WARNING, "WARNING: GfxDebugOutput is enabled via ppsspp.ini. Things may be slow.", 10.0f);
413
}
414
}
415
#endif
416
417
if (Core_GetPowerSaving()) {
418
auto sy = GetI18NCategory(I18NCat::SYSTEM);
419
#ifdef __ANDROID__
420
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Android battery save mode is on"), 2.0f, "core_powerSaving");
421
#else
422
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Battery save mode is on"), 2.0f, "core_powerSaving");
423
#endif
424
}
425
426
if (g_Config.bStereoRendering) {
427
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
428
auto di = GetI18NCategory(I18NCat::DIALOG);
429
// Stereo rendering is experimental, so let's notify the user it's being used.
430
// Carefully reuse translations for this rare warning.
431
g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(gr->T("Stereo rendering")) + ": " + std::string(di->T("Enabled")));
432
}
433
434
saveStateSlot_ = SaveState::GetCurrentSlot();
435
436
if (loadingViewColor_)
437
loadingViewColor_->Divert(0x00FFFFFF, 0.2f);
438
if (loadingViewVisible_)
439
loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);
440
441
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
442
g_Config.TimeTracker().Start(gameID);
443
444
bootIsReset_ = false;
445
446
// Reset views in case controls are in a different place.
447
RecreateViews();
448
}
449
450
EmuScreen::~EmuScreen() {
451
if (imguiInited_) {
452
ImGui_ImplThin3d_Shutdown();
453
ImGui::DestroyContext(ctx_);
454
}
455
456
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
457
g_Config.TimeTracker().Stop(gameID);
458
459
// Should not be able to quit during boot, as boot can't be cancelled.
460
_dbg_assert_(!bootPending_);
461
if (!bootPending_) {
462
Achievements::UnloadGame();
463
PSP_Shutdown(true);
464
}
465
466
// If achievements are disabled in the global config, let's shut it down here.
467
if (!g_Config.bAchievementsEnable) {
468
Achievements::Shutdown();
469
}
470
471
_dbg_assert_(coreState == CORE_POWERDOWN);
472
473
System_PostUIMessage(UIMessage::GAME_SELECTED, "");
474
475
g_OSD.ClearAchievementStuff();
476
477
SetExtraAssertInfo(nullptr);
478
SetAssertCancelCallback(nullptr, nullptr);
479
480
g_logManager.DisableOutput(LogOutput::RingBuffer);
481
482
#ifndef MOBILE_DEVICE
483
if (g_Config.bDumpFrames && startDumping_)
484
{
485
avi.Stop();
486
g_OSD.Show(OSDType::MESSAGE_INFO, "AVI Dump stopped.", 2.0f);
487
startDumping_ = false;
488
}
489
#endif
490
491
if (GetUIState() == UISTATE_EXIT)
492
g_Discord.ClearPresence();
493
else
494
g_Discord.SetPresenceMenu();
495
}
496
497
void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) {
498
std::string_view tag = dialog->tag();
499
if (tag == "TextEditPopup") {
500
// Chat message finished.
501
return;
502
}
503
504
// Returning to the PauseScreen, unless we're stepping, means we should go back to controls.
505
if (Core_IsActive()) {
506
UI::EnableFocusMovement(false);
507
}
508
509
// TODO: improve the way with which we got commands from PauseMenu.
510
// DR_CANCEL/DR_BACK means clicked on "continue", DR_OK means clicked on "back to menu",
511
// DR_YES means a message sent to PauseMenu by System_PostUIMessage.
512
if ((result == DR_OK || quit_) && !bootPending_) {
513
screenManager()->switchScreen(new MainScreen());
514
quit_ = false;
515
} else {
516
RecreateViews();
517
}
518
519
SetExtraAssertInfo(extraAssertInfoStr_.c_str());
520
521
// Make sure we re-enable keyboard mode if it was disabled by the dialog, and if needed.
522
lastImguiEnabled_ = false;
523
}
524
525
static void AfterSaveStateAction(SaveState::Status status, std::string_view message) {
526
if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {
527
g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR, message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);
528
}
529
}
530
531
void EmuScreen::focusChanged(ScreenFocusChange focusChange) {
532
Screen::focusChanged(focusChange);
533
534
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
535
if (gameID.empty()) {
536
// startup or shutdown
537
return;
538
}
539
switch (focusChange) {
540
case ScreenFocusChange::FOCUS_LOST_TOP:
541
g_Config.TimeTracker().Stop(gameID);
542
controlMapper_.ReleaseAll();
543
break;
544
case ScreenFocusChange::FOCUS_BECAME_TOP:
545
g_Config.TimeTracker().Start(gameID);
546
break;
547
}
548
}
549
550
void EmuScreen::sendMessage(UIMessage message, const char *value) {
551
// External commands, like from the Windows UI.
552
// This happens on the main thread.
553
if (message == UIMessage::REQUEST_GAME_PAUSE && screenManager()->topScreen() == this) {
554
screenManager()->push(new GamePauseScreen(gamePath_, bootPending_));
555
} else if (message == UIMessage::REQUEST_GAME_STOP) {
556
// We will push MainScreen in update().
557
if (bootPending_) {
558
WARN_LOG(Log::Loader, "Can't stop during a pending boot");
559
return;
560
}
561
// The destructor will take care of shutting down.
562
screenManager()->switchScreen(new MainScreen());
563
} else if (message == UIMessage::REQUEST_GAME_RESET) {
564
if (bootPending_) {
565
WARN_LOG(Log::Loader, "Can't reset during a pending boot");
566
return;
567
}
568
Achievements::UnloadGame();
569
PSP_Shutdown(true);
570
571
// Restart the boot process
572
bootPending_ = true;
573
bootIsReset_ = true;
574
RecreateViews();
575
_dbg_assert_(coreState == CORE_POWERDOWN);
576
if (!PSP_InitStart(PSP_CoreParameter())) {
577
bootPending_ = false;
578
WARN_LOG(Log::Loader, "Error resetting");
579
screenManager()->switchScreen(new MainScreen());
580
return;
581
}
582
} else if (message == UIMessage::REQUEST_GAME_BOOT) {
583
INFO_LOG(Log::Loader, "EmuScreen received REQUEST_GAME_BOOT: %s", value);
584
585
if (bootPending_) {
586
ERROR_LOG(Log::Loader, "Can't boot a new game during a pending boot");
587
return;
588
}
589
// TODO: Ignore or not if it's the same game that's already running?
590
if (gamePath_ == Path(value)) {
591
WARN_LOG(Log::Loader, "Game already running, ignoring");
592
return;
593
}
594
595
// TODO: Create a path first and
596
Path newGamePath(value);
597
598
if (newGamePath.GetFileExtension() == ".ppst") {
599
// TODO: Should verify that it's for the correct game....
600
INFO_LOG(Log::Loader, "New game is a save state - just load it.");
601
SaveState::Load(newGamePath, -1, [](SaveState::Status status, std::string_view message) {
602
Core_Resume();
603
System_Notify(SystemNotification::DISASSEMBLY);
604
});
605
} else {
606
Achievements::UnloadGame();
607
PSP_Shutdown(true);
608
609
// OK, now pop any open settings screens and stuff that are running above us.
610
// Otherwise, we can get strange results with game-specific settings.
611
screenManager()->cancelScreensAbove(this);
612
613
bootPending_ = true;
614
bootIsReset_ = false;
615
gamePath_ = newGamePath;
616
}
617
} else if (message == UIMessage::CONFIG_LOADED) {
618
// In case we need to position touch controls differently.
619
RecreateViews();
620
} else if (message == UIMessage::SHOW_CONTROL_MAPPING && screenManager()->topScreen() == this) {
621
UpdateUIState(UISTATE_PAUSEMENU);
622
screenManager()->push(new ControlMappingScreen(gamePath_));
623
} else if (message == UIMessage::SHOW_DISPLAY_LAYOUT_EDITOR && screenManager()->topScreen() == this) {
624
UpdateUIState(UISTATE_PAUSEMENU);
625
screenManager()->push(new DisplayLayoutScreen(gamePath_));
626
} else if (message == UIMessage::SHOW_SETTINGS && screenManager()->topScreen() == this) {
627
UpdateUIState(UISTATE_PAUSEMENU);
628
screenManager()->push(new GameSettingsScreen(gamePath_));
629
} else if (message == UIMessage::REQUEST_GPU_DUMP_NEXT_FRAME) {
630
if (gpu)
631
gpu->DumpNextFrame();
632
} else if (message == UIMessage::REQUEST_CLEAR_JIT) {
633
if (!bootPending_) {
634
currentMIPS->ClearJitCache();
635
if (PSP_IsInited()) {
636
currentMIPS->UpdateCore((CPUCore)g_Config.iCpuCore);
637
}
638
}
639
} else if (message == UIMessage::WINDOW_MINIMIZED) {
640
if (!strcmp(value, "true")) {
641
gstate_c.skipDrawReason |= SKIPDRAW_WINDOW_MINIMIZED;
642
} else {
643
gstate_c.skipDrawReason &= ~SKIPDRAW_WINDOW_MINIMIZED;
644
}
645
} else if (message == UIMessage::SHOW_CHAT_SCREEN) {
646
if (g_Config.bEnableNetworkChat && !g_Config.bShowImDebugger) {
647
if (!chatButton_)
648
RecreateViews();
649
650
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {
651
// temporary workaround for hotkey its freeze the ui when open chat screen using hotkey and native keyboard is enable
652
if (g_Config.bBypassOSKWithKeyboard) {
653
// TODO: Make translatable.
654
g_OSD.Show(OSDType::MESSAGE_INFO, "Disable \"Use system native keyboard\" to use ctrl + c hotkey", 2.0f);
655
} else {
656
UI::EventParams e{};
657
OnChatMenu.Trigger(e);
658
}
659
} else {
660
UI::EventParams e{};
661
OnChatMenu.Trigger(e);
662
}
663
} else if (!g_Config.bEnableNetworkChat) {
664
if (chatButton_) {
665
RecreateViews();
666
}
667
}
668
} else if (message == UIMessage::APP_RESUMED && screenManager()->topScreen() == this) {
669
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) {
670
if (!KeyMap::IsKeyMapped(DEVICE_ID_PAD_0, VIRTKEY_PAUSE) || !KeyMap::IsKeyMapped(DEVICE_ID_PAD_1, VIRTKEY_PAUSE)) {
671
// If it's a TV (so no built-in back button), and there's no back button mapped to a pad,
672
// use this as the fallback way to get into the menu.
673
// Don't do it on the first resume though, in case we launch directly into emuscreen, like from a frontend - see #18926
674
if (!equals(value, "first")) {
675
screenManager()->push(new GamePauseScreen(gamePath_, bootPending_));
676
}
677
}
678
}
679
} else if (message == UIMessage::REQUEST_PLAY_SOUND) {
680
if (g_Config.bAchievementsSoundEffects && g_Config.bEnableSound) {
681
float achievementVolume = Volume100ToMultiplier(g_Config.iAchievementVolume);
682
// TODO: Handle this some nicer way.
683
if (!strcmp(value, "achievement_unlocked")) {
684
g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume);
685
}
686
if (!strcmp(value, "leaderboard_submitted")) {
687
g_BackgroundAudio.SFX().Play(UI::UISound::LEADERBOARD_SUBMITTED, achievementVolume);
688
}
689
}
690
}
691
}
692
693
bool EmuScreen::UnsyncTouch(const TouchInput &touch) {
694
System_Notify(SystemNotification::ACTIVITY);
695
696
bool ignoreGamepad = false;
697
698
if (chatMenu_ && chatMenu_->GetVisibility() == UI::V_VISIBLE) {
699
// Avoid pressing touch button behind the chat
700
if (chatMenu_->Contains(touch.x, touch.y)) {
701
ignoreGamepad = true;
702
}
703
}
704
705
if (touch.flags & TouchInputFlags::DOWN) {
706
if (!(g_Config.bShowImDebugger && imguiInited_) && !ignoreGamepad) {
707
// This just prevents the gamepad from timing out.
708
GamepadTouch();
709
}
710
}
711
712
if (root_) {
713
UIScreen::UnsyncTouch(touch);
714
}
715
return true;
716
}
717
718
// TODO: We should replace the "fpsLimit" system with a speed factor.
719
static void ShowFpsLimitNotice() {
720
int fpsLimit = 60;
721
722
switch (PSP_CoreParameter().fpsLimit) {
723
case FPSLimit::CUSTOM1:
724
fpsLimit = g_Config.iFpsLimit1;
725
break;
726
case FPSLimit::CUSTOM2:
727
fpsLimit = g_Config.iFpsLimit2;
728
break;
729
default:
730
break;
731
}
732
733
// Now display it.
734
735
char temp[51];
736
snprintf(temp, sizeof(temp), "%d%%", (int)((float)fpsLimit / 60.0f * 100.0f));
737
g_OSD.Show(OSDType::STATUS_ICON, temp, "", "I_FAST_FORWARD", 1.5f, "altspeed");
738
g_OSD.SetFlags("altspeed", OSDMessageFlags::Transparent);
739
}
740
741
void EmuScreen::onVKey(VirtKey virtualKeyCode, bool down) {
742
auto sc = GetI18NCategory(I18NCat::SCREEN);
743
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
744
745
switch (virtualKeyCode) {
746
case VIRTKEY_TOGGLE_DEBUGGER:
747
if (down) {
748
g_Config.bShowImDebugger = !g_Config.bShowImDebugger;
749
}
750
break;
751
case VIRTKEY_TOGGLE_TILT:
752
if (down) {
753
g_Config.bTiltInputEnabled = !g_Config.bTiltInputEnabled;
754
if (!g_Config.bTiltInputEnabled) {
755
// Reset whatever got tilted.
756
switch (g_Config.iTiltInputType) {
757
case TILT_ANALOG:
758
__CtrlSetAnalogXY(0, 0, 0);
759
break;
760
case TILT_ACTION_BUTTON:
761
__CtrlUpdateButtons(0, CTRL_CROSS | CTRL_CIRCLE | CTRL_SQUARE | CTRL_TRIANGLE);
762
break;
763
case TILT_DPAD:
764
__CtrlUpdateButtons(0, CTRL_UP | CTRL_DOWN | CTRL_LEFT | CTRL_RIGHT);
765
break;
766
case TILT_TRIGGER_BUTTONS:
767
__CtrlUpdateButtons(0, CTRL_LTRIGGER | CTRL_RTRIGGER);
768
break;
769
}
770
}
771
}
772
break;
773
case VIRTKEY_FASTFORWARD:
774
if (down && !NetworkWarnUserIfOnlineAndCantSpeed() && !bootPending_) {
775
/*
776
// This seems like strange behavior. Commented it out.
777
if (coreState == CORE_STEPPING_CPU) {
778
Core_Resume();
779
}
780
*/
781
PSP_CoreParameter().fastForward = true;
782
} else {
783
PSP_CoreParameter().fastForward = false;
784
}
785
break;
786
787
case VIRTKEY_SPEED_TOGGLE:
788
if (down && !NetworkWarnUserIfOnlineAndCantSpeed()) {
789
// Cycle through enabled speeds.
790
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) {
791
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
792
} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit2 >= 0) {
793
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
794
} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 || PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
795
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
796
}
797
798
ShowFpsLimitNotice();
799
}
800
break;
801
802
case VIRTKEY_SPEED_CUSTOM1:
803
if (down && !NetworkWarnUserIfOnlineAndCantSpeed()) {
804
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
805
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
806
ShowFpsLimitNotice();
807
}
808
} else {
809
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) {
810
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
811
ShowFpsLimitNotice();
812
}
813
}
814
break;
815
case VIRTKEY_SPEED_CUSTOM2:
816
if (down && !NetworkWarnUserIfOnlineAndCantSpeed()) {
817
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
818
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
819
ShowFpsLimitNotice();
820
}
821
} else {
822
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
823
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
824
ShowFpsLimitNotice();
825
}
826
}
827
break;
828
829
case VIRTKEY_PAUSE:
830
if (down) {
831
// Note: We don't check NetworkWarnUserIfOnlineAndCantSpeed, because we can keep
832
// running in the background of the menu.
833
pauseTrigger_ = true;
834
controlMapper_.ForceReleaseVKey(virtualKeyCode);
835
}
836
break;
837
838
case VIRTKEY_RESET_EMULATION:
839
if (down) {
840
System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
841
}
842
break;
843
844
#ifndef MOBILE_DEVICE
845
case VIRTKEY_RECORD:
846
if (down) {
847
if (g_Config.bDumpFrames == g_Config.bDumpAudio) {
848
g_Config.bDumpFrames = !g_Config.bDumpFrames;
849
g_Config.bDumpAudio = !g_Config.bDumpAudio;
850
} else {
851
// This hotkey should always toggle both audio and video together.
852
// So let's make sure that's the only outcome even if video OR audio was already being dumped.
853
if (g_Config.bDumpFrames) {
854
AVIDump::Stop();
855
AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);
856
g_Config.bDumpAudio = true;
857
} else {
858
WAVDump::Reset();
859
g_Config.bDumpFrames = true;
860
}
861
}
862
}
863
break;
864
#endif
865
866
case VIRTKEY_SAVE_STATE:
867
if (down && !Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) {
868
SaveState::SaveSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, &AfterSaveStateAction);
869
}
870
break;
871
case VIRTKEY_LOAD_STATE:
872
if (down && !Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) {
873
SaveState::LoadSlot(SaveState::GetGamePrefix(g_paramSFO), g_Config.iCurrentStateSlot, &AfterSaveStateAction);
874
}
875
break;
876
case VIRTKEY_PREVIOUS_SLOT:
877
if (down && !Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
878
SaveState::PrevSlot();
879
System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);
880
}
881
break;
882
case VIRTKEY_NEXT_SLOT:
883
if (down && !Achievements::WarnUserIfHardcoreModeActive(true) && !NetworkWarnUserIfOnlineAndCantSavestate()) {
884
SaveState::NextSlot();
885
System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);
886
}
887
break;
888
case VIRTKEY_SCREENSHOT:
889
if (down)
890
g_TakeScreenshot = true;
891
break;
892
case VIRTKEY_RAPID_FIRE:
893
__CtrlSetRapidFire(down, g_Config.iRapidFireInterval);
894
break;
895
default:
896
// To make sure we're not in an async context.
897
if (down) {
898
queuedVirtKeys_.push_back(virtualKeyCode);
899
}
900
break;
901
}
902
}
903
904
void EmuScreen::ProcessQueuedVKeys() {
905
for (auto iter : queuedVirtKeys_) {
906
ProcessVKey(iter);
907
}
908
queuedVirtKeys_.clear();
909
}
910
911
void EmuScreen::ProcessVKey(VirtKey virtKey) {
912
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
913
auto sc = GetI18NCategory(I18NCat::SCREEN);
914
915
switch (virtKey) {
916
case VIRTKEY_OPENCHAT:
917
if (g_Config.bEnableNetworkChat && !g_Config.bShowImDebugger) {
918
UI::EventParams e{};
919
OnChatMenu.Trigger(e);
920
controlMapper_.ForceReleaseVKey(VIRTKEY_OPENCHAT);
921
}
922
break;
923
924
case VIRTKEY_AXIS_SWAP:
925
controlMapper_.ToggleSwapAxes();
926
g_OSD.Show(OSDType::MESSAGE_INFO, mc->T("AxisSwap")); // best string we have.
927
break;
928
929
case VIRTKEY_DEVMENU:
930
{
931
UI::EventParams e{};
932
OnDevMenu.Trigger(e);
933
}
934
break;
935
936
case VIRTKEY_TOGGLE_MOUSE:
937
g_Config.bMouseControl = !g_Config.bMouseControl;
938
break;
939
940
case VIRTKEY_TEXTURE_DUMP:
941
g_Config.bSaveNewTextures = !g_Config.bSaveNewTextures;
942
if (g_Config.bSaveNewTextures) {
943
g_OSD.Show(OSDType::MESSAGE_SUCCESS, sc->T("saveNewTextures_true", "Textures will now be saved to your storage"), 2.0, "savetexturechanged");
944
} else {
945
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_false", "Texture saving was disabled"), 2.0, "savetexturechanged");
946
}
947
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
948
break;
949
950
case VIRTKEY_TEXTURE_REPLACE:
951
g_Config.bReplaceTextures = !g_Config.bReplaceTextures;
952
if (g_Config.bReplaceTextures) {
953
g_OSD.Show(OSDType::MESSAGE_SUCCESS, sc->T("replaceTextures_true", "Texture replacement enabled"), 2.0, "replacetexturechanged");
954
} else {
955
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_false", "Textures are no longer being replaced"), 2.0, "replacetexturechanged");
956
}
957
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
958
break;
959
960
case VIRTKEY_MUTE_TOGGLE:
961
g_Config.bEnableSound = !g_Config.bEnableSound;
962
break;
963
964
case VIRTKEY_SCREEN_ROTATION_VERTICAL:
965
{
966
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
967
config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL;
968
break;
969
}
970
case VIRTKEY_SCREEN_ROTATION_VERTICAL180:
971
{
972
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
973
config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180;
974
break;
975
}
976
case VIRTKEY_SCREEN_ROTATION_HORIZONTAL:
977
{
978
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
979
config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL;
980
break;
981
}
982
case VIRTKEY_SCREEN_ROTATION_HORIZONTAL180:
983
{
984
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
985
config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180;
986
break;
987
}
988
989
case VIRTKEY_TOGGLE_WLAN:
990
// Let's not allow the user to toggle wlan while connected, could get confusing.
991
if (!g_netInited) {
992
auto n = GetI18NCategory(I18NCat::NETWORKING);
993
auto di = GetI18NCategory(I18NCat::DIALOG);
994
g_Config.bEnableWlan = !g_Config.bEnableWlan;
995
// Try to avoid adding more strings so we piece together a message from existing ones.
996
g_OSD.Show(OSDType::MESSAGE_INFO, StringFromFormat(
997
"%s: %s", n->T_cstr("Enable networking"), g_Config.bEnableWlan ? di->T_cstr("Enabled") : di->T_cstr("Disabled")), 2.0, "toggle_wlan");
998
}
999
break;
1000
1001
case VIRTKEY_TOGGLE_FULLSCREEN:
1002
// TODO: Limit to platforms that can support fullscreen.
1003
g_Config.bFullScreen = !g_Config.bFullScreen;
1004
System_ApplyFullscreenState();
1005
break;
1006
1007
case VIRTKEY_TOGGLE_TOUCH_CONTROLS:
1008
if (g_Config.bShowTouchControls) {
1009
// This just messes with opacity if enabled, so you can touch the screen again to bring them back.
1010
if (GamepadGetOpacity() < 0.01f) {
1011
GamepadTouch();
1012
} else {
1013
GamepadResetTouch();
1014
}
1015
} else {
1016
// If touch controls are disabled though, they'll get enabled.
1017
g_Config.bShowTouchControls = true;
1018
RecreateViews();
1019
GamepadTouch();
1020
}
1021
break;
1022
1023
case VIRTKEY_REWIND:
1024
if (!Achievements::WarnUserIfHardcoreModeActive(false) && !NetworkWarnUserIfOnlineAndCantSavestate() && !bootPending_) {
1025
if (SaveState::CanRewind()) {
1026
SaveState::Rewind(&AfterSaveStateAction);
1027
} else {
1028
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0);
1029
}
1030
}
1031
break;
1032
1033
case VIRTKEY_PAUSE_NO_MENU:
1034
if (!NetworkWarnUserIfOnlineAndCantSpeed()) {
1035
// We re-use debug break/resume to implement pause/resume without a menu.
1036
if (coreState == CORE_STEPPING_CPU) { // should we check reason?
1037
Core_Resume();
1038
} else {
1039
Core_Break(BreakReason::UIPause);
1040
}
1041
}
1042
break;
1043
1044
case VIRTKEY_EXIT_APP:
1045
{
1046
if (!bootPending_) {
1047
std::string confirmExitMessage = GetConfirmExitMessage();
1048
if (!confirmExitMessage.empty()) {
1049
auto di = GetI18NCategory(I18NCat::DIALOG);
1050
auto mm = GetI18NCategory(I18NCat::MAINMENU);
1051
confirmExitMessage += '\n';
1052
confirmExitMessage += di->T("Are you sure you want to exit?");
1053
screenManager()->push(new UI::MessagePopupScreen(mm->T("Exit"), confirmExitMessage, di->T("Yes"), di->T("No"), [=](bool result) {
1054
if (result) {
1055
System_ExitApp();
1056
}
1057
}));
1058
} else {
1059
System_ExitApp();
1060
}
1061
}
1062
break;
1063
}
1064
1065
case VIRTKEY_FRAME_ADVANCE:
1066
// Can't do this reliably in an async fashion, so we just set a variable.
1067
// Is this used by anyone? There's no user-friendly way to resume, other than PAUSE_NO_MENU or the debugger.
1068
if (!NetworkWarnUserIfOnlineAndCantSpeed()) {
1069
if (Core_IsStepping()) {
1070
Core_Resume();
1071
frameStep_ = true;
1072
} else {
1073
Core_Break(BreakReason::FrameAdvance);
1074
}
1075
}
1076
break;
1077
1078
default:
1079
break;
1080
}
1081
}
1082
1083
void EmuScreen::onVKeyAnalog(VirtKey virtualKeyCode, float value) {
1084
if (virtualKeyCode != VIRTKEY_SPEED_ANALOG) {
1085
return;
1086
}
1087
1088
// We only handle VIRTKEY_SPEED_ANALOG here.
1089
1090
// Xbox controllers need a pretty big deadzone here to not leave behind small values
1091
// on occasion when releasing the trigger. Still feels right.
1092
static constexpr float DEADZONE_THRESHOLD = 0.2f;
1093
static constexpr float DEADZONE_SCALE = 1.0f / (1.0f - DEADZONE_THRESHOLD);
1094
1095
FPSLimit &limitMode = PSP_CoreParameter().fpsLimit;
1096
// If we're using an alternate speed already, let that win.
1097
if (limitMode != FPSLimit::NORMAL && limitMode != FPSLimit::ANALOG)
1098
return;
1099
// Don't even try if the limit is invalid.
1100
if (g_Config.iAnalogFpsLimit <= 0)
1101
return;
1102
1103
// Apply a small deadzone (against the resting position.)
1104
value = std::max(0.0f, (value - DEADZONE_THRESHOLD) * DEADZONE_SCALE);
1105
1106
// If target is above 60, value is how much to speed up over 60. Otherwise, it's how much slower.
1107
// So normalize the target.
1108
int target = g_Config.iAnalogFpsLimit - 60;
1109
PSP_CoreParameter().analogFpsLimit = 60 + (int)(target * value);
1110
1111
// If we've reset back to normal, turn it off.
1112
limitMode = PSP_CoreParameter().analogFpsLimit == 60 ? FPSLimit::NORMAL : FPSLimit::ANALOG;
1113
}
1114
1115
bool EmuScreen::UnsyncKey(const KeyInput &key) {
1116
System_Notify(SystemNotification::ACTIVITY);
1117
1118
// Update imgui modifier flags
1119
if (key.flags & (KeyInputFlags::DOWN | KeyInputFlags::UP)) {
1120
bool down = (key.flags & KeyInputFlags::DOWN) != 0;
1121
switch (key.keyCode) {
1122
case NKCODE_CTRL_LEFT: keyCtrlLeft_ = down; break;
1123
case NKCODE_CTRL_RIGHT: keyCtrlRight_ = down; break;
1124
case NKCODE_SHIFT_LEFT: keyShiftLeft_ = down; break;
1125
case NKCODE_SHIFT_RIGHT: keyShiftRight_ = down; break;
1126
case NKCODE_ALT_LEFT: keyAltLeft_ = down; break;
1127
case NKCODE_ALT_RIGHT: keyAltRight_ = down; break;
1128
default: break;
1129
}
1130
}
1131
1132
const bool chatMenuOpen = chatMenu_ && chatMenu_->GetVisibility() == UI::V_VISIBLE;
1133
1134
if (chatMenuOpen || (g_Config.bShowImDebugger && imguiInited_)) {
1135
// Note: Allow some Vkeys through, so we can toggle the imgui for example (since we actually block the control mapper otherwise in imgui mode).
1136
// We need to manually implement it here :/
1137
if (g_Config.bShowImDebugger && imguiInited_) {
1138
if (key.flags & (KeyInputFlags::UP | KeyInputFlags::DOWN)) {
1139
InputMapping mapping(key.deviceId, key.keyCode);
1140
std::vector<int> pspButtons;
1141
bool mappingFound = KeyMap::InputMappingToPspButton(mapping, &pspButtons);
1142
if (mappingFound) {
1143
for (auto b : pspButtons) {
1144
if (b == VIRTKEY_TOGGLE_DEBUGGER || b == VIRTKEY_PAUSE) {
1145
return controlMapper_.Key(key, &pauseTrigger_);
1146
}
1147
}
1148
}
1149
}
1150
UI::EnableFocusMovement(false);
1151
// Enable gamepad controls while running imgui (but ignore mouse/keyboard).
1152
switch (key.deviceId) {
1153
case DEVICE_ID_KEYBOARD:
1154
if (!ImGui::GetIO().WantCaptureKeyboard) {
1155
controlMapper_.Key(key, &pauseTrigger_);
1156
}
1157
break;
1158
case DEVICE_ID_MOUSE:
1159
if (!ImGui::GetIO().WantCaptureMouse) {
1160
controlMapper_.Key(key, &pauseTrigger_);
1161
}
1162
break;
1163
default:
1164
controlMapper_.Key(key, &pauseTrigger_);
1165
break;
1166
}
1167
} else {
1168
// Let up-events through to the controlMapper_ so input doesn't get stuck.
1169
if (key.flags & KeyInputFlags::UP) {
1170
controlMapper_.Key(key, &pauseTrigger_);
1171
}
1172
}
1173
1174
return UIScreen::UnsyncKey(key);
1175
}
1176
return controlMapper_.Key(key, &pauseTrigger_);
1177
}
1178
1179
void EmuScreen::UnsyncAxis(const AxisInput *axes, size_t count) {
1180
System_Notify(SystemNotification::ACTIVITY);
1181
1182
if (UI::IsFocusMovementEnabled()) {
1183
return UIScreen::UnsyncAxis(axes, count);
1184
}
1185
1186
return controlMapper_.Axis(axes, count);
1187
}
1188
1189
bool EmuScreen::key(const KeyInput &key) {
1190
bool retval = UIScreen::key(key);
1191
1192
if (!retval && g_Config.bShowImDebugger && imguiInited_) {
1193
ImGui_ImplPlatform_KeyEvent(key);
1194
}
1195
1196
if (!retval && (key.flags & KeyInputFlags::DOWN) != 0 && UI::IsEscapeKey(key)) {
1197
if (chatMenu_)
1198
chatMenu_->Close();
1199
if (chatButton_)
1200
chatButton_->SetVisibility(UI::V_VISIBLE);
1201
UI::EnableFocusMovement(false);
1202
return true;
1203
}
1204
1205
return retval;
1206
}
1207
1208
void EmuScreen::touch(const TouchInput &touch) {
1209
if (g_Config.bShowImDebugger && imguiInited_) {
1210
ImGui_ImplPlatform_TouchEvent(touch);
1211
if (!ImGui::GetIO().WantCaptureMouse) {
1212
UIScreen::touch(touch);
1213
}
1214
} else if (g_Config.bMouseControl && !(touch.flags & TouchInputFlags::UP) && (touch.flags & TouchInputFlags::MOUSE)) {
1215
// don't do anything as the mouse pointer is hidden in this case.
1216
// But we let touch-up events through to avoid getting stuck if the user toggles mouse control.
1217
} else {
1218
// Handle closing the chat menu if touched outside it.
1219
if (chatMenu_ && chatMenu_->GetVisibility() == UI::V_VISIBLE) {
1220
// Avoid pressing touch button behind the chat
1221
if (!chatMenu_->Contains(touch.x, touch.y)) {
1222
if ((touch.flags & TouchInputFlags::DOWN) != 0) {
1223
chatMenu_->Close();
1224
if (chatButton_)
1225
chatButton_->SetVisibility(UI::V_VISIBLE);
1226
UI::EnableFocusMovement(false);
1227
}
1228
}
1229
}
1230
UIScreen::touch(touch);
1231
}
1232
}
1233
1234
class GameInfoBGView : public UI::InertView {
1235
public:
1236
GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {}
1237
1238
void Draw(UIContext &dc) override {
1239
// Should only be called when visible.
1240
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::PIC1);
1241
dc.Flush();
1242
1243
// PIC1 is the loading image, so let's only draw if it's available.
1244
if (ginfo->Ready(GameInfoFlags::PIC1) && ginfo->pic1.texture) {
1245
Draw::Texture *texture = ginfo->pic1.texture;
1246
if (texture) {
1247
const DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(g_display.GetDeviceOrientation());
1248
// Similar to presentation, we want to put the game PIC1 in the same region of the screen.
1249
FRect frame = GetScreenFrame(config.bIgnoreScreenInsets, g_display.pixel_xres, g_display.pixel_yres);
1250
FRect rc;
1251
CalculateDisplayOutputRect(config, &rc, texture->Width(), texture->Height(), frame, config.iInternalScreenRotation);
1252
1253
// Need to adjust for DPI here since we're still in the UI coordinate space here, not the pixel coordinate space used for in-game presentation.
1254
Bounds bounds(rc.x * g_display.dpi_scale_x, rc.y * g_display.dpi_scale_y, rc.w * g_display.dpi_scale_x, rc.h * g_display.dpi_scale_y);
1255
1256
dc.GetDrawContext()->BindTexture(0, texture);
1257
1258
double loadTime = ginfo->pic1.timeLoaded;
1259
uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3));
1260
dc.Draw()->DrawTexRect(bounds, 0, 0, 1, 1, color);
1261
dc.Flush();
1262
dc.RebindTexture();
1263
}
1264
}
1265
}
1266
1267
std::string DescribeText() const override {
1268
return "";
1269
}
1270
1271
void SetColor(uint32_t c) {
1272
color_ = c;
1273
}
1274
1275
protected:
1276
Path gamePath_;
1277
uint32_t color_ = 0xFFC0C0C0;
1278
};
1279
1280
// TODO: Shouldn't actually need bounds for this, Anchor can center too.
1281
static UI::AnchorLayoutParams *AnchorInCorner(const Bounds &bounds, int corner, float xOffset, float yOffset) {
1282
using namespace UI;
1283
switch ((ScreenEdgePosition)g_Config.iChatButtonPosition) {
1284
case ScreenEdgePosition::BOTTOM_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, NONE, NONE, yOffset, Centering::Both);
1285
case ScreenEdgePosition::BOTTOM_CENTER: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), NONE, NONE, yOffset, Centering::Both);
1286
case ScreenEdgePosition::BOTTOM_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, NONE, xOffset, yOffset, Centering::Both);
1287
case ScreenEdgePosition::TOP_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, yOffset, NONE, NONE, Centering::Both);
1288
case ScreenEdgePosition::TOP_CENTER: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), yOffset, NONE, NONE, Centering::Both);
1289
case ScreenEdgePosition::TOP_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, yOffset, xOffset, NONE, Centering::Both);
1290
case ScreenEdgePosition::CENTER_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, bounds.centerY(), NONE, NONE, Centering::Both);
1291
case ScreenEdgePosition::CENTER_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, bounds.centerY(), xOffset, NONE, Centering::Both);
1292
default: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, NONE, NONE, yOffset, Centering::Both);
1293
}
1294
}
1295
1296
void EmuScreen::CreateViews() {
1297
using namespace UI;
1298
1299
auto di = GetI18NCategory(I18NCat::DIALOG);
1300
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
1301
auto sc = GetI18NCategory(I18NCat::SCREEN);
1302
1303
const DeviceOrientation deviceOrientation = GetDeviceOrientation();
1304
1305
TouchControlConfig &touch = g_Config.GetTouchControlsConfig(deviceOrientation);
1306
1307
const Bounds &bounds = screenManager()->getUIContext()->GetLayoutBounds();
1308
InitPadLayout(&touch, deviceOrientation, bounds.w, bounds.h);
1309
1310
root_ = CreatePadLayout(touch, bounds.w, bounds.h, &pauseTrigger_, &controlMapper_);
1311
if (g_Config.bShowDeveloperMenu) {
1312
root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools);
1313
}
1314
1315
LinearLayout *buttons = new LinearLayout(Orientation::ORIENT_HORIZONTAL, new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 60, Centering::Both));
1316
buttons->SetSpacing(20.0f);
1317
root_->Add(buttons);
1318
1319
resumeButton_ = buttons->Add(new Button(dev->T("Resume")));
1320
resumeButton_->OnClick.Add([](UI::EventParams &) {
1321
if (coreState == CoreState::CORE_RUNTIME_ERROR) {
1322
// Force it!
1323
Memory::MemFault_IgnoreLastCrash();
1324
coreState = CoreState::CORE_RUNNING_CPU;
1325
}
1326
});
1327
resumeButton_->SetVisibility(V_GONE);
1328
1329
resetButton_ = buttons->Add(new Button(di->T("Reset")));
1330
resetButton_->OnClick.Add([](UI::EventParams &) {
1331
if (coreState == CoreState::CORE_RUNTIME_ERROR) {
1332
System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
1333
}
1334
});
1335
resetButton_->SetVisibility(V_GONE);
1336
1337
backButton_ = buttons->Add(new Button(di->T("Back")));
1338
backButton_->OnClick.Add([this](UI::EventParams &) {
1339
this->pauseTrigger_ = true;
1340
});
1341
backButton_->SetVisibility(V_GONE);
1342
1343
cardboardDisableButton_ = root_->Add(new Button(sc->T("Cardboard VR OFF"), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 30, Centering::Both)));
1344
DeviceOrientation orientation = GetDeviceOrientation();
1345
cardboardDisableButton_->OnClick.Add([deviceOrientation](UI::EventParams &) {
1346
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(deviceOrientation);
1347
config.bEnableCardboardVR = false;
1348
});
1349
cardboardDisableButton_->SetVisibility(V_GONE);
1350
cardboardDisableButton_->SetScale(0.65f); // make it smaller - this button can be in the way otherwise.
1351
1352
chatButton_ = nullptr;
1353
chatMenu_ = nullptr;
1354
if (g_Config.bEnableNetworkChat) {
1355
if (g_Config.iChatButtonPosition != 8) {
1356
auto n = GetI18NCategory(I18NCat::NETWORKING);
1357
AnchorLayoutParams *layoutParams = AnchorInCorner(bounds, g_Config.iChatButtonPosition, 80.0f, 50.0f);
1358
ChoiceWithValueDisplay *btn = new ChoiceWithValueDisplay(&newChatMessages_, n->T("Chat"), layoutParams);
1359
root_->Add(btn)->OnClick.Handle(this, &EmuScreen::OnChat);
1360
chatButton_ = btn;
1361
}
1362
chatMenu_ = root_->Add(new ChatMenu(GetRequesterToken(), screenManager()->getUIContext()->GetBounds(), screenManager(), new LayoutParams(FILL_PARENT, FILL_PARENT)));
1363
chatMenu_->SetVisibility(UI::V_GONE);
1364
}
1365
1366
saveStatePreview_ = new AsyncImageFileView(Path(), IS_FIXED, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, Centering::Both));
1367
saveStatePreview_->SetFixedSize(160, 90);
1368
saveStatePreview_->SetColor(0x90FFFFFF);
1369
saveStatePreview_->SetVisibility(V_GONE);
1370
saveStatePreview_->SetCanBeFocused(false);
1371
root_->Add(saveStatePreview_);
1372
1373
GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)));
1374
1375
static const ImageID symbols[4] = {
1376
ImageID("I_CROSS"),
1377
ImageID("I_CIRCLE"),
1378
ImageID("I_SQUARE"),
1379
ImageID("I_TRIANGLE"),
1380
};
1381
1382
Spinner *loadingSpinner = root_->Add(new Spinner(symbols, ARRAY_SIZE(symbols), new AnchorLayoutParams(NONE, NONE, 45, 45, Centering::Both)));
1383
loadingSpinner_ = loadingSpinner;
1384
1385
loadingBG->SetTag("LoadingBG");
1386
loadingSpinner->SetTag("LoadingSpinner");
1387
1388
loadingViewColor_ = loadingSpinner->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut));
1389
loadingViewColor_->SetCallback([loadingBG, loadingSpinner](View *v, uint32_t c) {
1390
loadingBG->SetColor(c & 0xFFC0C0C0);
1391
loadingSpinner->SetColor(alphaMul(c, 0.7f));
1392
});
1393
loadingViewColor_->Persist();
1394
1395
// We start invisible here, in case of recreated views.
1396
loadingViewVisible_ = loadingSpinner->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut));
1397
loadingViewVisible_->Persist();
1398
loadingViewVisible_->Finish.Add([loadingBG, loadingSpinner](EventParams &p) {
1399
loadingBG->SetVisibility(p.v->GetVisibility());
1400
1401
// If we just became invisible, flush BGs since we don't need them anymore.
1402
// Saves some VRAM for the game, but don't do it before we fade out...
1403
if (p.v->GetVisibility() == V_INVISIBLE) {
1404
g_gameInfoCache->FlushBGs();
1405
// And we can go away too. This means the tween will never run again.
1406
loadingBG->SetVisibility(V_GONE);
1407
loadingSpinner->SetVisibility(V_GONE);
1408
}
1409
});
1410
// Will become visible along with the loadingView.
1411
loadingBG->SetVisibility(V_INVISIBLE);
1412
}
1413
1414
void EmuScreen::deviceLost() {
1415
UIScreen::deviceLost();
1416
1417
if (imguiInited_) {
1418
if (imDebugger_) {
1419
imDebugger_->DeviceLost();
1420
}
1421
ImGui_ImplThin3d_DestroyDeviceObjects();
1422
}
1423
}
1424
1425
void EmuScreen::deviceRestored(Draw::DrawContext *draw) {
1426
UIScreen::deviceRestored(draw);
1427
if (imguiInited_) {
1428
ImGui_ImplThin3d_CreateDeviceObjects(draw);
1429
}
1430
}
1431
1432
void EmuScreen::OnDevTools(UI::EventParams &params) {
1433
DevMenuScreen *devMenu = new DevMenuScreen(gamePath_, I18NCat::DEVELOPER);
1434
if (params.v)
1435
devMenu->SetPopupOrigin(params.v);
1436
screenManager()->push(devMenu);
1437
}
1438
1439
void EmuScreen::OnChat(UI::EventParams &params) {
1440
if (chatButton_ != nullptr && chatButton_->GetVisibility() == UI::V_VISIBLE) {
1441
chatButton_->SetVisibility(UI::V_GONE);
1442
}
1443
if (chatMenu_ != nullptr) {
1444
chatMenu_->SetVisibility(UI::V_VISIBLE);
1445
1446
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(SDL)
1447
UI::EnableFocusMovement(true);
1448
root_->SetDefaultFocusView(chatMenu_);
1449
1450
chatMenu_->SetFocus();
1451
UI::View *focused = UI::GetFocusedView();
1452
if (focused) {
1453
root_->SubviewFocused(focused);
1454
}
1455
#endif
1456
}
1457
}
1458
1459
// To avoid including proAdhoc.h, which includes a lot of stuff.
1460
int GetChatMessageCount();
1461
1462
void EmuScreen::update() {
1463
using namespace UI;
1464
1465
// This is where views are recreated.
1466
UIScreen::update();
1467
1468
resumeButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR && Memory::MemFault_MayBeResumable() ? V_VISIBLE : V_GONE);
1469
resetButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);
1470
backButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);
1471
1472
if (chatButton_ && chatMenu_) {
1473
if (chatMenu_->GetVisibility() != V_GONE) {
1474
chatMessages_ = GetChatMessageCount();
1475
newChatMessages_ = 0;
1476
} else {
1477
int diff = GetChatMessageCount() - chatMessages_;
1478
// Cap the count at 50.
1479
newChatMessages_ = diff > 50 ? 50 : diff;
1480
}
1481
}
1482
1483
// Simply forcibly update to the current screen size every frame. Doesn't cost much.
1484
// If bounds is set to be smaller than the actual pixel resolution of the display, respect that.
1485
// TODO: Should be able to use g_dpi_scale here instead. Might want to store the dpi scale in the UI context too.
1486
1487
#ifndef _WIN32
1488
const Bounds &bounds = screenManager()->getUIContext()->GetBounds();
1489
PSP_CoreParameter().pixelWidth = g_display.pixel_xres * bounds.w / g_display.dp_xres;
1490
PSP_CoreParameter().pixelHeight = g_display.pixel_yres * bounds.h / g_display.dp_yres;
1491
#endif
1492
1493
if (PSP_IsInited()) {
1494
UpdateUIState(coreState != CORE_RUNTIME_ERROR ? UISTATE_INGAME : UISTATE_EXCEPTION);
1495
}
1496
1497
if (errorMessage_.size()) {
1498
auto err = GetI18NCategory(I18NCat::ERRORS);
1499
auto di = GetI18NCategory(I18NCat::DIALOG);
1500
std::string errLoadingFile = gamePath_.ToVisualString() + "\n\n";
1501
errLoadingFile.append(err->T("Error loading file", "Could not load game"));
1502
errLoadingFile.append("\n");
1503
errLoadingFile.append(errorMessage_);
1504
1505
screenManager()->push(new PromptScreen(gamePath_, errLoadingFile, di->T("OK"), ""));
1506
errorMessage_.clear();
1507
quit_ = true;
1508
return;
1509
}
1510
1511
if (pauseTrigger_) {
1512
pauseTrigger_ = false;
1513
screenManager()->push(new GamePauseScreen(gamePath_, bootPending_));
1514
}
1515
1516
if (!PSP_IsInited())
1517
return;
1518
1519
double now = time_now_d();
1520
1521
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
1522
controlMapper_.Update(config, now);
1523
1524
if (saveStatePreview_ && !bootPending_) {
1525
int currentSlot = SaveState::GetCurrentSlot();
1526
if (saveStateSlot_ != currentSlot) {
1527
saveStateSlot_ = currentSlot;
1528
1529
const std::string gamePrefix = SaveState::GetGamePrefix(g_paramSFO);
1530
1531
Path fn;
1532
if (SaveState::HasSaveInSlot(gamePrefix, currentSlot)) {
1533
fn = SaveState::GenerateSaveSlotPath(gamePrefix, currentSlot, SaveState::SCREENSHOT_EXTENSION);
1534
}
1535
1536
saveStatePreview_->SetFilename(fn);
1537
if (!fn.empty()) {
1538
saveStatePreview_->SetVisibility(UI::V_VISIBLE);
1539
saveStatePreviewShownTime_ = now;
1540
} else {
1541
saveStatePreview_->SetVisibility(UI::V_GONE);
1542
}
1543
}
1544
1545
if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {
1546
double endTime = saveStatePreviewShownTime_ + 2.0;
1547
float alpha = clamp_value((endTime - now) * 4.0, 0.0, 1.0);
1548
saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha));
1549
1550
if (now - saveStatePreviewShownTime_ > 2) {
1551
saveStatePreview_->SetVisibility(UI::V_GONE);
1552
}
1553
}
1554
}
1555
}
1556
1557
bool EmuScreen::checkPowerDown() {
1558
// This is for handling things like sceKernelExitGame().
1559
// Also for REQUEST_STOP.
1560
if (coreState == CORE_POWERDOWN && PSP_GetBootState() == BootState::Complete && !bootPending_) {
1561
INFO_LOG(Log::System, "SELF-POWERDOWN!");
1562
screenManager()->switchScreen(new MainScreen());
1563
return true;
1564
}
1565
return false;
1566
}
1567
1568
ScreenRenderRole EmuScreen::renderRole(bool isTop) const {
1569
auto CanBeBackground = [&]() -> bool {
1570
if (g_Config.bSkipBufferEffects) {
1571
return isTop || (g_Config.bTransparentBackground && ShouldRunBehind());
1572
}
1573
1574
if (!g_Config.bTransparentBackground && !isTop) {
1575
if (ShouldRunBehind() || screenManager()->topScreen()->wantBrightBackground())
1576
return true;
1577
return false;
1578
}
1579
1580
if (!PSP_IsInited() && !bootPending_) {
1581
return false;
1582
}
1583
1584
return true;
1585
};
1586
1587
ScreenRenderRole role = ScreenRenderRole::MUST_BE_FIRST;
1588
if (CanBeBackground()) {
1589
role |= ScreenRenderRole::CAN_BE_BACKGROUND;
1590
}
1591
return role;
1592
}
1593
1594
void EmuScreen::darken() {
1595
if (!screenManager()->topScreen()->wantBrightBackground()) {
1596
UIContext &dc = *screenManager()->getUIContext();
1597
uint32_t color = GetBackgroundColorWithAlpha(dc);
1598
dc.Begin();
1599
dc.RebindTexture();
1600
dc.FillRect(UI::Drawable(color), dc.GetBounds());
1601
dc.Flush();
1602
}
1603
}
1604
1605
void EmuScreen::HandleFlip() {
1606
Achievements::FrameUpdate();
1607
1608
// This video dumping stuff is bad. Or at least completely broken with frameskip..
1609
#ifndef MOBILE_DEVICE
1610
if (g_Config.bDumpFrames && !startDumping_) {
1611
auto sy = GetI18NCategory(I18NCat::SYSTEM);
1612
avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);
1613
g_OSD.Show(OSDType::MESSAGE_INFO, sy->T("AVI Dump started."), 1.0f);
1614
startDumping_ = true;
1615
}
1616
if (g_Config.bDumpFrames && startDumping_) {
1617
avi.AddFrame();
1618
} else if (!g_Config.bDumpFrames && startDumping_) {
1619
auto sy = GetI18NCategory(I18NCat::SYSTEM);
1620
avi.Stop();
1621
g_OSD.Show(OSDType::MESSAGE_INFO, sy->T("AVI Dump stopped."), 3.0f, "avi_dump");
1622
g_OSD.SetClickCallback("avi_dump", [](bool, void *) {
1623
System_ShowFileInFolder(avi.LastFilename());
1624
}, nullptr);
1625
startDumping_ = false;
1626
}
1627
#endif
1628
}
1629
1630
ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
1631
// Moved from update, because we want it to be possible for booting to happen even when the screen
1632
// is in the background, like when choosing Reset from the pause menu.
1633
1634
// If a boot is in progress, update it.
1635
ProcessGameBoot(gamePath_);
1636
1637
ScreenRenderFlags flags = ScreenRenderFlags::NONE;
1638
Draw::Viewport viewport{ 0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f };
1639
using namespace Draw;
1640
1641
DrawContext *draw = screenManager()->getDrawContext();
1642
if (!draw) {
1643
return flags; // shouldn't really happen but I've seen a suspicious stack trace..
1644
}
1645
1646
ProcessQueuedVKeys();
1647
1648
const bool skipBufferEffects = g_Config.bSkipBufferEffects;
1649
1650
bool framebufferBound = false;
1651
1652
const DeviceOrientation orientation = GetDeviceOrientation();
1653
1654
if (mode & ScreenRenderMode::FIRST) {
1655
// Actually, always gonna be first when it exists (?)
1656
1657
// Here we do NOT bind the backbuffer or clear the screen, unless non-buffered.
1658
// The emuscreen is different than the others - we really want to allow the game to render to framebuffers
1659
// before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render
1660
// targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here.
1661
// We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)...
1662
// We do, however, start the frame in other ways.
1663
1664
if (skipBufferEffects && !g_Config.bSoftwareRendering) {
1665
// We need to clear here already so that drawing during the frame is done on a clean slate.
1666
if (Core_IsStepping() && gpuStats.numFlips != 0) {
1667
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_BackBuffer");
1668
} else {
1669
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "EmuScreen_BackBuffer");
1670
}
1671
1672
draw->SetViewport(viewport);
1673
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1674
framebufferBound = true;
1675
}
1676
draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
1677
} else {
1678
// Some other screen bound the backbuffer first.
1679
framebufferBound = true;
1680
}
1681
1682
g_OSD.NudgeIngameNotifications();
1683
1684
const DisplayLayoutConfig &displayLayoutConfig = g_Config.GetDisplayLayoutConfig(orientation);
1685
__DisplaySetDisplayLayoutConfig(displayLayoutConfig);
1686
1687
if (mode & ScreenRenderMode::TOP) {
1688
System_Notify(SystemNotification::KEEP_SCREEN_AWAKE);
1689
} else if (!ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) {
1690
// NOTE: The strcmp is != 0 - so all popped-over screens EXCEPT DevMenu
1691
// Just to make sure.
1692
if (PSP_IsInited() && !skipBufferEffects) {
1693
_dbg_assert_(gpu);
1694
gpu->BeginHostFrame(displayLayoutConfig);
1695
gpu->CopyDisplayToOutput(displayLayoutConfig, true);
1696
gpu->EndHostFrame();
1697
}
1698
if (gpu && gpu->PresentedThisFrame()) {
1699
framebufferBound = true;
1700
}
1701
if (!framebufferBound) {
1702
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_Behind");
1703
}
1704
1705
Draw::BackendState state = draw->GetCurrentBackendState();
1706
if (state.valid) {
1707
// The below can trigger when switching from skip-buffer-effects. We don't really care anymore...
1708
// _dbg_assert_msg_(state.passes >= 1, "skipB: %d sw: %d mode: %d back: %d tag: %s behi: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering, (int)mode, (int)g_Config.iGPUBackend, screenManager()->topScreen()->tag(), (int)g_Config.bRunBehindPauseMenu);
1709
// Workaround any remaining bugs like this.
1710
if (state.passes == 0) {
1711
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_SafeFallback");
1712
}
1713
}
1714
1715
// Need to make sure the UI texture is available, for "darken".
1716
screenManager()->getUIContext()->BeginFrame();
1717
draw->SetViewport(viewport);
1718
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1719
darken();
1720
return flags;
1721
}
1722
1723
if (!PSP_IsInited() || readyToFinishBoot_) {
1724
// It's possible this might be set outside PSP_RunLoopFor().
1725
// In this case, we need to double check it here.
1726
if (mode & ScreenRenderMode::TOP) {
1727
checkPowerDown();
1728
}
1729
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_Invalid");
1730
// Need to make sure the UI texture is available, for "darken".
1731
screenManager()->getUIContext()->BeginFrame();
1732
draw->SetViewport(viewport);
1733
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1734
renderUI();
1735
return flags;
1736
}
1737
1738
// Freeze-frame functionality (loads a savestate on every frame).
1739
if (PSP_CoreParameter().freezeNext) {
1740
PSP_CoreParameter().frozen = true;
1741
PSP_CoreParameter().freezeNext = false;
1742
SaveState::SaveToRam(freezeState_);
1743
} else if (PSP_CoreParameter().frozen) {
1744
std::string errorString;
1745
if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {
1746
ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());
1747
PSP_CoreParameter().frozen = false;
1748
}
1749
}
1750
1751
PSP_UpdateDebugStats((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS || g_Config.bLogFrameDrops);
1752
1753
// Running it early allows things like direct readbacks of buffers, things we can't do
1754
// when we have started the final render pass. Well, technically we probably could with some manipulation
1755
// of pass order in the render managers..
1756
runImDebugger();
1757
1758
bool blockedExecution = Achievements::IsBlockingExecution();
1759
uint32_t clearColor = 0;
1760
if (!blockedExecution) {
1761
if (gpu) {
1762
gpu->BeginHostFrame(displayLayoutConfig);
1763
}
1764
if (SaveState::Process()) {
1765
// We might have lost the framebuffer bind if we had one, due to a readback.
1766
if (framebufferBound) {
1767
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_SavestateRebind");
1768
}
1769
}
1770
PSP_RunLoopWhileState();
1771
1772
// Hopefully, after running, coreState is now CORE_NEXTFRAME
1773
switch (coreState) {
1774
case CORE_NEXTFRAME:
1775
// Reached the end of the frame while running at full blast, all good. Set back to running for the next frame
1776
coreState = frameStep_ ? CORE_STEPPING_CPU : CORE_RUNNING_CPU;
1777
flags |= ScreenRenderFlags::HANDLED_THROTTLING;
1778
break;
1779
case CORE_STEPPING_CPU:
1780
case CORE_STEPPING_GE:
1781
case CORE_RUNTIME_ERROR:
1782
{
1783
// If there's an exception, display information.
1784
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
1785
if (info.type != MIPSExceptionType::NONE) {
1786
// Clear to blue background screen
1787
bool dangerousSettings = !Reporting::IsSupported();
1788
clearColor = dangerousSettings ? 0xFF900050 : 0xFF900000;
1789
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_RuntimeError");
1790
framebufferBound = true;
1791
// The info is drawn later in renderUI
1792
} else {
1793
// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.
1794
// This won't work in non-buffered, but that's fine.
1795
if (!framebufferBound && PSP_IsInited()) {
1796
// draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_Stepping");
1797
gpu->CopyDisplayToOutput(displayLayoutConfig, true);
1798
framebufferBound = true;
1799
}
1800
}
1801
break;
1802
}
1803
default:
1804
// Didn't actually reach the end of the frame, ran out of the blockTicks cycles.
1805
// In this case we need to bind and wipe the backbuffer, at least.
1806
// It's possible we never ended up outputted anything - make sure we have the backbuffer cleared
1807
// So, we don't set framebufferBound here.
1808
1809
// However, let's not cause a UI sleep in the mainloop.
1810
flags |= ScreenRenderFlags::HANDLED_THROTTLING;
1811
break;
1812
}
1813
1814
if (gpu) {
1815
gpu->EndHostFrame();
1816
}
1817
1818
if (SaveState::PollRestartNeeded() && !bootPending_) {
1819
Achievements::UnloadGame();
1820
PSP_Shutdown(true);
1821
1822
// Restart the boot process
1823
bootPending_ = true;
1824
bootIsReset_ = true;
1825
RecreateViews();
1826
_dbg_assert_(coreState == CORE_POWERDOWN);
1827
if (!PSP_InitStart(PSP_CoreParameter())) {
1828
bootPending_ = false;
1829
WARN_LOG(Log::Loader, "Error resetting");
1830
screenManager()->switchScreen(new MainScreen());
1831
}
1832
}
1833
}
1834
1835
if (frameStep_) {
1836
frameStep_ = false;
1837
if (coreState == CORE_RUNNING_CPU) {
1838
Core_Break(BreakReason::FrameAdvance, 0);
1839
}
1840
}
1841
1842
if (gpu && gpu->PresentedThisFrame()) {
1843
framebufferBound = true;
1844
}
1845
1846
if (!framebufferBound) {
1847
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_NoFrame");
1848
draw->SetViewport(viewport);
1849
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1850
}
1851
1852
Draw::BackendState state = draw->GetCurrentBackendState();
1853
1854
// State.valid just states whether the passes parameter has a meaningful value.
1855
if (state.valid) {
1856
_dbg_assert_msg_(state.passes >= 1, "skipB: %d sw: %d mode: %d back: %d bound: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering, (int)mode, (int)g_Config.iGPUBackend, (int)framebufferBound);
1857
if (state.passes == 0) {
1858
// Workaround any remaining bugs like this.
1859
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_SafeFallback");
1860
}
1861
}
1862
1863
screenManager()->getUIContext()->BeginFrame();
1864
1865
if (!(mode & ScreenRenderMode::TOP)) {
1866
renderImDebugger();
1867
// We're in run-behind mode, but we don't want to draw chat, debug UI and stuff. We do draw the imdebugger though.
1868
// So, darken and bail here.
1869
// Reset viewport/scissor to be sure.
1870
draw->SetViewport(viewport);
1871
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1872
darken();
1873
return flags;
1874
}
1875
1876
// NOTE: We don't check for powerdown if we're not the top screen.
1877
if (checkPowerDown()) {
1878
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_PowerDown");
1879
}
1880
1881
if (hasVisibleUI()) {
1882
draw->SetViewport(viewport);
1883
cardboardDisableButton_->SetVisibility(displayLayoutConfig.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE);
1884
renderUI();
1885
}
1886
1887
if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) {
1888
SetVRAppMode(VRAppMode::VR_DIALOG_MODE);
1889
} else {
1890
SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE);
1891
}
1892
1893
renderImDebugger();
1894
return flags;
1895
}
1896
1897
void EmuScreen::runImDebugger() {
1898
if (!lastImguiEnabled_ && g_Config.bShowImDebugger) {
1899
System_NotifyUIEvent(UIEventNotification::TEXT_GOTFOCUS);
1900
VERBOSE_LOG(Log::System, "activating keyboard");
1901
} else if (lastImguiEnabled_ && !g_Config.bShowImDebugger) {
1902
System_NotifyUIEvent(UIEventNotification::TEXT_LOSTFOCUS);
1903
VERBOSE_LOG(Log::System, "deactivating keyboard");
1904
}
1905
lastImguiEnabled_ = g_Config.bShowImDebugger;
1906
if (g_Config.bShowImDebugger) {
1907
Draw::DrawContext *draw = screenManager()->getDrawContext();
1908
if (!imguiInited_) {
1909
// TODO: Do this only on demand.
1910
IMGUI_CHECKVERSION();
1911
ctx_ = ImGui::CreateContext();
1912
1913
ImGui_ImplPlatform_Init(GetSysDirectory(DIRECTORY_SYSTEM) / "imgui.ini");
1914
imDebugger_ = std::make_unique<ImDebugger>();
1915
1916
// Read the TTF font
1917
size_t propSize = 0;
1918
const uint8_t *propFontData = g_VFS.ReadFile("Roboto_Condensed-Regular.ttf", &propSize);
1919
size_t fixedSize = 0;
1920
const uint8_t *fixedFontData = g_VFS.ReadFile("Inconsolata-Regular.ttf", &fixedSize);
1921
// This call works even if fontData is nullptr, in which case the font just won't get loaded.
1922
// This takes ownership of the font array.
1923
ImGui_ImplThin3d_Init(draw, propFontData, propSize, fixedFontData, fixedSize);
1924
imguiInited_ = true;
1925
}
1926
1927
if (PSP_IsInited()) {
1928
_dbg_assert_(imDebugger_);
1929
1930
ImGui_ImplPlatform_NewFrame();
1931
ImGui_ImplThin3d_NewFrame(draw, ui_draw2d.GetDrawMatrix());
1932
1933
ImGui::NewFrame();
1934
1935
if (imCmd_.cmd != ImCmd::NONE) {
1936
imDebugger_->PostCmd(imCmd_);
1937
imCmd_.cmd = ImCmd::NONE;
1938
}
1939
1940
// Update keyboard modifiers.
1941
auto &io = ImGui::GetIO();
1942
io.AddKeyEvent(ImGuiMod_Ctrl, keyCtrlLeft_ || keyCtrlRight_);
1943
io.AddKeyEvent(ImGuiMod_Shift, keyShiftLeft_ || keyShiftRight_);
1944
io.AddKeyEvent(ImGuiMod_Alt, keyAltLeft_ || keyAltRight_);
1945
// io.AddKeyEvent(ImGuiMod_Super, e.key.super);
1946
1947
ImGuiID dockID = ImGui::DockSpaceOverViewport(0, ImGui::GetMainViewport(), ImGuiDockNodeFlags_PassthruCentralNode | ImGuiDockNodeFlags_NoDockingOverCentralNode);
1948
ImGuiDockNode* node = ImGui::DockBuilderGetCentralNode(dockID);
1949
1950
// Not elegant! But don't know how else to pass through the bounds, without making a mess.
1951
Bounds centralNode(node->Pos.x, node->Pos.y, node->Size.x, node->Size.y);
1952
SetOverrideScreenFrame(&centralNode);
1953
1954
if (!io.WantCaptureKeyboard) {
1955
// Draw a focus rectangle to indicate inputs will be passed through.
1956
ImGui::GetBackgroundDrawList()->AddRect
1957
(
1958
node->Pos,
1959
{ node->Pos.x + node->Size.x, node->Pos.y + node->Size.y },
1960
IM_COL32(255, 255, 255, 90),
1961
0.f,
1962
ImDrawFlags_None,
1963
1.f
1964
);
1965
}
1966
imDebugger_->Frame(currentDebugMIPS, gpuDebug, draw);
1967
1968
// Convert to drawlists.
1969
ImGui::Render();
1970
}
1971
}
1972
}
1973
1974
void EmuScreen::renderImDebugger() {
1975
if (g_Config.bShowImDebugger) {
1976
Draw::DrawContext *draw = screenManager()->getDrawContext();
1977
if (PSP_IsInited() && imDebugger_) {
1978
ImGui_ImplThin3d_RenderDrawData(ImGui::GetDrawData(), draw);
1979
}
1980
}
1981
}
1982
1983
bool EmuScreen::hasVisibleUI() {
1984
// Regular but uncommon UI.
1985
if (saveStatePreview_->GetVisibility() != UI::V_GONE || loadingSpinner_->GetVisibility() == UI::V_VISIBLE)
1986
return true;
1987
if (!g_OSD.IsEmpty() || g_Config.bShowTouchControls || g_Config.iShowStatusFlags != 0)
1988
return true;
1989
DisplayLayoutConfig &config = g_Config.GetDisplayLayoutConfig(GetDeviceOrientation());
1990
if (config.bEnableCardboardVR || g_Config.bEnableNetworkChat)
1991
return true;
1992
if (g_Config.bShowGPOLEDs)
1993
return true;
1994
// Debug UI.
1995
if ((DebugOverlay)g_Config.iDebugOverlay != DebugOverlay::OFF || g_Config.bShowDeveloperMenu)
1996
return true;
1997
1998
// Exception information.
1999
if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING_CPU) {
2000
return true;
2001
}
2002
2003
return false;
2004
}
2005
2006
void EmuScreen::renderUI() {
2007
using namespace Draw;
2008
2009
DrawContext *thin3d = screenManager()->getDrawContext();
2010
UIContext *ctx = screenManager()->getUIContext();
2011
ctx->BeginFrame();
2012
// This sets up some important states but not the viewport.
2013
ctx->Begin();
2014
2015
Viewport viewport;
2016
viewport.TopLeftX = 0;
2017
viewport.TopLeftY = 0;
2018
viewport.Width = g_display.pixel_xres;
2019
viewport.Height = g_display.pixel_yres;
2020
viewport.MaxDepth = 1.0;
2021
viewport.MinDepth = 0.0;
2022
thin3d->SetViewport(viewport);
2023
2024
if (root_) {
2025
UI::LayoutViewHierarchy(*ctx, RootMargins(), root_, false, false);
2026
root_->Draw(*ctx);
2027
}
2028
2029
if (PSP_IsInited()) {
2030
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::CONTROL) {
2031
DrawControlMapperOverlay(ctx, ctx->GetLayoutBounds(), controlMapper_);
2032
}
2033
if (g_Config.iShowStatusFlags) {
2034
DrawFPS(ctx, ctx->GetLayoutBounds());
2035
}
2036
}
2037
2038
#ifdef USE_PROFILER
2039
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_PROFILE && PSP_IsInited()) {
2040
DrawProfile(*ctx);
2041
}
2042
#endif
2043
2044
if (g_Config.bShowGPOLEDs) {
2045
// Draw a vertical strip of LEDs at the right side of the screen.
2046
const float ledSize = 24.0f;
2047
const float spacing = 4.0f;
2048
const float height = 8 * ledSize + 7 * spacing;
2049
const float x = ctx->GetBounds().w - spacing - ledSize;
2050
const float y = (ctx->GetBounds().h - height) * 0.5f;
2051
ctx->FillRect(UI::Drawable(0xFF000000), Bounds(x - spacing, y - spacing, ledSize + spacing * 2, height + spacing * 2));
2052
for (int i = 0; i < 8; i++) {
2053
int bit = (g_GPOBits >> i) & 1;
2054
uint32_t color = 0xFF30FF30;
2055
if (!bit) {
2056
color = darkenColor(darkenColor(color));
2057
}
2058
Bounds ledBounds(x, y + (spacing + ledSize) * i, ledSize, ledSize);
2059
ctx->FillRect(UI::Drawable(color), ledBounds);
2060
}
2061
ctx->Flush();
2062
}
2063
2064
if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING_CPU) {
2065
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
2066
if (info.type != MIPSExceptionType::NONE) {
2067
DrawCrashDump(ctx, gamePath_);
2068
} else {
2069
// We're somehow in ERROR or STEPPING without a crash dump. This case is what lead
2070
// to the bare "Resume" and "Reset" buttons without a crash dump before, in cases
2071
// where we were unable to ignore memory errors.
2072
}
2073
}
2074
2075
ctx->Flush();
2076
}
2077
2078
void EmuScreen::AutoLoadSaveState() {
2079
if (autoLoadFailed_) {
2080
return;
2081
}
2082
2083
int autoSlot = -1;
2084
2085
std::string gamePrefix = SaveState::GetGamePrefix(g_paramSFO);
2086
2087
//check if save state has save, if so, load
2088
switch (g_Config.iAutoLoadSaveState) {
2089
case (int)AutoLoadSaveState::OFF: // "AutoLoad Off"
2090
return;
2091
case (int)AutoLoadSaveState::OLDEST: // "Oldest Save"
2092
autoSlot = SaveState::GetOldestSlot(gamePrefix);
2093
break;
2094
case (int)AutoLoadSaveState::NEWEST: // "Newest Save"
2095
autoSlot = SaveState::GetNewestSlot(gamePrefix);
2096
break;
2097
default: // try the specific save state slot specified
2098
autoSlot = (SaveState::HasSaveInSlot(gamePrefix, g_Config.iAutoLoadSaveState - 3)) ? (g_Config.iAutoLoadSaveState - 3) : -1;
2099
break;
2100
}
2101
2102
if (g_Config.iAutoLoadSaveState && autoSlot != -1) {
2103
SaveState::LoadSlot(gamePrefix, autoSlot, [this, autoSlot](SaveState::Status status, std::string_view message) {
2104
AfterSaveStateAction(status, message);
2105
auto sy = GetI18NCategory(I18NCat::SYSTEM);
2106
std::string msg = std::string(sy->T("Auto Load Savestate")) + ": " + StringFromFormat("%d", autoSlot);
2107
g_OSD.Show(OSDType::MESSAGE_SUCCESS, msg);
2108
if (status == SaveState::Status::FAILURE) {
2109
autoLoadFailed_ = true;
2110
}
2111
});
2112
g_Config.iCurrentStateSlot = autoSlot;
2113
}
2114
}
2115
2116
void EmuScreen::resized() {
2117
RecreateViews();
2118
}
2119
2120
bool MustRunBehind() {
2121
return IsNetworkConnected();
2122
}
2123
2124
bool ShouldRunBehind() {
2125
// Enforce run-behind if ad-hoc connected
2126
return g_Config.bRunBehindPauseMenu || MustRunBehind();
2127
}
2128
2129