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