CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutSign UpSign In
hrydgard

CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!

GitHub Repository: hrydgard/ppsspp
Path: blob/master/UI/EmuScreen.cpp
Views: 1401
1
// Copyright (c) 2012- PPSSPP Project.
2
3
// This program is free software: you can redistribute it and/or modify
4
// it under the terms of the GNU General Public License as published by
5
// the Free Software Foundation, version 2.0 or later versions.
6
7
// This program is distributed in the hope that it will be useful,
8
// but WITHOUT ANY WARRANTY; without even the implied warranty of
9
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10
// GNU General Public License 2.0 for more details.
11
12
// A copy of the GPL 2.0 should have been included with the program.
13
// If not, see http://www.gnu.org/licenses/
14
15
// Official git repository and contact information can be found at
16
// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.
17
18
#include "ppsspp_config.h"
19
20
#include <algorithm>
21
#include <functional>
22
23
using namespace std::placeholders;
24
25
#include "Common/Render/TextureAtlas.h"
26
#include "Common/GPU/OpenGL/GLFeatures.h"
27
#include "Common/Render/Text/draw_text.h"
28
#include "Common/File/FileUtil.h"
29
#include "Common/Battery/Battery.h"
30
31
#include "Common/UI/Root.h"
32
#include "Common/UI/UI.h"
33
#include "Common/UI/Context.h"
34
#include "Common/UI/Tween.h"
35
#include "Common/UI/View.h"
36
#include "Common/UI/AsyncImageFileView.h"
37
#include "Common/VR/PPSSPPVR.h"
38
39
#include "Common/Data/Text/I18n.h"
40
#include "Common/Input/InputState.h"
41
#include "Common/Log.h"
42
#include "Common/System/Display.h"
43
#include "Common/System/System.h"
44
#include "Common/System/NativeApp.h"
45
#include "Common/System/Request.h"
46
#include "Common/System/OSD.h"
47
#include "Common/Profiler/Profiler.h"
48
#include "Common/Math/curves.h"
49
#include "Common/TimeUtil.h"
50
51
#ifndef MOBILE_DEVICE
52
#include "Core/AVIDump.h"
53
#endif
54
#include "Core/Config.h"
55
#include "Core/ConfigValues.h"
56
#include "Core/CoreTiming.h"
57
#include "Core/CoreParameter.h"
58
#include "Core/Core.h"
59
#include "Core/KeyMap.h"
60
#include "Core/MemFault.h"
61
#include "Core/Reporting.h"
62
#include "Core/System.h"
63
#include "Core/FileSystems/VirtualDiscFileSystem.h"
64
#include "GPU/GPUState.h"
65
#include "GPU/GPUInterface.h"
66
#include "GPU/Common/FramebufferManagerCommon.h"
67
#if !PPSSPP_PLATFORM(UWP)
68
#include "GPU/Vulkan/DebugVisVulkan.h"
69
#endif
70
#include "Core/MIPS/MIPS.h"
71
#include "Core/HLE/sceCtrl.h"
72
#include "Core/HLE/sceSas.h"
73
#include "Core/Debugger/SymbolMap.h"
74
#include "Core/RetroAchievements.h"
75
#include "Core/SaveState.h"
76
#include "Core/HLE/__sceAudio.h"
77
#include "Core/HLE/proAdhoc.h"
78
#include "Core/HW/Display.h"
79
80
#include "UI/BackgroundAudio.h"
81
#include "UI/OnScreenDisplay.h"
82
#include "UI/GamepadEmu.h"
83
#include "UI/PauseScreen.h"
84
#include "UI/MainScreen.h"
85
#include "UI/EmuScreen.h"
86
#include "UI/DevScreens.h"
87
#include "UI/GameInfoCache.h"
88
#include "UI/MiscScreens.h"
89
#include "UI/ControlMappingScreen.h"
90
#include "UI/DisplayLayoutScreen.h"
91
#include "UI/GameSettingsScreen.h"
92
#include "UI/ProfilerDraw.h"
93
#include "UI/DiscordIntegration.h"
94
#include "UI/ChatScreen.h"
95
#include "UI/DebugOverlay.h"
96
97
#include "Core/Reporting.h"
98
99
#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)
100
#include "Windows/MainWindow.h"
101
#endif
102
103
#ifndef MOBILE_DEVICE
104
static AVIDump avi;
105
#endif
106
107
// TODO: Ugly!
108
static bool frameStep_;
109
static int lastNumFlips;
110
static bool startDumping;
111
112
extern bool g_TakeScreenshot;
113
114
static void __EmuScreenVblank()
115
{
116
auto sy = GetI18NCategory(I18NCat::SYSTEM);
117
118
if (frameStep_ && lastNumFlips != gpuStats.numFlips)
119
{
120
frameStep_ = false;
121
Core_EnableStepping(true, "ui.frameAdvance", 0);
122
lastNumFlips = gpuStats.numFlips;
123
}
124
#ifndef MOBILE_DEVICE
125
if (g_Config.bDumpFrames && !startDumping)
126
{
127
avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);
128
g_OSD.Show(OSDType::MESSAGE_INFO, sy->T("AVI Dump started."), 1.0f);
129
startDumping = true;
130
}
131
if (g_Config.bDumpFrames && startDumping)
132
{
133
avi.AddFrame();
134
}
135
else if (!g_Config.bDumpFrames && startDumping)
136
{
137
avi.Stop();
138
g_OSD.Show(OSDType::MESSAGE_INFO, sy->T("AVI Dump stopped."), 1.0f);
139
startDumping = false;
140
}
141
#endif
142
}
143
144
// Handles control rotation due to internal screen rotation.
145
static void SetPSPAnalog(int stick, float x, float y) {
146
switch (g_Config.iInternalScreenRotation) {
147
case ROTATION_LOCKED_HORIZONTAL:
148
// Standard rotation. No change.
149
break;
150
case ROTATION_LOCKED_HORIZONTAL180:
151
x = -x;
152
y = -y;
153
break;
154
case ROTATION_LOCKED_VERTICAL:
155
{
156
float new_y = -x;
157
x = y;
158
y = new_y;
159
break;
160
}
161
case ROTATION_LOCKED_VERTICAL180:
162
{
163
float new_y = y = x;
164
x = -y;
165
y = new_y;
166
break;
167
}
168
default:
169
break;
170
}
171
__CtrlSetAnalogXY(stick, x, y);
172
}
173
174
EmuScreen::EmuScreen(const Path &filename)
175
: gamePath_(filename) {
176
saveStateSlot_ = SaveState::GetCurrentSlot();
177
__DisplayListenVblank(__EmuScreenVblank);
178
frameStep_ = false;
179
lastNumFlips = gpuStats.numFlips;
180
startDumping = false;
181
controlMapper_.SetCallbacks(
182
std::bind(&EmuScreen::onVKey, this, _1, _2),
183
std::bind(&EmuScreen::onVKeyAnalog, this, _1, _2),
184
[](uint32_t bitsToSet, uint32_t bitsToClear) {
185
__CtrlUpdateButtons(bitsToSet, bitsToClear);
186
},
187
&SetPSPAnalog,
188
nullptr);
189
190
// Make sure we don't leave it at powerdown after the last game.
191
// TODO: This really should be handled elsewhere if it isn't.
192
if (coreState == CORE_POWERDOWN)
193
coreState = CORE_STEPPING;
194
195
OnDevMenu.Handle(this, &EmuScreen::OnDevTools);
196
OnChatMenu.Handle(this, &EmuScreen::OnChat);
197
198
// Usually, we don't want focus movement enabled on this screen, so disable on start.
199
// Only if you open chat or dev tools do we want it to start working.
200
UI::EnableFocusMovement(false);
201
}
202
203
bool EmuScreen::bootAllowStorage(const Path &filename) {
204
// No permissions needed. The easy life.
205
if (filename.Type() == PathType::HTTP)
206
return true;
207
208
if (!System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS))
209
return true;
210
211
PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);
212
switch (status) {
213
case PERMISSION_STATUS_UNKNOWN:
214
System_AskForPermission(SYSTEM_PERMISSION_STORAGE);
215
return false;
216
217
case PERMISSION_STATUS_DENIED:
218
stopRender_ = true;
219
screenManager()->switchScreen(new MainScreen());
220
return false;
221
222
case PERMISSION_STATUS_PENDING:
223
// Keep waiting.
224
return false;
225
226
case PERMISSION_STATUS_GRANTED:
227
return true;
228
}
229
230
_assert_(false);
231
return false;
232
}
233
234
void EmuScreen::bootGame(const Path &filename) {
235
if (Achievements::IsBlockingExecution()) {
236
// Keep waiting.
237
return;
238
}
239
240
if (PSP_IsRebooting())
241
return;
242
if (PSP_IsInited()) {
243
bootPending_ = false;
244
invalid_ = false;
245
bootComplete();
246
return;
247
}
248
249
if (PSP_IsIniting()) {
250
std::string error_string = "(unknown error)";
251
252
bootPending_ = !PSP_InitUpdate(&error_string);
253
254
if (!bootPending_) {
255
invalid_ = !PSP_IsInited();
256
if (invalid_) {
257
errorMessage_ = error_string;
258
ERROR_LOG(Log::Boot, "isIniting bootGame error: %s", errorMessage_.c_str());
259
return;
260
}
261
bootComplete();
262
}
263
return;
264
}
265
266
g_BackgroundAudio.SetGame(Path());
267
268
// Check permission status first, in case we came from a shortcut.
269
if (!bootAllowStorage(filename))
270
return;
271
272
invalid_ = true;
273
274
// We don't want to boot with the wrong game specific config, so wait until info is ready.
275
std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, filename, GameInfoFlags::PARAM_SFO);
276
if (!info->Ready(GameInfoFlags::PARAM_SFO)) {
277
return;
278
}
279
280
auto sc = GetI18NCategory(I18NCat::SCREEN);
281
if (info->fileType == IdentifiedFileType::PSP_DISC_DIRECTORY) {
282
// Check for existence of ppsspp-index.lst - if it exists, the user likely knows what they're doing.
283
// TODO: Better would be to check that it was loaded successfully.
284
if (!File::Exists(filename / INDEX_FILENAME)) {
285
g_OSD.Show(OSDType::MESSAGE_CENTERED_WARNING, sc->T("ExtractedIsoWarning", "Extracted ISOs often don't work.\nPlay the ISO file directly."), gamePath_.ToVisualString(), 7.0f);
286
} else {
287
INFO_LOG(Log::Loader, "Extracted ISO loaded without warning - %s is present.", INDEX_FILENAME.c_str());
288
}
289
}
290
291
extraAssertInfoStr_ = info->id + " " + info->GetTitle();
292
SetExtraAssertInfo(extraAssertInfoStr_.c_str());
293
294
if (!info->id.empty()) {
295
g_Config.loadGameConfig(info->id, info->GetTitle());
296
// Reset views in case controls are in a different place.
297
RecreateViews();
298
299
g_Discord.SetPresenceGame(info->GetTitle());
300
} else {
301
g_Discord.SetPresenceGame(sc->T("Untitled PSP game"));
302
}
303
304
CoreParameter coreParam{};
305
coreParam.cpuCore = (CPUCore)g_Config.iCpuCore;
306
coreParam.gpuCore = GPUCORE_GLES;
307
switch (GetGPUBackend()) {
308
case GPUBackend::DIRECT3D11:
309
coreParam.gpuCore = GPUCORE_DIRECTX11;
310
break;
311
#if !PPSSPP_PLATFORM(UWP)
312
#if PPSSPP_API(ANY_GL)
313
case GPUBackend::OPENGL:
314
coreParam.gpuCore = GPUCORE_GLES;
315
break;
316
#endif
317
case GPUBackend::DIRECT3D9:
318
coreParam.gpuCore = GPUCORE_DIRECTX9;
319
break;
320
case GPUBackend::VULKAN:
321
coreParam.gpuCore = GPUCORE_VULKAN;
322
break;
323
#endif
324
}
325
326
// Preserve the existing graphics context.
327
coreParam.graphicsContext = PSP_CoreParameter().graphicsContext;
328
coreParam.enableSound = g_Config.bEnableSound;
329
coreParam.fileToStart = filename;
330
coreParam.mountIso.clear();
331
coreParam.mountRoot.clear();
332
coreParam.startBreak = !g_Config.bAutoRun;
333
coreParam.headLess = false;
334
335
if (g_Config.iInternalResolution == 0) {
336
coreParam.renderWidth = g_display.pixel_xres;
337
coreParam.renderHeight = g_display.pixel_yres;
338
} else {
339
if (g_Config.iInternalResolution < 0)
340
g_Config.iInternalResolution = 1;
341
coreParam.renderWidth = 480 * g_Config.iInternalResolution;
342
coreParam.renderHeight = 272 * g_Config.iInternalResolution;
343
}
344
coreParam.pixelWidth = g_display.pixel_xres;
345
coreParam.pixelHeight = g_display.pixel_yres;
346
347
std::string error_string;
348
if (!PSP_InitStart(coreParam, &error_string)) {
349
bootPending_ = false;
350
invalid_ = true;
351
errorMessage_ = error_string;
352
ERROR_LOG(Log::Boot, "InitStart bootGame error: %s", errorMessage_.c_str());
353
}
354
355
if (PSP_CoreParameter().compat.flags().RequireBufferedRendering && g_Config.bSkipBufferEffects) {
356
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
357
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 10.0f);
358
}
359
360
if (PSP_CoreParameter().compat.flags().RequireBlockTransfer && g_Config.iSkipGPUReadbackMode != (int)SkipGPUReadbackMode::NO_SKIP && !PSP_CoreParameter().compat.flags().ForceEnableGPUReadback) {
361
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
362
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BlockTransferRequired", "Warning: This game requires Skip GPU Readbacks be set to No."), 10.0f);
363
}
364
365
if (PSP_CoreParameter().compat.flags().RequireDefaultCPUClock && g_Config.iLockedCPUSpeed != 0) {
366
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
367
g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f);
368
}
369
370
loadingViewColor_->Divert(0xFFFFFFFF, 0.75f);
371
loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f);
372
373
screenManager()->getDrawContext()->ResetStats();
374
375
if (bootPending_) {
376
System_PostUIMessage(UIMessage::GAME_SELECTED, filename.c_str());
377
}
378
}
379
380
void EmuScreen::bootComplete() {
381
UpdateUIState(UISTATE_INGAME);
382
System_Notify(SystemNotification::BOOT_DONE);
383
System_Notify(SystemNotification::DISASSEMBLY);
384
385
NOTICE_LOG(Log::Boot, "Booted %s...", PSP_CoreParameter().fileToStart.c_str());
386
if (!Achievements::HardcoreModeActive()) {
387
// Don't auto-load savestates in hardcore mode.
388
autoLoad();
389
}
390
391
auto sc = GetI18NCategory(I18NCat::SCREEN);
392
393
#ifndef MOBILE_DEVICE
394
if (g_Config.bFirstRun) {
395
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("PressESC", "Press ESC to open the pause menu"));
396
}
397
#endif
398
399
#if !PPSSPP_PLATFORM(UWP)
400
if (GetGPUBackend() == GPUBackend::OPENGL) {
401
const char *renderer = gl_extensions.model;
402
if (strstr(renderer, "Chainfire3D") != 0) {
403
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("Chainfire3DWarning", "WARNING: Chainfire3D detected, may cause problems"), 10.0f);
404
} else if (strstr(renderer, "GLTools") != 0) {
405
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("GLToolsWarning", "WARNING: GLTools detected, may cause problems"), 10.0f);
406
}
407
408
if (g_Config.bGfxDebugOutput) {
409
g_OSD.Show(OSDType::MESSAGE_WARNING, "WARNING: GfxDebugOutput is enabled via ppsspp.ini. Things may be slow.", 10.0f);
410
}
411
}
412
#endif
413
414
if (Core_GetPowerSaving()) {
415
auto sy = GetI18NCategory(I18NCat::SYSTEM);
416
#ifdef __ANDROID__
417
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Android battery save mode is on"), 2.0f, "core_powerSaving");
418
#else
419
g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Battery save mode is on"), 2.0f, "core_powerSaving");
420
#endif
421
}
422
423
if (g_Config.bStereoRendering) {
424
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
425
auto di = GetI18NCategory(I18NCat::DIALOG);
426
// Stereo rendering is experimental, so let's notify the user it's being used.
427
// Carefully reuse translations for this rare warning.
428
g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(gr->T("Stereo rendering")) + ": " + std::string(di->T("Enabled")));
429
}
430
431
saveStateSlot_ = SaveState::GetCurrentSlot();
432
433
loadingViewColor_->Divert(0x00FFFFFF, 0.2f);
434
loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);
435
436
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
437
g_Config.TimeTracker().Start(gameID);
438
}
439
440
EmuScreen::~EmuScreen() {
441
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
442
g_Config.TimeTracker().Stop(gameID);
443
444
// If we were invalid, it would already be shutdown.
445
if (!invalid_ || bootPending_) {
446
PSP_Shutdown();
447
}
448
449
System_PostUIMessage(UIMessage::GAME_SELECTED, "");
450
451
g_OSD.ClearAchievementStuff();
452
453
SetExtraAssertInfo(nullptr);
454
455
#ifndef MOBILE_DEVICE
456
if (g_Config.bDumpFrames && startDumping)
457
{
458
avi.Stop();
459
g_OSD.Show(OSDType::MESSAGE_INFO, "AVI Dump stopped.", 2.0f);
460
startDumping = false;
461
}
462
#endif
463
464
if (GetUIState() == UISTATE_EXIT)
465
g_Discord.ClearPresence();
466
else
467
g_Discord.SetPresenceMenu();
468
}
469
470
void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) {
471
if (std::string_view(dialog->tag()) == "TextEditPopup") {
472
// Chat message finished.
473
return;
474
}
475
476
// TODO: improve the way with which we got commands from PauseMenu.
477
// DR_CANCEL/DR_BACK means clicked on "continue", DR_OK means clicked on "back to menu",
478
// DR_YES means a message sent to PauseMenu by System_PostUIMessage.
479
if (result == DR_OK || quit_) {
480
screenManager()->switchScreen(new MainScreen());
481
quit_ = false;
482
}
483
// Returning to the PauseScreen, unless we're stepping, means we should go back to controls.
484
if (Core_IsActive())
485
UI::EnableFocusMovement(false);
486
RecreateViews();
487
SetExtraAssertInfo(extraAssertInfoStr_.c_str());
488
}
489
490
static void AfterSaveStateAction(SaveState::Status status, std::string_view message, void *) {
491
if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {
492
g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR, message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);
493
}
494
}
495
496
static void AfterStateBoot(SaveState::Status status, std::string_view message, void *ignored) {
497
AfterSaveStateAction(status, message, ignored);
498
Core_EnableStepping(false);
499
System_Notify(SystemNotification::DISASSEMBLY);
500
}
501
502
void EmuScreen::focusChanged(ScreenFocusChange focusChange) {
503
Screen::focusChanged(focusChange);
504
505
std::string gameID = g_paramSFO.GetValueString("DISC_ID");
506
if (gameID.empty()) {
507
// startup or shutdown
508
return;
509
}
510
switch (focusChange) {
511
case ScreenFocusChange::FOCUS_LOST_TOP:
512
g_Config.TimeTracker().Stop(gameID);
513
controlMapper_.ReleaseAll();
514
break;
515
case ScreenFocusChange::FOCUS_BECAME_TOP:
516
g_Config.TimeTracker().Start(gameID);
517
break;
518
}
519
}
520
521
void EmuScreen::sendMessage(UIMessage message, const char *value) {
522
// External commands, like from the Windows UI.
523
if (message == UIMessage::REQUEST_GAME_PAUSE && screenManager()->topScreen() == this) {
524
screenManager()->push(new GamePauseScreen(gamePath_));
525
} else if (message == UIMessage::REQUEST_GAME_STOP) {
526
// We will push MainScreen in update().
527
PSP_Shutdown();
528
bootPending_ = false;
529
stopRender_ = true;
530
invalid_ = true;
531
System_Notify(SystemNotification::DISASSEMBLY);
532
} else if (message == UIMessage::REQUEST_GAME_RESET) {
533
PSP_Shutdown();
534
bootPending_ = true;
535
invalid_ = true;
536
System_Notify(SystemNotification::DISASSEMBLY);
537
538
std::string resetError;
539
if (!PSP_InitStart(PSP_CoreParameter(), &resetError)) {
540
ERROR_LOG(Log::Loader, "Error resetting: %s", resetError.c_str());
541
stopRender_ = true;
542
screenManager()->switchScreen(new MainScreen());
543
return;
544
}
545
} else if (message == UIMessage::REQUEST_GAME_BOOT) {
546
// TODO: Ignore or not if it's the same game that's already running?
547
if (gamePath_ == Path(value)) {
548
WARN_LOG(Log::Loader, "Game already running, ignoring");
549
return;
550
}
551
const char *ext = strrchr(value, '.');
552
if (ext != nullptr && !strcmp(ext, ".ppst")) {
553
SaveState::Load(Path(value), -1, &AfterStateBoot);
554
} else {
555
PSP_Shutdown();
556
bootPending_ = true;
557
gamePath_ = Path(value);
558
// Don't leave it on CORE_POWERDOWN, we'll sometimes aggressively bail.
559
Core_UpdateState(CORE_POWERUP);
560
}
561
} else if (message == UIMessage::CONFIG_LOADED) {
562
// In case we need to position touch controls differently.
563
RecreateViews();
564
} else if (message == UIMessage::SHOW_CONTROL_MAPPING && screenManager()->topScreen() == this) {
565
UpdateUIState(UISTATE_PAUSEMENU);
566
screenManager()->push(new ControlMappingScreen(gamePath_));
567
} else if (message == UIMessage::SHOW_DISPLAY_LAYOUT_EDITOR && screenManager()->topScreen() == this) {
568
UpdateUIState(UISTATE_PAUSEMENU);
569
screenManager()->push(new DisplayLayoutScreen(gamePath_));
570
} else if (message == UIMessage::SHOW_SETTINGS && screenManager()->topScreen() == this) {
571
UpdateUIState(UISTATE_PAUSEMENU);
572
screenManager()->push(new GameSettingsScreen(gamePath_));
573
} else if (message == UIMessage::REQUEST_GPU_DUMP_NEXT_FRAME) {
574
if (gpu)
575
gpu->DumpNextFrame();
576
} else if (message == UIMessage::REQUEST_CLEAR_JIT) {
577
currentMIPS->ClearJitCache();
578
if (PSP_IsInited()) {
579
currentMIPS->UpdateCore((CPUCore)g_Config.iCpuCore);
580
}
581
} else if (message == UIMessage::WINDOW_MINIMIZED) {
582
if (!strcmp(value, "true")) {
583
gstate_c.skipDrawReason |= SKIPDRAW_WINDOW_MINIMIZED;
584
} else {
585
gstate_c.skipDrawReason &= ~SKIPDRAW_WINDOW_MINIMIZED;
586
}
587
} else if (message == UIMessage::SHOW_CHAT_SCREEN) {
588
if (g_Config.bEnableNetworkChat) {
589
if (!chatButton_)
590
RecreateViews();
591
592
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {
593
// temporary workaround for hotkey its freeze the ui when open chat screen using hotkey and native keyboard is enable
594
if (g_Config.bBypassOSKWithKeyboard) {
595
// TODO: Make translatable.
596
g_OSD.Show(OSDType::MESSAGE_INFO, "Disable \"Use system native keyboard\" to use ctrl + c hotkey", 2.0f);
597
} else {
598
UI::EventParams e{};
599
OnChatMenu.Trigger(e);
600
}
601
} else {
602
UI::EventParams e{};
603
OnChatMenu.Trigger(e);
604
}
605
}
606
} else if (message == UIMessage::APP_RESUMED && screenManager()->topScreen() == this) {
607
if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) {
608
if (!KeyMap::IsKeyMapped(DEVICE_ID_PAD_0, VIRTKEY_PAUSE) || !KeyMap::IsKeyMapped(DEVICE_ID_PAD_1, VIRTKEY_PAUSE)) {
609
// If it's a TV (so no built-in back button), and there's no back button mapped to a pad,
610
// use this as the fallback way to get into the menu.
611
612
screenManager()->push(new GamePauseScreen(gamePath_));
613
}
614
}
615
} else if (message == UIMessage::REQUEST_PLAY_SOUND) {
616
if (g_Config.bAchievementsSoundEffects && g_Config.bEnableSound) {
617
float achievementVolume = g_Config.iAchievementSoundVolume * 0.1f;
618
// TODO: Handle this some nicer way.
619
if (!strcmp(value, "achievement_unlocked")) {
620
g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume * 1.0f);
621
}
622
if (!strcmp(value, "leaderboard_submitted")) {
623
g_BackgroundAudio.SFX().Play(UI::UISound::LEADERBOARD_SUBMITTED, achievementVolume * 1.0f);
624
}
625
}
626
}
627
}
628
629
bool EmuScreen::UnsyncTouch(const TouchInput &touch) {
630
System_Notify(SystemNotification::ACTIVITY);
631
632
if (chatMenu_ && chatMenu_->GetVisibility() == UI::V_VISIBLE) {
633
// Avoid pressing touch button behind the chat
634
if (chatMenu_->Contains(touch.x, touch.y)) {
635
chatMenu_->Touch(touch);
636
return true;
637
} else if ((touch.flags & TOUCH_DOWN) != 0) {
638
chatMenu_->Close();
639
if (chatButton_)
640
chatButton_->SetVisibility(UI::V_VISIBLE);
641
UI::EnableFocusMovement(false);
642
}
643
}
644
645
GamepadTouch();
646
647
if (root_) {
648
UIScreen::UnsyncTouch(touch);
649
}
650
return true;
651
}
652
653
void EmuScreen::onVKey(int virtualKeyCode, bool down) {
654
auto sc = GetI18NCategory(I18NCat::SCREEN);
655
auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);
656
657
switch (virtualKeyCode) {
658
case VIRTKEY_FASTFORWARD:
659
if (down) {
660
if (coreState == CORE_STEPPING) {
661
Core_EnableStepping(false);
662
}
663
PSP_CoreParameter().fastForward = true;
664
} else {
665
PSP_CoreParameter().fastForward = false;
666
}
667
break;
668
669
case VIRTKEY_SPEED_TOGGLE:
670
if (down) {
671
// Cycle through enabled speeds.
672
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) {
673
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
674
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("fixed", "Speed: alternate"), 1.0, "altspeed");
675
} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit2 >= 0) {
676
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
677
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0, "altspeed");
678
} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 || PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
679
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
680
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0, "altspeed");
681
}
682
}
683
break;
684
685
case VIRTKEY_SPEED_CUSTOM1:
686
if (down) {
687
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
688
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;
689
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("fixed", "Speed: alternate"), 1.0, "altspeed");
690
}
691
} else {
692
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) {
693
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
694
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0, "altspeed");
695
}
696
}
697
break;
698
case VIRTKEY_SPEED_CUSTOM2:
699
if (down) {
700
if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {
701
PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;
702
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0, "altspeed");
703
}
704
} else {
705
if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {
706
PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;
707
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0, "altspeed");
708
}
709
}
710
break;
711
712
case VIRTKEY_PAUSE:
713
if (down) {
714
// Trigger on key-up to partially avoid repetition problems.
715
// This is needed whenever we pop up a menu since the mapper
716
// might miss the key-up. Same as VIRTKEY_OPENCHAT.
717
pauseTrigger_ = true;
718
controlMapper_.ForceReleaseVKey(virtualKeyCode);
719
}
720
break;
721
722
case VIRTKEY_FRAME_ADVANCE:
723
// Can't do this reliably in an async fashion, so we just set a variable.
724
if (down) {
725
doFrameAdvance_.store(true);
726
}
727
break;
728
729
case VIRTKEY_OPENCHAT:
730
if (down && g_Config.bEnableNetworkChat) {
731
UI::EventParams e{};
732
OnChatMenu.Trigger(e);
733
controlMapper_.ForceReleaseVKey(virtualKeyCode);
734
}
735
break;
736
737
case VIRTKEY_AXIS_SWAP:
738
if (down) {
739
controlMapper_.ToggleSwapAxes();
740
g_OSD.Show(OSDType::MESSAGE_INFO, mc->T("AxisSwap")); // best string we have.
741
}
742
break;
743
744
case VIRTKEY_DEVMENU:
745
if (down) {
746
UI::EventParams e{};
747
OnDevMenu.Trigger(e);
748
}
749
break;
750
751
case VIRTKEY_RESET_EMULATION:
752
System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
753
break;
754
755
#ifndef MOBILE_DEVICE
756
case VIRTKEY_RECORD:
757
if (down) {
758
if (g_Config.bDumpFrames == g_Config.bDumpAudio) {
759
g_Config.bDumpFrames = !g_Config.bDumpFrames;
760
g_Config.bDumpAudio = !g_Config.bDumpAudio;
761
} else {
762
// This hotkey should always toggle both audio and video together.
763
// So let's make sure that's the only outcome even if video OR audio was already being dumped.
764
if (g_Config.bDumpFrames) {
765
AVIDump::Stop();
766
AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);
767
g_Config.bDumpAudio = true;
768
} else {
769
WAVDump::Reset();
770
g_Config.bDumpFrames = true;
771
}
772
}
773
}
774
break;
775
#endif
776
777
case VIRTKEY_REWIND:
778
if (down && !Achievements::WarnUserIfHardcoreModeActive(false)) {
779
if (SaveState::CanRewind()) {
780
SaveState::Rewind(&AfterSaveStateAction);
781
} else {
782
g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0);
783
}
784
}
785
break;
786
case VIRTKEY_SAVE_STATE:
787
if (down && !Achievements::WarnUserIfHardcoreModeActive(true)) {
788
SaveState::SaveSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);
789
}
790
break;
791
case VIRTKEY_LOAD_STATE:
792
if (down && !Achievements::WarnUserIfHardcoreModeActive(false)) {
793
SaveState::LoadSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);
794
}
795
break;
796
case VIRTKEY_PREVIOUS_SLOT:
797
if (down && !Achievements::WarnUserIfHardcoreModeActive(true)) {
798
SaveState::PrevSlot();
799
System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);
800
}
801
break;
802
case VIRTKEY_NEXT_SLOT:
803
if (down && !Achievements::WarnUserIfHardcoreModeActive(true)) {
804
SaveState::NextSlot();
805
System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);
806
}
807
break;
808
case VIRTKEY_TOGGLE_FULLSCREEN:
809
if (down)
810
System_ToggleFullscreenState("");
811
break;
812
case VIRTKEY_TOGGLE_TOUCH_CONTROLS:
813
if (down) {
814
if (g_Config.bShowTouchControls) {
815
// This just messes with opacity if enabled, so you can touch the screen again to bring them back.
816
if (GamepadGetOpacity() < 0.01f) {
817
GamepadTouch();
818
} else {
819
// Reset.
820
GamepadTouch(true);
821
}
822
} else {
823
// If touch controls are disabled though, they'll get enabled.
824
g_Config.bShowTouchControls = true;
825
RecreateViews();
826
GamepadTouch();
827
}
828
}
829
break;
830
case VIRTKEY_TOGGLE_MOUSE:
831
if (down) {
832
g_Config.bMouseControl = !g_Config.bMouseControl;
833
}
834
break;
835
case VIRTKEY_SCREENSHOT:
836
if (down)
837
g_TakeScreenshot = true;
838
break;
839
840
case VIRTKEY_TEXTURE_DUMP:
841
if (down) {
842
g_Config.bSaveNewTextures = !g_Config.bSaveNewTextures;
843
if (g_Config.bSaveNewTextures) {
844
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_true", "Textures will now be saved to your storage"), 2.0, "savetexturechanged");
845
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
846
} else {
847
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_false", "Texture saving was disabled"), 2.0, "savetexturechanged");
848
}
849
}
850
break;
851
case VIRTKEY_TEXTURE_REPLACE:
852
if (down) {
853
g_Config.bReplaceTextures = !g_Config.bReplaceTextures;
854
if (g_Config.bReplaceTextures)
855
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_true", "Texture replacement enabled"), 2.0, "replacetexturechanged");
856
else
857
g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_false", "Textures are no longer being replaced"), 2.0, "replacetexturechanged");
858
System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);
859
}
860
break;
861
case VIRTKEY_RAPID_FIRE:
862
__CtrlSetRapidFire(down, g_Config.iRapidFireInterval);
863
break;
864
case VIRTKEY_MUTE_TOGGLE:
865
if (down)
866
g_Config.bEnableSound = !g_Config.bEnableSound;
867
break;
868
case VIRTKEY_SCREEN_ROTATION_VERTICAL:
869
if (down)
870
g_Config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL;
871
break;
872
case VIRTKEY_SCREEN_ROTATION_VERTICAL180:
873
if (down)
874
g_Config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180;
875
break;
876
case VIRTKEY_SCREEN_ROTATION_HORIZONTAL:
877
if (down)
878
g_Config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL;
879
break;
880
case VIRTKEY_SCREEN_ROTATION_HORIZONTAL180:
881
if (down)
882
g_Config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180;
883
break;
884
case VIRTKEY_TOGGLE_WLAN:
885
if (down) {
886
auto n = GetI18NCategory(I18NCat::NETWORKING);
887
auto di = GetI18NCategory(I18NCat::DIALOG);
888
g_Config.bEnableWlan = !g_Config.bEnableWlan;
889
// Try to avoid adding more strings so we piece together a message from existing ones.
890
g_OSD.Show(OSDType::MESSAGE_INFO, StringFromFormat(
891
"%s: %s", n->T("Enable networking"), g_Config.bEnableWlan ? di->T("Enabled") : di->T("Disabled")), 2.0, "toggle_wlan");
892
}
893
break;
894
case VIRTKEY_EXIT_APP:
895
System_ExitApp();
896
break;
897
}
898
}
899
900
void EmuScreen::onVKeyAnalog(int virtualKeyCode, float value) {
901
if (virtualKeyCode != VIRTKEY_SPEED_ANALOG) {
902
return;
903
}
904
905
// We only handle VIRTKEY_SPEED_ANALOG here.
906
907
// Xbox controllers need a pretty big deadzone here to not leave behind small values
908
// on occasion when releasing the trigger. Still feels right.
909
static constexpr float DEADZONE_THRESHOLD = 0.2f;
910
static constexpr float DEADZONE_SCALE = 1.0f / (1.0f - DEADZONE_THRESHOLD);
911
912
FPSLimit &limitMode = PSP_CoreParameter().fpsLimit;
913
// If we're using an alternate speed already, let that win.
914
if (limitMode != FPSLimit::NORMAL && limitMode != FPSLimit::ANALOG)
915
return;
916
// Don't even try if the limit is invalid.
917
if (g_Config.iAnalogFpsLimit <= 0)
918
return;
919
920
// Apply a small deadzone (against the resting position.)
921
value = std::max(0.0f, (value - DEADZONE_THRESHOLD) * DEADZONE_SCALE);
922
923
// If target is above 60, value is how much to speed up over 60. Otherwise, it's how much slower.
924
// So normalize the target.
925
int target = g_Config.iAnalogFpsLimit - 60;
926
PSP_CoreParameter().analogFpsLimit = 60 + (int)(target * value);
927
928
// If we've reset back to normal, turn it off.
929
limitMode = PSP_CoreParameter().analogFpsLimit == 60 ? FPSLimit::NORMAL : FPSLimit::ANALOG;
930
}
931
932
bool EmuScreen::UnsyncKey(const KeyInput &key) {
933
System_Notify(SystemNotification::ACTIVITY);
934
935
if (UI::IsFocusMovementEnabled()) {
936
return UIScreen::UnsyncKey(key);
937
}
938
return controlMapper_.Key(key, &pauseTrigger_);
939
}
940
941
bool EmuScreen::key(const KeyInput &key) {
942
bool retval = UIScreen::key(key);
943
944
if (!retval && (key.flags & KEY_DOWN) != 0 && UI::IsEscapeKey(key)) {
945
if (chatMenu_)
946
chatMenu_->Close();
947
if (chatButton_)
948
chatButton_->SetVisibility(UI::V_VISIBLE);
949
UI::EnableFocusMovement(false);
950
return true;
951
}
952
953
return retval;
954
}
955
956
void EmuScreen::UnsyncAxis(const AxisInput *axes, size_t count) {
957
System_Notify(SystemNotification::ACTIVITY);
958
959
if (UI::IsFocusMovementEnabled()) {
960
return UIScreen::UnsyncAxis(axes, count);
961
}
962
963
return controlMapper_.Axis(axes, count);
964
}
965
966
class GameInfoBGView : public UI::InertView {
967
public:
968
GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {
969
}
970
971
void Draw(UIContext &dc) override {
972
// Should only be called when visible.
973
std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::BG);
974
dc.Flush();
975
976
// PIC1 is the loading image, so let's only draw if it's available.
977
if (ginfo->Ready(GameInfoFlags::BG) && ginfo->pic1.texture) {
978
Draw::Texture *texture = ginfo->pic1.texture;
979
if (texture) {
980
dc.GetDrawContext()->BindTexture(0, texture);
981
982
double loadTime = ginfo->pic1.timeLoaded;
983
uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3));
984
dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color);
985
dc.Flush();
986
dc.RebindTexture();
987
}
988
}
989
}
990
991
std::string DescribeText() const override {
992
return "";
993
}
994
995
void SetColor(uint32_t c) {
996
color_ = c;
997
}
998
999
protected:
1000
Path gamePath_;
1001
uint32_t color_ = 0xFFC0C0C0;
1002
};
1003
1004
// TODO: Shouldn't actually need bounds for this, Anchor can center too.
1005
static UI::AnchorLayoutParams *AnchorInCorner(const Bounds &bounds, int corner, float xOffset, float yOffset) {
1006
using namespace UI;
1007
switch ((ScreenEdgePosition)g_Config.iChatButtonPosition) {
1008
case ScreenEdgePosition::BOTTOM_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, NONE, NONE, yOffset, true);
1009
case ScreenEdgePosition::BOTTOM_CENTER: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), NONE, NONE, yOffset, true);
1010
case ScreenEdgePosition::BOTTOM_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, NONE, xOffset, yOffset, true);
1011
case ScreenEdgePosition::TOP_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, yOffset, NONE, NONE, true);
1012
case ScreenEdgePosition::TOP_CENTER: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), yOffset, NONE, NONE, true);
1013
case ScreenEdgePosition::TOP_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, yOffset, xOffset, NONE, true);
1014
case ScreenEdgePosition::CENTER_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, bounds.centerY(), NONE, NONE, true);
1015
case ScreenEdgePosition::CENTER_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, bounds.centerY(), xOffset, NONE, true);
1016
default: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, NONE, NONE, yOffset, true);
1017
}
1018
}
1019
1020
void EmuScreen::CreateViews() {
1021
using namespace UI;
1022
1023
auto di = GetI18NCategory(I18NCat::DIALOG);
1024
auto dev = GetI18NCategory(I18NCat::DEVELOPER);
1025
auto sc = GetI18NCategory(I18NCat::SCREEN);
1026
1027
const Bounds &bounds = screenManager()->getUIContext()->GetLayoutBounds();
1028
InitPadLayout(bounds.w, bounds.h);
1029
1030
// Devices without a back button like iOS need an on-screen touch back button.
1031
bool showPauseButton = !System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON) || g_Config.bShowTouchPause;
1032
1033
root_ = CreatePadLayout(bounds.w, bounds.h, &pauseTrigger_, showPauseButton, &controlMapper_);
1034
if (g_Config.bShowDeveloperMenu) {
1035
root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools);
1036
}
1037
1038
LinearLayout *buttons = new LinearLayout(Orientation::ORIENT_HORIZONTAL, new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 60, true));
1039
buttons->SetSpacing(20.0f);
1040
root_->Add(buttons);
1041
1042
resumeButton_ = buttons->Add(new Button(dev->T("Resume")));
1043
resumeButton_->OnClick.Handle(this, &EmuScreen::OnResume);
1044
resumeButton_->SetVisibility(V_GONE);
1045
1046
resetButton_ = buttons->Add(new Button(dev->T("Reset")));
1047
resetButton_->OnClick.Add([](UI::EventParams &) {
1048
if (coreState == CoreState::CORE_RUNTIME_ERROR) {
1049
System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);
1050
}
1051
return UI::EVENT_DONE;
1052
});
1053
resetButton_->SetVisibility(V_GONE);
1054
1055
backButton_ = buttons->Add(new Button(dev->T("Back")));
1056
backButton_->OnClick.Add([this](UI::EventParams &) {
1057
this->pauseTrigger_ = true;
1058
return UI::EVENT_DONE;
1059
});
1060
backButton_->SetVisibility(V_GONE);
1061
1062
cardboardDisableButton_ = root_->Add(new Button(sc->T("Cardboard VR OFF"), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 30, true)));
1063
cardboardDisableButton_->OnClick.Handle(this, &EmuScreen::OnDisableCardboard);
1064
cardboardDisableButton_->SetVisibility(V_GONE);
1065
cardboardDisableButton_->SetScale(0.65f); // make it smaller - this button can be in the way otherwise.
1066
1067
if (g_Config.bEnableNetworkChat) {
1068
if (g_Config.iChatButtonPosition != 8) {
1069
auto n = GetI18NCategory(I18NCat::NETWORKING);
1070
AnchorLayoutParams *layoutParams = AnchorInCorner(bounds, g_Config.iChatButtonPosition, 80.0f, 50.0f);
1071
ChoiceWithValueDisplay *btn = new ChoiceWithValueDisplay(&newChatMessages_, n->T("Chat"), layoutParams);
1072
root_->Add(btn)->OnClick.Handle(this, &EmuScreen::OnChat);
1073
chatButton_ = btn;
1074
}
1075
chatMenu_ = root_->Add(new ChatMenu(GetRequesterToken(), screenManager()->getUIContext()->GetBounds(), screenManager(), new LayoutParams(FILL_PARENT, FILL_PARENT)));
1076
chatMenu_->SetVisibility(UI::V_GONE);
1077
} else {
1078
chatButton_ = nullptr;
1079
chatMenu_ = nullptr;
1080
}
1081
1082
saveStatePreview_ = new AsyncImageFileView(Path(), IS_FIXED, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, true));
1083
saveStatePreview_->SetFixedSize(160, 90);
1084
saveStatePreview_->SetColor(0x90FFFFFF);
1085
saveStatePreview_->SetVisibility(V_GONE);
1086
saveStatePreview_->SetCanBeFocused(false);
1087
root_->Add(saveStatePreview_);
1088
1089
GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)));
1090
TextView *loadingTextView = root_->Add(new TextView(sc->T(PSP_GetLoading()), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 40, true)));
1091
loadingTextView_ = loadingTextView;
1092
1093
static const ImageID symbols[4] = {
1094
ImageID("I_CROSS"),
1095
ImageID("I_CIRCLE"),
1096
ImageID("I_SQUARE"),
1097
ImageID("I_TRIANGLE"),
1098
};
1099
1100
Spinner *loadingSpinner = root_->Add(new Spinner(symbols, ARRAY_SIZE(symbols), new AnchorLayoutParams(NONE, NONE, 45, 45, true)));
1101
loadingSpinner_ = loadingSpinner;
1102
1103
loadingBG->SetTag("LoadingBG");
1104
loadingTextView->SetTag("LoadingText");
1105
loadingSpinner->SetTag("LoadingSpinner");
1106
1107
// Don't really need this, and it creates a lot of strings to translate...
1108
loadingTextView->SetVisibility(V_GONE);
1109
loadingTextView->SetShadow(true);
1110
1111
loadingViewColor_ = loadingSpinner->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut));
1112
loadingViewColor_->SetCallback([loadingBG, loadingTextView, loadingSpinner](View *v, uint32_t c) {
1113
loadingBG->SetColor(c & 0xFFC0C0C0);
1114
loadingTextView->SetTextColor(c);
1115
loadingSpinner->SetColor(alphaMul(c, 0.7f));
1116
});
1117
loadingViewColor_->Persist();
1118
1119
// We start invisible here, in case of recreated views.
1120
loadingViewVisible_ = loadingSpinner->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut));
1121
loadingViewVisible_->Persist();
1122
loadingViewVisible_->Finish.Add([loadingBG, loadingSpinner](EventParams &p) {
1123
loadingBG->SetVisibility(p.v->GetVisibility());
1124
1125
// If we just became invisible, flush BGs since we don't need them anymore.
1126
// Saves some VRAM for the game, but don't do it before we fade out...
1127
if (p.v->GetVisibility() == V_INVISIBLE) {
1128
g_gameInfoCache->FlushBGs();
1129
// And we can go away too. This means the tween will never run again.
1130
loadingBG->SetVisibility(V_GONE);
1131
loadingSpinner->SetVisibility(V_GONE);
1132
}
1133
return EVENT_DONE;
1134
});
1135
// Will become visible along with the loadingView.
1136
loadingBG->SetVisibility(V_INVISIBLE);
1137
}
1138
1139
UI::EventReturn EmuScreen::OnDevTools(UI::EventParams &params) {
1140
DevMenuScreen *devMenu = new DevMenuScreen(gamePath_, I18NCat::DEVELOPER);
1141
if (params.v)
1142
devMenu->SetPopupOrigin(params.v);
1143
screenManager()->push(devMenu);
1144
return UI::EVENT_DONE;
1145
}
1146
1147
UI::EventReturn EmuScreen::OnDisableCardboard(UI::EventParams &params) {
1148
g_Config.bEnableCardboardVR = false;
1149
return UI::EVENT_DONE;
1150
}
1151
1152
UI::EventReturn EmuScreen::OnChat(UI::EventParams &params) {
1153
if (chatButton_ != nullptr && chatButton_->GetVisibility() == UI::V_VISIBLE) {
1154
chatButton_->SetVisibility(UI::V_GONE);
1155
}
1156
if (chatMenu_ != nullptr) {
1157
chatMenu_->SetVisibility(UI::V_VISIBLE);
1158
1159
#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(SDL)
1160
UI::EnableFocusMovement(true);
1161
root_->SetDefaultFocusView(chatMenu_);
1162
1163
chatMenu_->SetFocus();
1164
UI::View *focused = UI::GetFocusedView();
1165
if (focused) {
1166
root_->SubviewFocused(focused);
1167
}
1168
#endif
1169
}
1170
return UI::EVENT_DONE;
1171
}
1172
1173
UI::EventReturn EmuScreen::OnResume(UI::EventParams &params) {
1174
if (coreState == CoreState::CORE_RUNTIME_ERROR) {
1175
// Force it!
1176
Memory::MemFault_IgnoreLastCrash();
1177
coreState = CoreState::CORE_RUNNING;
1178
}
1179
return UI::EVENT_DONE;
1180
}
1181
1182
void EmuScreen::update() {
1183
using namespace UI;
1184
1185
UIScreen::update();
1186
resumeButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR && Memory::MemFault_MayBeResumable() ? V_VISIBLE : V_GONE);
1187
resetButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);
1188
backButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);
1189
1190
if (chatButton_ && chatMenu_) {
1191
if (chatMenu_->GetVisibility() != V_GONE) {
1192
chatMessages_ = GetChatMessageCount();
1193
newChatMessages_ = 0;
1194
} else {
1195
int diff = GetChatMessageCount() - chatMessages_;
1196
// Cap the count at 50.
1197
newChatMessages_ = diff > 50 ? 50 : diff;
1198
}
1199
}
1200
1201
if (bootPending_) {
1202
// Keep trying the boot until bootPending_ is lifted.
1203
// It may be delayed due to RetroAchievements or any other cause.
1204
bootGame(gamePath_);
1205
}
1206
1207
// Simply forcibly update to the current screen size every frame. Doesn't cost much.
1208
// If bounds is set to be smaller than the actual pixel resolution of the display, respect that.
1209
// TODO: Should be able to use g_dpi_scale here instead. Might want to store the dpi scale in the UI context too.
1210
1211
#ifndef _WIN32
1212
const Bounds &bounds = screenManager()->getUIContext()->GetBounds();
1213
PSP_CoreParameter().pixelWidth = g_display.pixel_xres * bounds.w / g_display.dp_xres;
1214
PSP_CoreParameter().pixelHeight = g_display.pixel_yres * bounds.h / g_display.dp_yres;
1215
#endif
1216
1217
if (!invalid_) {
1218
UpdateUIState(coreState != CORE_RUNTIME_ERROR ? UISTATE_INGAME : UISTATE_EXCEPTION);
1219
}
1220
1221
if (errorMessage_.size()) {
1222
auto err = GetI18NCategory(I18NCat::ERRORS);
1223
std::string errLoadingFile = gamePath_.ToVisualString() + "\n";
1224
errLoadingFile.append(err->T("Error loading file", "Could not load game"));
1225
errLoadingFile.append(" ");
1226
errLoadingFile.append(err->T(errorMessage_.c_str()));
1227
1228
screenManager()->push(new PromptScreen(gamePath_, errLoadingFile, "OK", ""));
1229
errorMessage_.clear();
1230
quit_ = true;
1231
return;
1232
}
1233
1234
if (pauseTrigger_) {
1235
pauseTrigger_ = false;
1236
screenManager()->push(new GamePauseScreen(gamePath_));
1237
}
1238
1239
if (invalid_)
1240
return;
1241
1242
double now = time_now_d();
1243
1244
controlMapper_.Update(now);
1245
1246
if (saveStatePreview_ && !bootPending_) {
1247
int currentSlot = SaveState::GetCurrentSlot();
1248
if (saveStateSlot_ != currentSlot) {
1249
saveStateSlot_ = currentSlot;
1250
1251
Path fn;
1252
if (SaveState::HasSaveInSlot(gamePath_, currentSlot)) {
1253
fn = SaveState::GenerateSaveSlotFilename(gamePath_, currentSlot, SaveState::SCREENSHOT_EXTENSION);
1254
}
1255
1256
saveStatePreview_->SetFilename(fn);
1257
if (!fn.empty()) {
1258
saveStatePreview_->SetVisibility(UI::V_VISIBLE);
1259
saveStatePreviewShownTime_ = now;
1260
} else {
1261
saveStatePreview_->SetVisibility(UI::V_GONE);
1262
}
1263
}
1264
1265
if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {
1266
double endTime = saveStatePreviewShownTime_ + 2.0;
1267
float alpha = clamp_value((endTime - now) * 4.0, 0.0, 1.0);
1268
saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha));
1269
1270
if (now - saveStatePreviewShownTime_ > 2) {
1271
saveStatePreview_->SetVisibility(UI::V_GONE);
1272
}
1273
}
1274
}
1275
}
1276
1277
bool EmuScreen::checkPowerDown() {
1278
if (PSP_IsRebooting()) {
1279
bootPending_ = true;
1280
invalid_ = true;
1281
}
1282
1283
if (coreState == CORE_POWERDOWN && !PSP_IsIniting() && !PSP_IsRebooting()) {
1284
if (PSP_IsInited()) {
1285
PSP_Shutdown();
1286
}
1287
INFO_LOG(Log::System, "SELF-POWERDOWN!");
1288
screenManager()->switchScreen(new MainScreen());
1289
bootPending_ = false;
1290
invalid_ = true;
1291
return true;
1292
}
1293
return false;
1294
}
1295
1296
ScreenRenderRole EmuScreen::renderRole(bool isTop) const {
1297
auto CanBeBackground = [&]() -> bool {
1298
if (g_Config.bSkipBufferEffects) {
1299
return isTop || (g_Config.bTransparentBackground && Core_ShouldRunBehind());
1300
}
1301
1302
if (!g_Config.bTransparentBackground && !isTop) {
1303
if (Core_ShouldRunBehind() || screenManager()->topScreen()->wantBrightBackground())
1304
return true;
1305
return false;
1306
}
1307
1308
if (invalid_) {
1309
return false;
1310
}
1311
1312
return true;
1313
};
1314
1315
ScreenRenderRole role = ScreenRenderRole::MUST_BE_FIRST;
1316
if (CanBeBackground()) {
1317
role |= ScreenRenderRole::CAN_BE_BACKGROUND;
1318
}
1319
return role;
1320
}
1321
1322
void EmuScreen::darken() {
1323
if (!screenManager()->topScreen()->wantBrightBackground()) {
1324
UIContext &dc = *screenManager()->getUIContext();
1325
uint32_t color = GetBackgroundColorWithAlpha(dc);
1326
dc.Begin();
1327
dc.RebindTexture();
1328
dc.FillRect(UI::Drawable(color), dc.GetBounds());
1329
dc.Flush();
1330
}
1331
}
1332
1333
ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {
1334
ScreenRenderFlags flags = ScreenRenderFlags::NONE;
1335
Draw::Viewport viewport{ 0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f };
1336
using namespace Draw;
1337
1338
DrawContext *draw = screenManager()->getDrawContext();
1339
if (!draw) {
1340
return flags; // shouldn't really happen but I've seen a suspicious stack trace..
1341
}
1342
1343
GamepadUpdateOpacity();
1344
1345
bool skipBufferEffects = g_Config.bSkipBufferEffects;
1346
1347
bool framebufferBound = false;
1348
1349
if (mode & ScreenRenderMode::FIRST) {
1350
// Actually, always gonna be first when it exists (?)
1351
1352
// Here we do NOT bind the backbuffer or clear the screen, unless non-buffered.
1353
// The emuscreen is different than the others - we really want to allow the game to render to framebuffers
1354
// before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render
1355
// targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here.
1356
// We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)...
1357
// We do, however, start the frame in other ways.
1358
1359
if (skipBufferEffects && !g_Config.bSoftwareRendering) {
1360
// We need to clear here already so that drawing during the frame is done on a clean slate.
1361
if (Core_IsStepping() && gpuStats.numFlips != 0) {
1362
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_BackBuffer");
1363
} else {
1364
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "EmuScreen_BackBuffer");
1365
}
1366
1367
draw->SetViewport(viewport);
1368
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1369
skipBufferEffects = true;
1370
framebufferBound = true;
1371
}
1372
draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);
1373
}
1374
1375
g_OSD.NudgeSidebar();
1376
1377
if (mode & ScreenRenderMode::TOP) {
1378
System_Notify(SystemNotification::KEEP_SCREEN_AWAKE);
1379
} else if (!Core_ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) {
1380
// Just to make sure.
1381
if (PSP_IsInited() && !skipBufferEffects) {
1382
_dbg_assert_(gpu);
1383
PSP_BeginHostFrame();
1384
gpu->CopyDisplayToOutput(true);
1385
PSP_EndHostFrame();
1386
}
1387
if (!framebufferBound && (!gpu || !gpu->PresentedThisFrame())) {
1388
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_Behind");
1389
}
1390
// Need to make sure the UI texture is available, for "darken".
1391
screenManager()->getUIContext()->BeginFrame();
1392
draw->SetViewport(viewport);
1393
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1394
darken();
1395
return flags;
1396
}
1397
1398
if (invalid_) {
1399
// Loading, or after shutdown?
1400
if (loadingTextView_ && loadingTextView_->GetVisibility() == UI::V_VISIBLE)
1401
loadingTextView_->SetText(PSP_GetLoading());
1402
1403
// It's possible this might be set outside PSP_RunLoopFor().
1404
// In this case, we need to double check it here.
1405
if (mode & ScreenRenderMode::TOP) {
1406
checkPowerDown();
1407
}
1408
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_Invalid");
1409
// Need to make sure the UI texture is available, for "darken".
1410
screenManager()->getUIContext()->BeginFrame();
1411
draw->SetViewport(viewport);
1412
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1413
renderUI();
1414
return flags;
1415
}
1416
1417
// Freeze-frame functionality (loads a savestate on every frame).
1418
if (PSP_CoreParameter().freezeNext) {
1419
PSP_CoreParameter().frozen = true;
1420
PSP_CoreParameter().freezeNext = false;
1421
SaveState::SaveToRam(freezeState_);
1422
} else if (PSP_CoreParameter().frozen) {
1423
std::string errorString;
1424
if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {
1425
ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());
1426
PSP_CoreParameter().frozen = false;
1427
}
1428
}
1429
1430
Core_UpdateDebugStats((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS || g_Config.bLogFrameDrops);
1431
1432
if (doFrameAdvance_.exchange(false)) {
1433
if (!Achievements::WarnUserIfHardcoreModeActive(false)) {
1434
// If game is running, pause emulation immediately. Otherwise, advance a single frame.
1435
if (Core_IsStepping()) {
1436
frameStep_ = true;
1437
Core_EnableStepping(false);
1438
} else if (!frameStep_) {
1439
lastNumFlips = gpuStats.numFlips;
1440
Core_EnableStepping(true, "ui.frameAdvance", 0);
1441
}
1442
}
1443
}
1444
1445
bool blockedExecution = Achievements::IsBlockingExecution();
1446
uint32_t clearColor = 0;
1447
if (!blockedExecution) {
1448
PSP_BeginHostFrame();
1449
PSP_RunLoopWhileState();
1450
1451
flags |= ScreenRenderFlags::HANDLED_THROTTLING;
1452
1453
// Hopefully coreState is now CORE_NEXTFRAME
1454
switch (coreState) {
1455
case CORE_NEXTFRAME:
1456
// Reached the end of the frame, all good. Set back to running for the next frame
1457
coreState = CORE_RUNNING;
1458
break;
1459
case CORE_STEPPING:
1460
case CORE_RUNTIME_ERROR:
1461
{
1462
// If there's an exception, display information.
1463
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
1464
if (info.type != MIPSExceptionType::NONE) {
1465
// Clear to blue background screen
1466
bool dangerousSettings = !Reporting::IsSupported();
1467
clearColor = dangerousSettings ? 0xFF900050 : 0xFF900000;
1468
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_RuntimeError");
1469
framebufferBound = true;
1470
// The info is drawn later in renderUI
1471
} else {
1472
// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.
1473
// This won't work in non-buffered, but that's fine.
1474
if (!framebufferBound && PSP_IsInited()) {
1475
// draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_Stepping");
1476
gpu->CopyDisplayToOutput(true);
1477
framebufferBound = true;
1478
}
1479
}
1480
break;
1481
}
1482
default:
1483
// Didn't actually reach the end of the frame, ran out of the blockTicks cycles.
1484
// In this case we need to bind and wipe the backbuffer, at least.
1485
// It's possible we never ended up outputted anything - make sure we have the backbuffer cleared
1486
// So, we don't set framebufferBound here.
1487
break;
1488
}
1489
1490
if (framebufferBound && gpu) {
1491
gpu->PresentedThisFrame();
1492
}
1493
1494
PSP_EndHostFrame();
1495
1496
// This place rougly matches how libretro handles it (after retro_frame).
1497
Achievements::FrameUpdate();
1498
}
1499
1500
if (gpu && gpu->PresentedThisFrame()) {
1501
framebufferBound = true;
1502
}
1503
1504
if (!framebufferBound) {
1505
draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_NoFrame");
1506
draw->SetViewport(viewport);
1507
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1508
}
1509
1510
Draw::BackendState state = draw->GetCurrentBackendState();
1511
1512
// We allow if !state.valid, that means it's not the Vulkan backend.
1513
_assert_msg_(!state.valid || state.passes >= 1, "skipB: %d sw: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering);
1514
1515
screenManager()->getUIContext()->BeginFrame();
1516
1517
if (!(mode & ScreenRenderMode::TOP)) {
1518
// We're in run-behind mode, but we don't want to draw chat, debug UI and stuff.
1519
// So, darken and bail here.
1520
// Reset viewport/scissor to be sure.
1521
draw->SetViewport(viewport);
1522
draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);
1523
darken();
1524
return flags;
1525
}
1526
1527
// NOTE: We don't check for powerdown if we're not the top screen.
1528
checkPowerDown();
1529
1530
if (hasVisibleUI()) {
1531
draw->SetViewport(viewport);
1532
cardboardDisableButton_->SetVisibility(g_Config.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE);
1533
screenManager()->getUIContext()->BeginFrame();
1534
renderUI();
1535
}
1536
1537
if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) {
1538
SetVRAppMode(VRAppMode::VR_DIALOG_MODE);
1539
} else {
1540
SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE);
1541
}
1542
1543
if (!(mode & ScreenRenderMode::TOP)) {
1544
darken();
1545
}
1546
return flags;
1547
}
1548
1549
bool EmuScreen::hasVisibleUI() {
1550
// Regular but uncommon UI.
1551
if (saveStatePreview_->GetVisibility() != UI::V_GONE || loadingSpinner_->GetVisibility() == UI::V_VISIBLE)
1552
return true;
1553
if (!g_OSD.IsEmpty() || g_Config.bShowTouchControls || g_Config.iShowStatusFlags != 0)
1554
return true;
1555
if (g_Config.bEnableCardboardVR || g_Config.bEnableNetworkChat)
1556
return true;
1557
if (g_Config.bShowGPOLEDs)
1558
return true;
1559
// Debug UI.
1560
if ((DebugOverlay)g_Config.iDebugOverlay != DebugOverlay::OFF || g_Config.bShowDeveloperMenu)
1561
return true;
1562
1563
// Exception information.
1564
if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) {
1565
return true;
1566
}
1567
1568
return false;
1569
}
1570
1571
void EmuScreen::renderUI() {
1572
using namespace Draw;
1573
1574
DrawContext *thin3d = screenManager()->getDrawContext();
1575
UIContext *ctx = screenManager()->getUIContext();
1576
ctx->BeginFrame();
1577
// This sets up some important states but not the viewport.
1578
ctx->Begin();
1579
1580
Viewport viewport;
1581
viewport.TopLeftX = 0;
1582
viewport.TopLeftY = 0;
1583
viewport.Width = g_display.pixel_xres;
1584
viewport.Height = g_display.pixel_yres;
1585
viewport.MaxDepth = 1.0;
1586
viewport.MinDepth = 0.0;
1587
thin3d->SetViewport(viewport);
1588
1589
if (root_) {
1590
UI::LayoutViewHierarchy(*ctx, root_, false);
1591
root_->Draw(*ctx);
1592
}
1593
1594
if (!invalid_) {
1595
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::CONTROL) {
1596
DrawControlMapperOverlay(ctx, ctx->GetLayoutBounds(), controlMapper_);
1597
}
1598
if (g_Config.iShowStatusFlags) {
1599
DrawFPS(ctx, ctx->GetLayoutBounds());
1600
}
1601
}
1602
1603
#ifdef USE_PROFILER
1604
if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_PROFILE && !invalid_) {
1605
DrawProfile(*ctx);
1606
}
1607
#endif
1608
1609
if (g_Config.bShowGPOLEDs) {
1610
// Draw a vertical strip of LEDs at the right side of the screen.
1611
const float ledSize = 24.0f;
1612
const float spacing = 4.0f;
1613
const float height = 8 * ledSize + 7 * spacing;
1614
const float x = ctx->GetBounds().w - spacing - ledSize;
1615
const float y = (ctx->GetBounds().h - height) * 0.5f;
1616
ctx->FillRect(UI::Drawable(0xFF000000), Bounds(x - spacing, y - spacing, ledSize + spacing * 2, height + spacing * 2));
1617
for (int i = 0; i < 8; i++) {
1618
int bit = (g_GPOBits >> i) & 1;
1619
uint32_t color = 0xFF30FF30;
1620
if (!bit) {
1621
color = darkenColor(darkenColor(color));
1622
}
1623
Bounds ledBounds(x, y + (spacing + ledSize) * i, ledSize, ledSize);
1624
ctx->FillRect(UI::Drawable(color), ledBounds);
1625
}
1626
ctx->Flush();
1627
}
1628
1629
if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) {
1630
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
1631
if (info.type != MIPSExceptionType::NONE) {
1632
DrawCrashDump(ctx, gamePath_);
1633
} else {
1634
// We're somehow in ERROR or STEPPING without a crash dump. This case is what lead
1635
// to the bare "Resume" and "Reset" buttons without a crash dump before, in cases
1636
// where we were unable to ignore memory errors.
1637
}
1638
}
1639
1640
ctx->Flush();
1641
}
1642
1643
void EmuScreen::autoLoad() {
1644
int autoSlot = -1;
1645
1646
//check if save state has save, if so, load
1647
switch (g_Config.iAutoLoadSaveState) {
1648
case (int)AutoLoadSaveState::OFF: // "AutoLoad Off"
1649
return;
1650
case (int)AutoLoadSaveState::OLDEST: // "Oldest Save"
1651
autoSlot = SaveState::GetOldestSlot(gamePath_);
1652
break;
1653
case (int)AutoLoadSaveState::NEWEST: // "Newest Save"
1654
autoSlot = SaveState::GetNewestSlot(gamePath_);
1655
break;
1656
default: // try the specific save state slot specified
1657
autoSlot = (SaveState::HasSaveInSlot(gamePath_, g_Config.iAutoLoadSaveState - 3)) ? (g_Config.iAutoLoadSaveState - 3) : -1;
1658
break;
1659
}
1660
1661
if (g_Config.iAutoLoadSaveState && autoSlot != -1) {
1662
SaveState::LoadSlot(gamePath_, autoSlot, &AfterSaveStateAction);
1663
g_Config.iCurrentStateSlot = autoSlot;
1664
}
1665
}
1666
1667
void EmuScreen::resized() {
1668
RecreateViews();
1669
}
1670
1671