CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
CoCalc provides the best real-time collaborative environment for Jupyter Notebooks, LaTeX documents, and SageMath, scalable from individual users to large groups and classes!
Path: blob/master/UI/EmuScreen.cpp
Views: 1401
// Copyright (c) 2012- PPSSPP Project.12// This program is free software: you can redistribute it and/or modify3// it under the terms of the GNU General Public License as published by4// the Free Software Foundation, version 2.0 or later versions.56// This program is distributed in the hope that it will be useful,7// but WITHOUT ANY WARRANTY; without even the implied warranty of8// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the9// GNU General Public License 2.0 for more details.1011// A copy of the GPL 2.0 should have been included with the program.12// If not, see http://www.gnu.org/licenses/1314// Official git repository and contact information can be found at15// https://github.com/hrydgard/ppsspp and http://www.ppsspp.org/.1617#include "ppsspp_config.h"1819#include <algorithm>20#include <functional>2122using namespace std::placeholders;2324#include "Common/Render/TextureAtlas.h"25#include "Common/GPU/OpenGL/GLFeatures.h"26#include "Common/Render/Text/draw_text.h"27#include "Common/File/FileUtil.h"28#include "Common/Battery/Battery.h"2930#include "Common/UI/Root.h"31#include "Common/UI/UI.h"32#include "Common/UI/Context.h"33#include "Common/UI/Tween.h"34#include "Common/UI/View.h"35#include "Common/UI/AsyncImageFileView.h"36#include "Common/VR/PPSSPPVR.h"3738#include "Common/Data/Text/I18n.h"39#include "Common/Input/InputState.h"40#include "Common/Log.h"41#include "Common/System/Display.h"42#include "Common/System/System.h"43#include "Common/System/NativeApp.h"44#include "Common/System/Request.h"45#include "Common/System/OSD.h"46#include "Common/Profiler/Profiler.h"47#include "Common/Math/curves.h"48#include "Common/TimeUtil.h"4950#ifndef MOBILE_DEVICE51#include "Core/AVIDump.h"52#endif53#include "Core/Config.h"54#include "Core/ConfigValues.h"55#include "Core/CoreTiming.h"56#include "Core/CoreParameter.h"57#include "Core/Core.h"58#include "Core/KeyMap.h"59#include "Core/MemFault.h"60#include "Core/Reporting.h"61#include "Core/System.h"62#include "Core/FileSystems/VirtualDiscFileSystem.h"63#include "GPU/GPUState.h"64#include "GPU/GPUInterface.h"65#include "GPU/Common/FramebufferManagerCommon.h"66#if !PPSSPP_PLATFORM(UWP)67#include "GPU/Vulkan/DebugVisVulkan.h"68#endif69#include "Core/MIPS/MIPS.h"70#include "Core/HLE/sceCtrl.h"71#include "Core/HLE/sceSas.h"72#include "Core/Debugger/SymbolMap.h"73#include "Core/RetroAchievements.h"74#include "Core/SaveState.h"75#include "Core/HLE/__sceAudio.h"76#include "Core/HLE/proAdhoc.h"77#include "Core/HW/Display.h"7879#include "UI/BackgroundAudio.h"80#include "UI/OnScreenDisplay.h"81#include "UI/GamepadEmu.h"82#include "UI/PauseScreen.h"83#include "UI/MainScreen.h"84#include "UI/EmuScreen.h"85#include "UI/DevScreens.h"86#include "UI/GameInfoCache.h"87#include "UI/MiscScreens.h"88#include "UI/ControlMappingScreen.h"89#include "UI/DisplayLayoutScreen.h"90#include "UI/GameSettingsScreen.h"91#include "UI/ProfilerDraw.h"92#include "UI/DiscordIntegration.h"93#include "UI/ChatScreen.h"94#include "UI/DebugOverlay.h"9596#include "Core/Reporting.h"9798#if PPSSPP_PLATFORM(WINDOWS) && !PPSSPP_PLATFORM(UWP)99#include "Windows/MainWindow.h"100#endif101102#ifndef MOBILE_DEVICE103static AVIDump avi;104#endif105106// TODO: Ugly!107static bool frameStep_;108static int lastNumFlips;109static bool startDumping;110111extern bool g_TakeScreenshot;112113static void __EmuScreenVblank()114{115auto sy = GetI18NCategory(I18NCat::SYSTEM);116117if (frameStep_ && lastNumFlips != gpuStats.numFlips)118{119frameStep_ = false;120Core_EnableStepping(true, "ui.frameAdvance", 0);121lastNumFlips = gpuStats.numFlips;122}123#ifndef MOBILE_DEVICE124if (g_Config.bDumpFrames && !startDumping)125{126avi.Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);127g_OSD.Show(OSDType::MESSAGE_INFO, sy->T("AVI Dump started."), 1.0f);128startDumping = true;129}130if (g_Config.bDumpFrames && startDumping)131{132avi.AddFrame();133}134else if (!g_Config.bDumpFrames && startDumping)135{136avi.Stop();137g_OSD.Show(OSDType::MESSAGE_INFO, sy->T("AVI Dump stopped."), 1.0f);138startDumping = false;139}140#endif141}142143// Handles control rotation due to internal screen rotation.144static void SetPSPAnalog(int stick, float x, float y) {145switch (g_Config.iInternalScreenRotation) {146case ROTATION_LOCKED_HORIZONTAL:147// Standard rotation. No change.148break;149case ROTATION_LOCKED_HORIZONTAL180:150x = -x;151y = -y;152break;153case ROTATION_LOCKED_VERTICAL:154{155float new_y = -x;156x = y;157y = new_y;158break;159}160case ROTATION_LOCKED_VERTICAL180:161{162float new_y = y = x;163x = -y;164y = new_y;165break;166}167default:168break;169}170__CtrlSetAnalogXY(stick, x, y);171}172173EmuScreen::EmuScreen(const Path &filename)174: gamePath_(filename) {175saveStateSlot_ = SaveState::GetCurrentSlot();176__DisplayListenVblank(__EmuScreenVblank);177frameStep_ = false;178lastNumFlips = gpuStats.numFlips;179startDumping = false;180controlMapper_.SetCallbacks(181std::bind(&EmuScreen::onVKey, this, _1, _2),182std::bind(&EmuScreen::onVKeyAnalog, this, _1, _2),183[](uint32_t bitsToSet, uint32_t bitsToClear) {184__CtrlUpdateButtons(bitsToSet, bitsToClear);185},186&SetPSPAnalog,187nullptr);188189// Make sure we don't leave it at powerdown after the last game.190// TODO: This really should be handled elsewhere if it isn't.191if (coreState == CORE_POWERDOWN)192coreState = CORE_STEPPING;193194OnDevMenu.Handle(this, &EmuScreen::OnDevTools);195OnChatMenu.Handle(this, &EmuScreen::OnChat);196197// Usually, we don't want focus movement enabled on this screen, so disable on start.198// Only if you open chat or dev tools do we want it to start working.199UI::EnableFocusMovement(false);200}201202bool EmuScreen::bootAllowStorage(const Path &filename) {203// No permissions needed. The easy life.204if (filename.Type() == PathType::HTTP)205return true;206207if (!System_GetPropertyBool(SYSPROP_SUPPORTS_PERMISSIONS))208return true;209210PermissionStatus status = System_GetPermissionStatus(SYSTEM_PERMISSION_STORAGE);211switch (status) {212case PERMISSION_STATUS_UNKNOWN:213System_AskForPermission(SYSTEM_PERMISSION_STORAGE);214return false;215216case PERMISSION_STATUS_DENIED:217stopRender_ = true;218screenManager()->switchScreen(new MainScreen());219return false;220221case PERMISSION_STATUS_PENDING:222// Keep waiting.223return false;224225case PERMISSION_STATUS_GRANTED:226return true;227}228229_assert_(false);230return false;231}232233void EmuScreen::bootGame(const Path &filename) {234if (Achievements::IsBlockingExecution()) {235// Keep waiting.236return;237}238239if (PSP_IsRebooting())240return;241if (PSP_IsInited()) {242bootPending_ = false;243invalid_ = false;244bootComplete();245return;246}247248if (PSP_IsIniting()) {249std::string error_string = "(unknown error)";250251bootPending_ = !PSP_InitUpdate(&error_string);252253if (!bootPending_) {254invalid_ = !PSP_IsInited();255if (invalid_) {256errorMessage_ = error_string;257ERROR_LOG(Log::Boot, "isIniting bootGame error: %s", errorMessage_.c_str());258return;259}260bootComplete();261}262return;263}264265g_BackgroundAudio.SetGame(Path());266267// Check permission status first, in case we came from a shortcut.268if (!bootAllowStorage(filename))269return;270271invalid_ = true;272273// We don't want to boot with the wrong game specific config, so wait until info is ready.274std::shared_ptr<GameInfo> info = g_gameInfoCache->GetInfo(nullptr, filename, GameInfoFlags::PARAM_SFO);275if (!info->Ready(GameInfoFlags::PARAM_SFO)) {276return;277}278279auto sc = GetI18NCategory(I18NCat::SCREEN);280if (info->fileType == IdentifiedFileType::PSP_DISC_DIRECTORY) {281// Check for existence of ppsspp-index.lst - if it exists, the user likely knows what they're doing.282// TODO: Better would be to check that it was loaded successfully.283if (!File::Exists(filename / INDEX_FILENAME)) {284g_OSD.Show(OSDType::MESSAGE_CENTERED_WARNING, sc->T("ExtractedIsoWarning", "Extracted ISOs often don't work.\nPlay the ISO file directly."), gamePath_.ToVisualString(), 7.0f);285} else {286INFO_LOG(Log::Loader, "Extracted ISO loaded without warning - %s is present.", INDEX_FILENAME.c_str());287}288}289290extraAssertInfoStr_ = info->id + " " + info->GetTitle();291SetExtraAssertInfo(extraAssertInfoStr_.c_str());292293if (!info->id.empty()) {294g_Config.loadGameConfig(info->id, info->GetTitle());295// Reset views in case controls are in a different place.296RecreateViews();297298g_Discord.SetPresenceGame(info->GetTitle());299} else {300g_Discord.SetPresenceGame(sc->T("Untitled PSP game"));301}302303CoreParameter coreParam{};304coreParam.cpuCore = (CPUCore)g_Config.iCpuCore;305coreParam.gpuCore = GPUCORE_GLES;306switch (GetGPUBackend()) {307case GPUBackend::DIRECT3D11:308coreParam.gpuCore = GPUCORE_DIRECTX11;309break;310#if !PPSSPP_PLATFORM(UWP)311#if PPSSPP_API(ANY_GL)312case GPUBackend::OPENGL:313coreParam.gpuCore = GPUCORE_GLES;314break;315#endif316case GPUBackend::DIRECT3D9:317coreParam.gpuCore = GPUCORE_DIRECTX9;318break;319case GPUBackend::VULKAN:320coreParam.gpuCore = GPUCORE_VULKAN;321break;322#endif323}324325// Preserve the existing graphics context.326coreParam.graphicsContext = PSP_CoreParameter().graphicsContext;327coreParam.enableSound = g_Config.bEnableSound;328coreParam.fileToStart = filename;329coreParam.mountIso.clear();330coreParam.mountRoot.clear();331coreParam.startBreak = !g_Config.bAutoRun;332coreParam.headLess = false;333334if (g_Config.iInternalResolution == 0) {335coreParam.renderWidth = g_display.pixel_xres;336coreParam.renderHeight = g_display.pixel_yres;337} else {338if (g_Config.iInternalResolution < 0)339g_Config.iInternalResolution = 1;340coreParam.renderWidth = 480 * g_Config.iInternalResolution;341coreParam.renderHeight = 272 * g_Config.iInternalResolution;342}343coreParam.pixelWidth = g_display.pixel_xres;344coreParam.pixelHeight = g_display.pixel_yres;345346std::string error_string;347if (!PSP_InitStart(coreParam, &error_string)) {348bootPending_ = false;349invalid_ = true;350errorMessage_ = error_string;351ERROR_LOG(Log::Boot, "InitStart bootGame error: %s", errorMessage_.c_str());352}353354if (PSP_CoreParameter().compat.flags().RequireBufferedRendering && g_Config.bSkipBufferEffects) {355auto gr = GetI18NCategory(I18NCat::GRAPHICS);356g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BufferedRenderingRequired", "Warning: This game requires Rendering Mode to be set to Buffered."), 10.0f);357}358359if (PSP_CoreParameter().compat.flags().RequireBlockTransfer && g_Config.iSkipGPUReadbackMode != (int)SkipGPUReadbackMode::NO_SKIP && !PSP_CoreParameter().compat.flags().ForceEnableGPUReadback) {360auto gr = GetI18NCategory(I18NCat::GRAPHICS);361g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("BlockTransferRequired", "Warning: This game requires Skip GPU Readbacks be set to No."), 10.0f);362}363364if (PSP_CoreParameter().compat.flags().RequireDefaultCPUClock && g_Config.iLockedCPUSpeed != 0) {365auto gr = GetI18NCategory(I18NCat::GRAPHICS);366g_OSD.Show(OSDType::MESSAGE_WARNING, gr->T("DefaultCPUClockRequired", "Warning: This game requires the CPU clock to be set to default."), 10.0f);367}368369loadingViewColor_->Divert(0xFFFFFFFF, 0.75f);370loadingViewVisible_->Divert(UI::V_VISIBLE, 0.75f);371372screenManager()->getDrawContext()->ResetStats();373374if (bootPending_) {375System_PostUIMessage(UIMessage::GAME_SELECTED, filename.c_str());376}377}378379void EmuScreen::bootComplete() {380UpdateUIState(UISTATE_INGAME);381System_Notify(SystemNotification::BOOT_DONE);382System_Notify(SystemNotification::DISASSEMBLY);383384NOTICE_LOG(Log::Boot, "Booted %s...", PSP_CoreParameter().fileToStart.c_str());385if (!Achievements::HardcoreModeActive()) {386// Don't auto-load savestates in hardcore mode.387autoLoad();388}389390auto sc = GetI18NCategory(I18NCat::SCREEN);391392#ifndef MOBILE_DEVICE393if (g_Config.bFirstRun) {394g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("PressESC", "Press ESC to open the pause menu"));395}396#endif397398#if !PPSSPP_PLATFORM(UWP)399if (GetGPUBackend() == GPUBackend::OPENGL) {400const char *renderer = gl_extensions.model;401if (strstr(renderer, "Chainfire3D") != 0) {402g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("Chainfire3DWarning", "WARNING: Chainfire3D detected, may cause problems"), 10.0f);403} else if (strstr(renderer, "GLTools") != 0) {404g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("GLToolsWarning", "WARNING: GLTools detected, may cause problems"), 10.0f);405}406407if (g_Config.bGfxDebugOutput) {408g_OSD.Show(OSDType::MESSAGE_WARNING, "WARNING: GfxDebugOutput is enabled via ppsspp.ini. Things may be slow.", 10.0f);409}410}411#endif412413if (Core_GetPowerSaving()) {414auto sy = GetI18NCategory(I18NCat::SYSTEM);415#ifdef __ANDROID__416g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Android battery save mode is on"), 2.0f, "core_powerSaving");417#else418g_OSD.Show(OSDType::MESSAGE_WARNING, sy->T("WARNING: Battery save mode is on"), 2.0f, "core_powerSaving");419#endif420}421422if (g_Config.bStereoRendering) {423auto gr = GetI18NCategory(I18NCat::GRAPHICS);424auto di = GetI18NCategory(I18NCat::DIALOG);425// Stereo rendering is experimental, so let's notify the user it's being used.426// Carefully reuse translations for this rare warning.427g_OSD.Show(OSDType::MESSAGE_WARNING, std::string(gr->T("Stereo rendering")) + ": " + std::string(di->T("Enabled")));428}429430saveStateSlot_ = SaveState::GetCurrentSlot();431432loadingViewColor_->Divert(0x00FFFFFF, 0.2f);433loadingViewVisible_->Divert(UI::V_INVISIBLE, 0.2f);434435std::string gameID = g_paramSFO.GetValueString("DISC_ID");436g_Config.TimeTracker().Start(gameID);437}438439EmuScreen::~EmuScreen() {440std::string gameID = g_paramSFO.GetValueString("DISC_ID");441g_Config.TimeTracker().Stop(gameID);442443// If we were invalid, it would already be shutdown.444if (!invalid_ || bootPending_) {445PSP_Shutdown();446}447448System_PostUIMessage(UIMessage::GAME_SELECTED, "");449450g_OSD.ClearAchievementStuff();451452SetExtraAssertInfo(nullptr);453454#ifndef MOBILE_DEVICE455if (g_Config.bDumpFrames && startDumping)456{457avi.Stop();458g_OSD.Show(OSDType::MESSAGE_INFO, "AVI Dump stopped.", 2.0f);459startDumping = false;460}461#endif462463if (GetUIState() == UISTATE_EXIT)464g_Discord.ClearPresence();465else466g_Discord.SetPresenceMenu();467}468469void EmuScreen::dialogFinished(const Screen *dialog, DialogResult result) {470if (std::string_view(dialog->tag()) == "TextEditPopup") {471// Chat message finished.472return;473}474475// TODO: improve the way with which we got commands from PauseMenu.476// DR_CANCEL/DR_BACK means clicked on "continue", DR_OK means clicked on "back to menu",477// DR_YES means a message sent to PauseMenu by System_PostUIMessage.478if (result == DR_OK || quit_) {479screenManager()->switchScreen(new MainScreen());480quit_ = false;481}482// Returning to the PauseScreen, unless we're stepping, means we should go back to controls.483if (Core_IsActive())484UI::EnableFocusMovement(false);485RecreateViews();486SetExtraAssertInfo(extraAssertInfoStr_.c_str());487}488489static void AfterSaveStateAction(SaveState::Status status, std::string_view message, void *) {490if (!message.empty() && (!g_Config.bDumpFrames || !g_Config.bDumpVideoOutput)) {491g_OSD.Show(status == SaveState::Status::SUCCESS ? OSDType::MESSAGE_SUCCESS : OSDType::MESSAGE_ERROR, message, status == SaveState::Status::SUCCESS ? 2.0 : 5.0);492}493}494495static void AfterStateBoot(SaveState::Status status, std::string_view message, void *ignored) {496AfterSaveStateAction(status, message, ignored);497Core_EnableStepping(false);498System_Notify(SystemNotification::DISASSEMBLY);499}500501void EmuScreen::focusChanged(ScreenFocusChange focusChange) {502Screen::focusChanged(focusChange);503504std::string gameID = g_paramSFO.GetValueString("DISC_ID");505if (gameID.empty()) {506// startup or shutdown507return;508}509switch (focusChange) {510case ScreenFocusChange::FOCUS_LOST_TOP:511g_Config.TimeTracker().Stop(gameID);512controlMapper_.ReleaseAll();513break;514case ScreenFocusChange::FOCUS_BECAME_TOP:515g_Config.TimeTracker().Start(gameID);516break;517}518}519520void EmuScreen::sendMessage(UIMessage message, const char *value) {521// External commands, like from the Windows UI.522if (message == UIMessage::REQUEST_GAME_PAUSE && screenManager()->topScreen() == this) {523screenManager()->push(new GamePauseScreen(gamePath_));524} else if (message == UIMessage::REQUEST_GAME_STOP) {525// We will push MainScreen in update().526PSP_Shutdown();527bootPending_ = false;528stopRender_ = true;529invalid_ = true;530System_Notify(SystemNotification::DISASSEMBLY);531} else if (message == UIMessage::REQUEST_GAME_RESET) {532PSP_Shutdown();533bootPending_ = true;534invalid_ = true;535System_Notify(SystemNotification::DISASSEMBLY);536537std::string resetError;538if (!PSP_InitStart(PSP_CoreParameter(), &resetError)) {539ERROR_LOG(Log::Loader, "Error resetting: %s", resetError.c_str());540stopRender_ = true;541screenManager()->switchScreen(new MainScreen());542return;543}544} else if (message == UIMessage::REQUEST_GAME_BOOT) {545// TODO: Ignore or not if it's the same game that's already running?546if (gamePath_ == Path(value)) {547WARN_LOG(Log::Loader, "Game already running, ignoring");548return;549}550const char *ext = strrchr(value, '.');551if (ext != nullptr && !strcmp(ext, ".ppst")) {552SaveState::Load(Path(value), -1, &AfterStateBoot);553} else {554PSP_Shutdown();555bootPending_ = true;556gamePath_ = Path(value);557// Don't leave it on CORE_POWERDOWN, we'll sometimes aggressively bail.558Core_UpdateState(CORE_POWERUP);559}560} else if (message == UIMessage::CONFIG_LOADED) {561// In case we need to position touch controls differently.562RecreateViews();563} else if (message == UIMessage::SHOW_CONTROL_MAPPING && screenManager()->topScreen() == this) {564UpdateUIState(UISTATE_PAUSEMENU);565screenManager()->push(new ControlMappingScreen(gamePath_));566} else if (message == UIMessage::SHOW_DISPLAY_LAYOUT_EDITOR && screenManager()->topScreen() == this) {567UpdateUIState(UISTATE_PAUSEMENU);568screenManager()->push(new DisplayLayoutScreen(gamePath_));569} else if (message == UIMessage::SHOW_SETTINGS && screenManager()->topScreen() == this) {570UpdateUIState(UISTATE_PAUSEMENU);571screenManager()->push(new GameSettingsScreen(gamePath_));572} else if (message == UIMessage::REQUEST_GPU_DUMP_NEXT_FRAME) {573if (gpu)574gpu->DumpNextFrame();575} else if (message == UIMessage::REQUEST_CLEAR_JIT) {576currentMIPS->ClearJitCache();577if (PSP_IsInited()) {578currentMIPS->UpdateCore((CPUCore)g_Config.iCpuCore);579}580} else if (message == UIMessage::WINDOW_MINIMIZED) {581if (!strcmp(value, "true")) {582gstate_c.skipDrawReason |= SKIPDRAW_WINDOW_MINIMIZED;583} else {584gstate_c.skipDrawReason &= ~SKIPDRAW_WINDOW_MINIMIZED;585}586} else if (message == UIMessage::SHOW_CHAT_SCREEN) {587if (g_Config.bEnableNetworkChat) {588if (!chatButton_)589RecreateViews();590591if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_DESKTOP) {592// temporary workaround for hotkey its freeze the ui when open chat screen using hotkey and native keyboard is enable593if (g_Config.bBypassOSKWithKeyboard) {594// TODO: Make translatable.595g_OSD.Show(OSDType::MESSAGE_INFO, "Disable \"Use system native keyboard\" to use ctrl + c hotkey", 2.0f);596} else {597UI::EventParams e{};598OnChatMenu.Trigger(e);599}600} else {601UI::EventParams e{};602OnChatMenu.Trigger(e);603}604}605} else if (message == UIMessage::APP_RESUMED && screenManager()->topScreen() == this) {606if (System_GetPropertyInt(SYSPROP_DEVICE_TYPE) == DEVICE_TYPE_TV) {607if (!KeyMap::IsKeyMapped(DEVICE_ID_PAD_0, VIRTKEY_PAUSE) || !KeyMap::IsKeyMapped(DEVICE_ID_PAD_1, VIRTKEY_PAUSE)) {608// If it's a TV (so no built-in back button), and there's no back button mapped to a pad,609// use this as the fallback way to get into the menu.610611screenManager()->push(new GamePauseScreen(gamePath_));612}613}614} else if (message == UIMessage::REQUEST_PLAY_SOUND) {615if (g_Config.bAchievementsSoundEffects && g_Config.bEnableSound) {616float achievementVolume = g_Config.iAchievementSoundVolume * 0.1f;617// TODO: Handle this some nicer way.618if (!strcmp(value, "achievement_unlocked")) {619g_BackgroundAudio.SFX().Play(UI::UISound::ACHIEVEMENT_UNLOCKED, achievementVolume * 1.0f);620}621if (!strcmp(value, "leaderboard_submitted")) {622g_BackgroundAudio.SFX().Play(UI::UISound::LEADERBOARD_SUBMITTED, achievementVolume * 1.0f);623}624}625}626}627628bool EmuScreen::UnsyncTouch(const TouchInput &touch) {629System_Notify(SystemNotification::ACTIVITY);630631if (chatMenu_ && chatMenu_->GetVisibility() == UI::V_VISIBLE) {632// Avoid pressing touch button behind the chat633if (chatMenu_->Contains(touch.x, touch.y)) {634chatMenu_->Touch(touch);635return true;636} else if ((touch.flags & TOUCH_DOWN) != 0) {637chatMenu_->Close();638if (chatButton_)639chatButton_->SetVisibility(UI::V_VISIBLE);640UI::EnableFocusMovement(false);641}642}643644GamepadTouch();645646if (root_) {647UIScreen::UnsyncTouch(touch);648}649return true;650}651652void EmuScreen::onVKey(int virtualKeyCode, bool down) {653auto sc = GetI18NCategory(I18NCat::SCREEN);654auto mc = GetI18NCategory(I18NCat::MAPPABLECONTROLS);655656switch (virtualKeyCode) {657case VIRTKEY_FASTFORWARD:658if (down) {659if (coreState == CORE_STEPPING) {660Core_EnableStepping(false);661}662PSP_CoreParameter().fastForward = true;663} else {664PSP_CoreParameter().fastForward = false;665}666break;667668case VIRTKEY_SPEED_TOGGLE:669if (down) {670// Cycle through enabled speeds.671if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL && g_Config.iFpsLimit1 >= 0) {672PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;673g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("fixed", "Speed: alternate"), 1.0, "altspeed");674} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 && g_Config.iFpsLimit2 >= 0) {675PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;676g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0, "altspeed");677} else if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1 || PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {678PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;679g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0, "altspeed");680}681}682break;683684case VIRTKEY_SPEED_CUSTOM1:685if (down) {686if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {687PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM1;688g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("fixed", "Speed: alternate"), 1.0, "altspeed");689}690} else {691if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM1) {692PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;693g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0, "altspeed");694}695}696break;697case VIRTKEY_SPEED_CUSTOM2:698if (down) {699if (PSP_CoreParameter().fpsLimit == FPSLimit::NORMAL) {700PSP_CoreParameter().fpsLimit = FPSLimit::CUSTOM2;701g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("SpeedCustom2", "Speed: alternate 2"), 1.0, "altspeed");702}703} else {704if (PSP_CoreParameter().fpsLimit == FPSLimit::CUSTOM2) {705PSP_CoreParameter().fpsLimit = FPSLimit::NORMAL;706g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("standard", "Speed: standard"), 1.0, "altspeed");707}708}709break;710711case VIRTKEY_PAUSE:712if (down) {713// Trigger on key-up to partially avoid repetition problems.714// This is needed whenever we pop up a menu since the mapper715// might miss the key-up. Same as VIRTKEY_OPENCHAT.716pauseTrigger_ = true;717controlMapper_.ForceReleaseVKey(virtualKeyCode);718}719break;720721case VIRTKEY_FRAME_ADVANCE:722// Can't do this reliably in an async fashion, so we just set a variable.723if (down) {724doFrameAdvance_.store(true);725}726break;727728case VIRTKEY_OPENCHAT:729if (down && g_Config.bEnableNetworkChat) {730UI::EventParams e{};731OnChatMenu.Trigger(e);732controlMapper_.ForceReleaseVKey(virtualKeyCode);733}734break;735736case VIRTKEY_AXIS_SWAP:737if (down) {738controlMapper_.ToggleSwapAxes();739g_OSD.Show(OSDType::MESSAGE_INFO, mc->T("AxisSwap")); // best string we have.740}741break;742743case VIRTKEY_DEVMENU:744if (down) {745UI::EventParams e{};746OnDevMenu.Trigger(e);747}748break;749750case VIRTKEY_RESET_EMULATION:751System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);752break;753754#ifndef MOBILE_DEVICE755case VIRTKEY_RECORD:756if (down) {757if (g_Config.bDumpFrames == g_Config.bDumpAudio) {758g_Config.bDumpFrames = !g_Config.bDumpFrames;759g_Config.bDumpAudio = !g_Config.bDumpAudio;760} else {761// This hotkey should always toggle both audio and video together.762// So let's make sure that's the only outcome even if video OR audio was already being dumped.763if (g_Config.bDumpFrames) {764AVIDump::Stop();765AVIDump::Start(PSP_CoreParameter().renderWidth, PSP_CoreParameter().renderHeight);766g_Config.bDumpAudio = true;767} else {768WAVDump::Reset();769g_Config.bDumpFrames = true;770}771}772}773break;774#endif775776case VIRTKEY_REWIND:777if (down && !Achievements::WarnUserIfHardcoreModeActive(false)) {778if (SaveState::CanRewind()) {779SaveState::Rewind(&AfterSaveStateAction);780} else {781g_OSD.Show(OSDType::MESSAGE_WARNING, sc->T("norewind", "No rewind save states available"), 2.0);782}783}784break;785case VIRTKEY_SAVE_STATE:786if (down && !Achievements::WarnUserIfHardcoreModeActive(true)) {787SaveState::SaveSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);788}789break;790case VIRTKEY_LOAD_STATE:791if (down && !Achievements::WarnUserIfHardcoreModeActive(false)) {792SaveState::LoadSlot(gamePath_, g_Config.iCurrentStateSlot, &AfterSaveStateAction);793}794break;795case VIRTKEY_PREVIOUS_SLOT:796if (down && !Achievements::WarnUserIfHardcoreModeActive(true)) {797SaveState::PrevSlot();798System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);799}800break;801case VIRTKEY_NEXT_SLOT:802if (down && !Achievements::WarnUserIfHardcoreModeActive(true)) {803SaveState::NextSlot();804System_PostUIMessage(UIMessage::SAVESTATE_DISPLAY_SLOT);805}806break;807case VIRTKEY_TOGGLE_FULLSCREEN:808if (down)809System_ToggleFullscreenState("");810break;811case VIRTKEY_TOGGLE_TOUCH_CONTROLS:812if (down) {813if (g_Config.bShowTouchControls) {814// This just messes with opacity if enabled, so you can touch the screen again to bring them back.815if (GamepadGetOpacity() < 0.01f) {816GamepadTouch();817} else {818// Reset.819GamepadTouch(true);820}821} else {822// If touch controls are disabled though, they'll get enabled.823g_Config.bShowTouchControls = true;824RecreateViews();825GamepadTouch();826}827}828break;829case VIRTKEY_TOGGLE_MOUSE:830if (down) {831g_Config.bMouseControl = !g_Config.bMouseControl;832}833break;834case VIRTKEY_SCREENSHOT:835if (down)836g_TakeScreenshot = true;837break;838839case VIRTKEY_TEXTURE_DUMP:840if (down) {841g_Config.bSaveNewTextures = !g_Config.bSaveNewTextures;842if (g_Config.bSaveNewTextures) {843g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_true", "Textures will now be saved to your storage"), 2.0, "savetexturechanged");844System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);845} else {846g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("saveNewTextures_false", "Texture saving was disabled"), 2.0, "savetexturechanged");847}848}849break;850case VIRTKEY_TEXTURE_REPLACE:851if (down) {852g_Config.bReplaceTextures = !g_Config.bReplaceTextures;853if (g_Config.bReplaceTextures)854g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_true", "Texture replacement enabled"), 2.0, "replacetexturechanged");855else856g_OSD.Show(OSDType::MESSAGE_INFO, sc->T("replaceTextures_false", "Textures are no longer being replaced"), 2.0, "replacetexturechanged");857System_PostUIMessage(UIMessage::GPU_CONFIG_CHANGED);858}859break;860case VIRTKEY_RAPID_FIRE:861__CtrlSetRapidFire(down, g_Config.iRapidFireInterval);862break;863case VIRTKEY_MUTE_TOGGLE:864if (down)865g_Config.bEnableSound = !g_Config.bEnableSound;866break;867case VIRTKEY_SCREEN_ROTATION_VERTICAL:868if (down)869g_Config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL;870break;871case VIRTKEY_SCREEN_ROTATION_VERTICAL180:872if (down)873g_Config.iInternalScreenRotation = ROTATION_LOCKED_VERTICAL180;874break;875case VIRTKEY_SCREEN_ROTATION_HORIZONTAL:876if (down)877g_Config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL;878break;879case VIRTKEY_SCREEN_ROTATION_HORIZONTAL180:880if (down)881g_Config.iInternalScreenRotation = ROTATION_LOCKED_HORIZONTAL180;882break;883case VIRTKEY_TOGGLE_WLAN:884if (down) {885auto n = GetI18NCategory(I18NCat::NETWORKING);886auto di = GetI18NCategory(I18NCat::DIALOG);887g_Config.bEnableWlan = !g_Config.bEnableWlan;888// Try to avoid adding more strings so we piece together a message from existing ones.889g_OSD.Show(OSDType::MESSAGE_INFO, StringFromFormat(890"%s: %s", n->T("Enable networking"), g_Config.bEnableWlan ? di->T("Enabled") : di->T("Disabled")), 2.0, "toggle_wlan");891}892break;893case VIRTKEY_EXIT_APP:894System_ExitApp();895break;896}897}898899void EmuScreen::onVKeyAnalog(int virtualKeyCode, float value) {900if (virtualKeyCode != VIRTKEY_SPEED_ANALOG) {901return;902}903904// We only handle VIRTKEY_SPEED_ANALOG here.905906// Xbox controllers need a pretty big deadzone here to not leave behind small values907// on occasion when releasing the trigger. Still feels right.908static constexpr float DEADZONE_THRESHOLD = 0.2f;909static constexpr float DEADZONE_SCALE = 1.0f / (1.0f - DEADZONE_THRESHOLD);910911FPSLimit &limitMode = PSP_CoreParameter().fpsLimit;912// If we're using an alternate speed already, let that win.913if (limitMode != FPSLimit::NORMAL && limitMode != FPSLimit::ANALOG)914return;915// Don't even try if the limit is invalid.916if (g_Config.iAnalogFpsLimit <= 0)917return;918919// Apply a small deadzone (against the resting position.)920value = std::max(0.0f, (value - DEADZONE_THRESHOLD) * DEADZONE_SCALE);921922// If target is above 60, value is how much to speed up over 60. Otherwise, it's how much slower.923// So normalize the target.924int target = g_Config.iAnalogFpsLimit - 60;925PSP_CoreParameter().analogFpsLimit = 60 + (int)(target * value);926927// If we've reset back to normal, turn it off.928limitMode = PSP_CoreParameter().analogFpsLimit == 60 ? FPSLimit::NORMAL : FPSLimit::ANALOG;929}930931bool EmuScreen::UnsyncKey(const KeyInput &key) {932System_Notify(SystemNotification::ACTIVITY);933934if (UI::IsFocusMovementEnabled()) {935return UIScreen::UnsyncKey(key);936}937return controlMapper_.Key(key, &pauseTrigger_);938}939940bool EmuScreen::key(const KeyInput &key) {941bool retval = UIScreen::key(key);942943if (!retval && (key.flags & KEY_DOWN) != 0 && UI::IsEscapeKey(key)) {944if (chatMenu_)945chatMenu_->Close();946if (chatButton_)947chatButton_->SetVisibility(UI::V_VISIBLE);948UI::EnableFocusMovement(false);949return true;950}951952return retval;953}954955void EmuScreen::UnsyncAxis(const AxisInput *axes, size_t count) {956System_Notify(SystemNotification::ACTIVITY);957958if (UI::IsFocusMovementEnabled()) {959return UIScreen::UnsyncAxis(axes, count);960}961962return controlMapper_.Axis(axes, count);963}964965class GameInfoBGView : public UI::InertView {966public:967GameInfoBGView(const Path &gamePath, UI::LayoutParams *layoutParams) : InertView(layoutParams), gamePath_(gamePath) {968}969970void Draw(UIContext &dc) override {971// Should only be called when visible.972std::shared_ptr<GameInfo> ginfo = g_gameInfoCache->GetInfo(dc.GetDrawContext(), gamePath_, GameInfoFlags::BG);973dc.Flush();974975// PIC1 is the loading image, so let's only draw if it's available.976if (ginfo->Ready(GameInfoFlags::BG) && ginfo->pic1.texture) {977Draw::Texture *texture = ginfo->pic1.texture;978if (texture) {979dc.GetDrawContext()->BindTexture(0, texture);980981double loadTime = ginfo->pic1.timeLoaded;982uint32_t color = alphaMul(color_, ease((time_now_d() - loadTime) * 3));983dc.Draw()->DrawTexRect(dc.GetBounds(), 0, 0, 1, 1, color);984dc.Flush();985dc.RebindTexture();986}987}988}989990std::string DescribeText() const override {991return "";992}993994void SetColor(uint32_t c) {995color_ = c;996}997998protected:999Path gamePath_;1000uint32_t color_ = 0xFFC0C0C0;1001};10021003// TODO: Shouldn't actually need bounds for this, Anchor can center too.1004static UI::AnchorLayoutParams *AnchorInCorner(const Bounds &bounds, int corner, float xOffset, float yOffset) {1005using namespace UI;1006switch ((ScreenEdgePosition)g_Config.iChatButtonPosition) {1007case ScreenEdgePosition::BOTTOM_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, NONE, NONE, yOffset, true);1008case ScreenEdgePosition::BOTTOM_CENTER: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), NONE, NONE, yOffset, true);1009case ScreenEdgePosition::BOTTOM_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, NONE, xOffset, yOffset, true);1010case ScreenEdgePosition::TOP_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, yOffset, NONE, NONE, true);1011case ScreenEdgePosition::TOP_CENTER: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, bounds.centerX(), yOffset, NONE, NONE, true);1012case ScreenEdgePosition::TOP_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, yOffset, xOffset, NONE, true);1013case ScreenEdgePosition::CENTER_LEFT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, bounds.centerY(), NONE, NONE, true);1014case ScreenEdgePosition::CENTER_RIGHT: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, NONE, bounds.centerY(), xOffset, NONE, true);1015default: return new AnchorLayoutParams(WRAP_CONTENT, WRAP_CONTENT, xOffset, NONE, NONE, yOffset, true);1016}1017}10181019void EmuScreen::CreateViews() {1020using namespace UI;10211022auto di = GetI18NCategory(I18NCat::DIALOG);1023auto dev = GetI18NCategory(I18NCat::DEVELOPER);1024auto sc = GetI18NCategory(I18NCat::SCREEN);10251026const Bounds &bounds = screenManager()->getUIContext()->GetLayoutBounds();1027InitPadLayout(bounds.w, bounds.h);10281029// Devices without a back button like iOS need an on-screen touch back button.1030bool showPauseButton = !System_GetPropertyBool(SYSPROP_HAS_BACK_BUTTON) || g_Config.bShowTouchPause;10311032root_ = CreatePadLayout(bounds.w, bounds.h, &pauseTrigger_, showPauseButton, &controlMapper_);1033if (g_Config.bShowDeveloperMenu) {1034root_->Add(new Button(dev->T("DevMenu")))->OnClick.Handle(this, &EmuScreen::OnDevTools);1035}10361037LinearLayout *buttons = new LinearLayout(Orientation::ORIENT_HORIZONTAL, new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 60, true));1038buttons->SetSpacing(20.0f);1039root_->Add(buttons);10401041resumeButton_ = buttons->Add(new Button(dev->T("Resume")));1042resumeButton_->OnClick.Handle(this, &EmuScreen::OnResume);1043resumeButton_->SetVisibility(V_GONE);10441045resetButton_ = buttons->Add(new Button(dev->T("Reset")));1046resetButton_->OnClick.Add([](UI::EventParams &) {1047if (coreState == CoreState::CORE_RUNTIME_ERROR) {1048System_PostUIMessage(UIMessage::REQUEST_GAME_RESET);1049}1050return UI::EVENT_DONE;1051});1052resetButton_->SetVisibility(V_GONE);10531054backButton_ = buttons->Add(new Button(dev->T("Back")));1055backButton_->OnClick.Add([this](UI::EventParams &) {1056this->pauseTrigger_ = true;1057return UI::EVENT_DONE;1058});1059backButton_->SetVisibility(V_GONE);10601061cardboardDisableButton_ = root_->Add(new Button(sc->T("Cardboard VR OFF"), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 30, true)));1062cardboardDisableButton_->OnClick.Handle(this, &EmuScreen::OnDisableCardboard);1063cardboardDisableButton_->SetVisibility(V_GONE);1064cardboardDisableButton_->SetScale(0.65f); // make it smaller - this button can be in the way otherwise.10651066if (g_Config.bEnableNetworkChat) {1067if (g_Config.iChatButtonPosition != 8) {1068auto n = GetI18NCategory(I18NCat::NETWORKING);1069AnchorLayoutParams *layoutParams = AnchorInCorner(bounds, g_Config.iChatButtonPosition, 80.0f, 50.0f);1070ChoiceWithValueDisplay *btn = new ChoiceWithValueDisplay(&newChatMessages_, n->T("Chat"), layoutParams);1071root_->Add(btn)->OnClick.Handle(this, &EmuScreen::OnChat);1072chatButton_ = btn;1073}1074chatMenu_ = root_->Add(new ChatMenu(GetRequesterToken(), screenManager()->getUIContext()->GetBounds(), screenManager(), new LayoutParams(FILL_PARENT, FILL_PARENT)));1075chatMenu_->SetVisibility(UI::V_GONE);1076} else {1077chatButton_ = nullptr;1078chatMenu_ = nullptr;1079}10801081saveStatePreview_ = new AsyncImageFileView(Path(), IS_FIXED, new AnchorLayoutParams(bounds.centerX(), 100, NONE, NONE, true));1082saveStatePreview_->SetFixedSize(160, 90);1083saveStatePreview_->SetColor(0x90FFFFFF);1084saveStatePreview_->SetVisibility(V_GONE);1085saveStatePreview_->SetCanBeFocused(false);1086root_->Add(saveStatePreview_);10871088GameInfoBGView *loadingBG = root_->Add(new GameInfoBGView(gamePath_, new AnchorLayoutParams(FILL_PARENT, FILL_PARENT)));1089TextView *loadingTextView = root_->Add(new TextView(sc->T(PSP_GetLoading()), new AnchorLayoutParams(bounds.centerX(), NONE, NONE, 40, true)));1090loadingTextView_ = loadingTextView;10911092static const ImageID symbols[4] = {1093ImageID("I_CROSS"),1094ImageID("I_CIRCLE"),1095ImageID("I_SQUARE"),1096ImageID("I_TRIANGLE"),1097};10981099Spinner *loadingSpinner = root_->Add(new Spinner(symbols, ARRAY_SIZE(symbols), new AnchorLayoutParams(NONE, NONE, 45, 45, true)));1100loadingSpinner_ = loadingSpinner;11011102loadingBG->SetTag("LoadingBG");1103loadingTextView->SetTag("LoadingText");1104loadingSpinner->SetTag("LoadingSpinner");11051106// Don't really need this, and it creates a lot of strings to translate...1107loadingTextView->SetVisibility(V_GONE);1108loadingTextView->SetShadow(true);11091110loadingViewColor_ = loadingSpinner->AddTween(new CallbackColorTween(0x00FFFFFF, 0x00FFFFFF, 0.2f, &bezierEaseInOut));1111loadingViewColor_->SetCallback([loadingBG, loadingTextView, loadingSpinner](View *v, uint32_t c) {1112loadingBG->SetColor(c & 0xFFC0C0C0);1113loadingTextView->SetTextColor(c);1114loadingSpinner->SetColor(alphaMul(c, 0.7f));1115});1116loadingViewColor_->Persist();11171118// We start invisible here, in case of recreated views.1119loadingViewVisible_ = loadingSpinner->AddTween(new VisibilityTween(UI::V_INVISIBLE, UI::V_INVISIBLE, 0.2f, &bezierEaseInOut));1120loadingViewVisible_->Persist();1121loadingViewVisible_->Finish.Add([loadingBG, loadingSpinner](EventParams &p) {1122loadingBG->SetVisibility(p.v->GetVisibility());11231124// If we just became invisible, flush BGs since we don't need them anymore.1125// Saves some VRAM for the game, but don't do it before we fade out...1126if (p.v->GetVisibility() == V_INVISIBLE) {1127g_gameInfoCache->FlushBGs();1128// And we can go away too. This means the tween will never run again.1129loadingBG->SetVisibility(V_GONE);1130loadingSpinner->SetVisibility(V_GONE);1131}1132return EVENT_DONE;1133});1134// Will become visible along with the loadingView.1135loadingBG->SetVisibility(V_INVISIBLE);1136}11371138UI::EventReturn EmuScreen::OnDevTools(UI::EventParams ¶ms) {1139DevMenuScreen *devMenu = new DevMenuScreen(gamePath_, I18NCat::DEVELOPER);1140if (params.v)1141devMenu->SetPopupOrigin(params.v);1142screenManager()->push(devMenu);1143return UI::EVENT_DONE;1144}11451146UI::EventReturn EmuScreen::OnDisableCardboard(UI::EventParams ¶ms) {1147g_Config.bEnableCardboardVR = false;1148return UI::EVENT_DONE;1149}11501151UI::EventReturn EmuScreen::OnChat(UI::EventParams ¶ms) {1152if (chatButton_ != nullptr && chatButton_->GetVisibility() == UI::V_VISIBLE) {1153chatButton_->SetVisibility(UI::V_GONE);1154}1155if (chatMenu_ != nullptr) {1156chatMenu_->SetVisibility(UI::V_VISIBLE);11571158#if PPSSPP_PLATFORM(WINDOWS) || defined(USING_QT_UI) || defined(SDL)1159UI::EnableFocusMovement(true);1160root_->SetDefaultFocusView(chatMenu_);11611162chatMenu_->SetFocus();1163UI::View *focused = UI::GetFocusedView();1164if (focused) {1165root_->SubviewFocused(focused);1166}1167#endif1168}1169return UI::EVENT_DONE;1170}11711172UI::EventReturn EmuScreen::OnResume(UI::EventParams ¶ms) {1173if (coreState == CoreState::CORE_RUNTIME_ERROR) {1174// Force it!1175Memory::MemFault_IgnoreLastCrash();1176coreState = CoreState::CORE_RUNNING;1177}1178return UI::EVENT_DONE;1179}11801181void EmuScreen::update() {1182using namespace UI;11831184UIScreen::update();1185resumeButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR && Memory::MemFault_MayBeResumable() ? V_VISIBLE : V_GONE);1186resetButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);1187backButton_->SetVisibility(coreState == CoreState::CORE_RUNTIME_ERROR ? V_VISIBLE : V_GONE);11881189if (chatButton_ && chatMenu_) {1190if (chatMenu_->GetVisibility() != V_GONE) {1191chatMessages_ = GetChatMessageCount();1192newChatMessages_ = 0;1193} else {1194int diff = GetChatMessageCount() - chatMessages_;1195// Cap the count at 50.1196newChatMessages_ = diff > 50 ? 50 : diff;1197}1198}11991200if (bootPending_) {1201// Keep trying the boot until bootPending_ is lifted.1202// It may be delayed due to RetroAchievements or any other cause.1203bootGame(gamePath_);1204}12051206// Simply forcibly update to the current screen size every frame. Doesn't cost much.1207// If bounds is set to be smaller than the actual pixel resolution of the display, respect that.1208// TODO: Should be able to use g_dpi_scale here instead. Might want to store the dpi scale in the UI context too.12091210#ifndef _WIN321211const Bounds &bounds = screenManager()->getUIContext()->GetBounds();1212PSP_CoreParameter().pixelWidth = g_display.pixel_xres * bounds.w / g_display.dp_xres;1213PSP_CoreParameter().pixelHeight = g_display.pixel_yres * bounds.h / g_display.dp_yres;1214#endif12151216if (!invalid_) {1217UpdateUIState(coreState != CORE_RUNTIME_ERROR ? UISTATE_INGAME : UISTATE_EXCEPTION);1218}12191220if (errorMessage_.size()) {1221auto err = GetI18NCategory(I18NCat::ERRORS);1222std::string errLoadingFile = gamePath_.ToVisualString() + "\n";1223errLoadingFile.append(err->T("Error loading file", "Could not load game"));1224errLoadingFile.append(" ");1225errLoadingFile.append(err->T(errorMessage_.c_str()));12261227screenManager()->push(new PromptScreen(gamePath_, errLoadingFile, "OK", ""));1228errorMessage_.clear();1229quit_ = true;1230return;1231}12321233if (pauseTrigger_) {1234pauseTrigger_ = false;1235screenManager()->push(new GamePauseScreen(gamePath_));1236}12371238if (invalid_)1239return;12401241double now = time_now_d();12421243controlMapper_.Update(now);12441245if (saveStatePreview_ && !bootPending_) {1246int currentSlot = SaveState::GetCurrentSlot();1247if (saveStateSlot_ != currentSlot) {1248saveStateSlot_ = currentSlot;12491250Path fn;1251if (SaveState::HasSaveInSlot(gamePath_, currentSlot)) {1252fn = SaveState::GenerateSaveSlotFilename(gamePath_, currentSlot, SaveState::SCREENSHOT_EXTENSION);1253}12541255saveStatePreview_->SetFilename(fn);1256if (!fn.empty()) {1257saveStatePreview_->SetVisibility(UI::V_VISIBLE);1258saveStatePreviewShownTime_ = now;1259} else {1260saveStatePreview_->SetVisibility(UI::V_GONE);1261}1262}12631264if (saveStatePreview_->GetVisibility() == UI::V_VISIBLE) {1265double endTime = saveStatePreviewShownTime_ + 2.0;1266float alpha = clamp_value((endTime - now) * 4.0, 0.0, 1.0);1267saveStatePreview_->SetColor(colorAlpha(0x00FFFFFF, alpha));12681269if (now - saveStatePreviewShownTime_ > 2) {1270saveStatePreview_->SetVisibility(UI::V_GONE);1271}1272}1273}1274}12751276bool EmuScreen::checkPowerDown() {1277if (PSP_IsRebooting()) {1278bootPending_ = true;1279invalid_ = true;1280}12811282if (coreState == CORE_POWERDOWN && !PSP_IsIniting() && !PSP_IsRebooting()) {1283if (PSP_IsInited()) {1284PSP_Shutdown();1285}1286INFO_LOG(Log::System, "SELF-POWERDOWN!");1287screenManager()->switchScreen(new MainScreen());1288bootPending_ = false;1289invalid_ = true;1290return true;1291}1292return false;1293}12941295ScreenRenderRole EmuScreen::renderRole(bool isTop) const {1296auto CanBeBackground = [&]() -> bool {1297if (g_Config.bSkipBufferEffects) {1298return isTop || (g_Config.bTransparentBackground && Core_ShouldRunBehind());1299}13001301if (!g_Config.bTransparentBackground && !isTop) {1302if (Core_ShouldRunBehind() || screenManager()->topScreen()->wantBrightBackground())1303return true;1304return false;1305}13061307if (invalid_) {1308return false;1309}13101311return true;1312};13131314ScreenRenderRole role = ScreenRenderRole::MUST_BE_FIRST;1315if (CanBeBackground()) {1316role |= ScreenRenderRole::CAN_BE_BACKGROUND;1317}1318return role;1319}13201321void EmuScreen::darken() {1322if (!screenManager()->topScreen()->wantBrightBackground()) {1323UIContext &dc = *screenManager()->getUIContext();1324uint32_t color = GetBackgroundColorWithAlpha(dc);1325dc.Begin();1326dc.RebindTexture();1327dc.FillRect(UI::Drawable(color), dc.GetBounds());1328dc.Flush();1329}1330}13311332ScreenRenderFlags EmuScreen::render(ScreenRenderMode mode) {1333ScreenRenderFlags flags = ScreenRenderFlags::NONE;1334Draw::Viewport viewport{ 0.0f, 0.0f, (float)g_display.pixel_xres, (float)g_display.pixel_yres, 0.0f, 1.0f };1335using namespace Draw;13361337DrawContext *draw = screenManager()->getDrawContext();1338if (!draw) {1339return flags; // shouldn't really happen but I've seen a suspicious stack trace..1340}13411342GamepadUpdateOpacity();13431344bool skipBufferEffects = g_Config.bSkipBufferEffects;13451346bool framebufferBound = false;13471348if (mode & ScreenRenderMode::FIRST) {1349// Actually, always gonna be first when it exists (?)13501351// Here we do NOT bind the backbuffer or clear the screen, unless non-buffered.1352// The emuscreen is different than the others - we really want to allow the game to render to framebuffers1353// before we ever bind the backbuffer for rendering. On mobile GPUs, switching back and forth between render1354// targets is a mortal sin so it's very important that we don't bind the backbuffer unnecessarily here.1355// We only bind it in FramebufferManager::CopyDisplayToOutput (unless non-buffered)...1356// We do, however, start the frame in other ways.13571358if (skipBufferEffects && !g_Config.bSoftwareRendering) {1359// We need to clear here already so that drawing during the frame is done on a clean slate.1360if (Core_IsStepping() && gpuStats.numFlips != 0) {1361draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::KEEP, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_BackBuffer");1362} else {1363draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, 0xFF000000 }, "EmuScreen_BackBuffer");1364}13651366draw->SetViewport(viewport);1367draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);1368skipBufferEffects = true;1369framebufferBound = true;1370}1371draw->SetTargetSize(g_display.pixel_xres, g_display.pixel_yres);1372}13731374g_OSD.NudgeSidebar();13751376if (mode & ScreenRenderMode::TOP) {1377System_Notify(SystemNotification::KEEP_SCREEN_AWAKE);1378} else if (!Core_ShouldRunBehind() && strcmp(screenManager()->topScreen()->tag(), "DevMenu") != 0) {1379// Just to make sure.1380if (PSP_IsInited() && !skipBufferEffects) {1381_dbg_assert_(gpu);1382PSP_BeginHostFrame();1383gpu->CopyDisplayToOutput(true);1384PSP_EndHostFrame();1385}1386if (!framebufferBound && (!gpu || !gpu->PresentedThisFrame())) {1387draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, }, "EmuScreen_Behind");1388}1389// Need to make sure the UI texture is available, for "darken".1390screenManager()->getUIContext()->BeginFrame();1391draw->SetViewport(viewport);1392draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);1393darken();1394return flags;1395}13961397if (invalid_) {1398// Loading, or after shutdown?1399if (loadingTextView_ && loadingTextView_->GetVisibility() == UI::V_VISIBLE)1400loadingTextView_->SetText(PSP_GetLoading());14011402// It's possible this might be set outside PSP_RunLoopFor().1403// In this case, we need to double check it here.1404if (mode & ScreenRenderMode::TOP) {1405checkPowerDown();1406}1407draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR }, "EmuScreen_Invalid");1408// Need to make sure the UI texture is available, for "darken".1409screenManager()->getUIContext()->BeginFrame();1410draw->SetViewport(viewport);1411draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);1412renderUI();1413return flags;1414}14151416// Freeze-frame functionality (loads a savestate on every frame).1417if (PSP_CoreParameter().freezeNext) {1418PSP_CoreParameter().frozen = true;1419PSP_CoreParameter().freezeNext = false;1420SaveState::SaveToRam(freezeState_);1421} else if (PSP_CoreParameter().frozen) {1422std::string errorString;1423if (CChunkFileReader::ERROR_NONE != SaveState::LoadFromRam(freezeState_, &errorString)) {1424ERROR_LOG(Log::SaveState, "Failed to load freeze state (%s). Unfreezing.", errorString.c_str());1425PSP_CoreParameter().frozen = false;1426}1427}14281429Core_UpdateDebugStats((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::DEBUG_STATS || g_Config.bLogFrameDrops);14301431if (doFrameAdvance_.exchange(false)) {1432if (!Achievements::WarnUserIfHardcoreModeActive(false)) {1433// If game is running, pause emulation immediately. Otherwise, advance a single frame.1434if (Core_IsStepping()) {1435frameStep_ = true;1436Core_EnableStepping(false);1437} else if (!frameStep_) {1438lastNumFlips = gpuStats.numFlips;1439Core_EnableStepping(true, "ui.frameAdvance", 0);1440}1441}1442}14431444bool blockedExecution = Achievements::IsBlockingExecution();1445uint32_t clearColor = 0;1446if (!blockedExecution) {1447PSP_BeginHostFrame();1448PSP_RunLoopWhileState();14491450flags |= ScreenRenderFlags::HANDLED_THROTTLING;14511452// Hopefully coreState is now CORE_NEXTFRAME1453switch (coreState) {1454case CORE_NEXTFRAME:1455// Reached the end of the frame, all good. Set back to running for the next frame1456coreState = CORE_RUNNING;1457break;1458case CORE_STEPPING:1459case CORE_RUNTIME_ERROR:1460{1461// If there's an exception, display information.1462const MIPSExceptionInfo &info = Core_GetExceptionInfo();1463if (info.type != MIPSExceptionType::NONE) {1464// Clear to blue background screen1465bool dangerousSettings = !Reporting::IsSupported();1466clearColor = dangerousSettings ? 0xFF900050 : 0xFF900000;1467draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_RuntimeError");1468framebufferBound = true;1469// The info is drawn later in renderUI1470} else {1471// If we're stepping, it's convenient not to clear the screen entirely, so we copy display to output.1472// This won't work in non-buffered, but that's fine.1473if (!framebufferBound && PSP_IsInited()) {1474// draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_Stepping");1475gpu->CopyDisplayToOutput(true);1476framebufferBound = true;1477}1478}1479break;1480}1481default:1482// Didn't actually reach the end of the frame, ran out of the blockTicks cycles.1483// In this case we need to bind and wipe the backbuffer, at least.1484// It's possible we never ended up outputted anything - make sure we have the backbuffer cleared1485// So, we don't set framebufferBound here.1486break;1487}14881489if (framebufferBound && gpu) {1490gpu->PresentedThisFrame();1491}14921493PSP_EndHostFrame();14941495// This place rougly matches how libretro handles it (after retro_frame).1496Achievements::FrameUpdate();1497}14981499if (gpu && gpu->PresentedThisFrame()) {1500framebufferBound = true;1501}15021503if (!framebufferBound) {1504draw->BindFramebufferAsRenderTarget(nullptr, { RPAction::CLEAR, RPAction::CLEAR, RPAction::CLEAR, clearColor }, "EmuScreen_NoFrame");1505draw->SetViewport(viewport);1506draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);1507}15081509Draw::BackendState state = draw->GetCurrentBackendState();15101511// We allow if !state.valid, that means it's not the Vulkan backend.1512_assert_msg_(!state.valid || state.passes >= 1, "skipB: %d sw: %d", (int)skipBufferEffects, (int)g_Config.bSoftwareRendering);15131514screenManager()->getUIContext()->BeginFrame();15151516if (!(mode & ScreenRenderMode::TOP)) {1517// We're in run-behind mode, but we don't want to draw chat, debug UI and stuff.1518// So, darken and bail here.1519// Reset viewport/scissor to be sure.1520draw->SetViewport(viewport);1521draw->SetScissorRect(0, 0, g_display.pixel_xres, g_display.pixel_yres);1522darken();1523return flags;1524}15251526// NOTE: We don't check for powerdown if we're not the top screen.1527checkPowerDown();15281529if (hasVisibleUI()) {1530draw->SetViewport(viewport);1531cardboardDisableButton_->SetVisibility(g_Config.bEnableCardboardVR ? UI::V_VISIBLE : UI::V_GONE);1532screenManager()->getUIContext()->BeginFrame();1533renderUI();1534}15351536if (chatMenu_ && (chatMenu_->GetVisibility() == UI::V_VISIBLE)) {1537SetVRAppMode(VRAppMode::VR_DIALOG_MODE);1538} else {1539SetVRAppMode(screenManager()->topScreen() == this ? VRAppMode::VR_GAME_MODE : VRAppMode::VR_DIALOG_MODE);1540}15411542if (!(mode & ScreenRenderMode::TOP)) {1543darken();1544}1545return flags;1546}15471548bool EmuScreen::hasVisibleUI() {1549// Regular but uncommon UI.1550if (saveStatePreview_->GetVisibility() != UI::V_GONE || loadingSpinner_->GetVisibility() == UI::V_VISIBLE)1551return true;1552if (!g_OSD.IsEmpty() || g_Config.bShowTouchControls || g_Config.iShowStatusFlags != 0)1553return true;1554if (g_Config.bEnableCardboardVR || g_Config.bEnableNetworkChat)1555return true;1556if (g_Config.bShowGPOLEDs)1557return true;1558// Debug UI.1559if ((DebugOverlay)g_Config.iDebugOverlay != DebugOverlay::OFF || g_Config.bShowDeveloperMenu)1560return true;15611562// Exception information.1563if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) {1564return true;1565}15661567return false;1568}15691570void EmuScreen::renderUI() {1571using namespace Draw;15721573DrawContext *thin3d = screenManager()->getDrawContext();1574UIContext *ctx = screenManager()->getUIContext();1575ctx->BeginFrame();1576// This sets up some important states but not the viewport.1577ctx->Begin();15781579Viewport viewport;1580viewport.TopLeftX = 0;1581viewport.TopLeftY = 0;1582viewport.Width = g_display.pixel_xres;1583viewport.Height = g_display.pixel_yres;1584viewport.MaxDepth = 1.0;1585viewport.MinDepth = 0.0;1586thin3d->SetViewport(viewport);15871588if (root_) {1589UI::LayoutViewHierarchy(*ctx, root_, false);1590root_->Draw(*ctx);1591}15921593if (!invalid_) {1594if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::CONTROL) {1595DrawControlMapperOverlay(ctx, ctx->GetLayoutBounds(), controlMapper_);1596}1597if (g_Config.iShowStatusFlags) {1598DrawFPS(ctx, ctx->GetLayoutBounds());1599}1600}16011602#ifdef USE_PROFILER1603if ((DebugOverlay)g_Config.iDebugOverlay == DebugOverlay::FRAME_PROFILE && !invalid_) {1604DrawProfile(*ctx);1605}1606#endif16071608if (g_Config.bShowGPOLEDs) {1609// Draw a vertical strip of LEDs at the right side of the screen.1610const float ledSize = 24.0f;1611const float spacing = 4.0f;1612const float height = 8 * ledSize + 7 * spacing;1613const float x = ctx->GetBounds().w - spacing - ledSize;1614const float y = (ctx->GetBounds().h - height) * 0.5f;1615ctx->FillRect(UI::Drawable(0xFF000000), Bounds(x - spacing, y - spacing, ledSize + spacing * 2, height + spacing * 2));1616for (int i = 0; i < 8; i++) {1617int bit = (g_GPOBits >> i) & 1;1618uint32_t color = 0xFF30FF30;1619if (!bit) {1620color = darkenColor(darkenColor(color));1621}1622Bounds ledBounds(x, y + (spacing + ledSize) * i, ledSize, ledSize);1623ctx->FillRect(UI::Drawable(color), ledBounds);1624}1625ctx->Flush();1626}16271628if (coreState == CORE_RUNTIME_ERROR || coreState == CORE_STEPPING) {1629const MIPSExceptionInfo &info = Core_GetExceptionInfo();1630if (info.type != MIPSExceptionType::NONE) {1631DrawCrashDump(ctx, gamePath_);1632} else {1633// We're somehow in ERROR or STEPPING without a crash dump. This case is what lead1634// to the bare "Resume" and "Reset" buttons without a crash dump before, in cases1635// where we were unable to ignore memory errors.1636}1637}16381639ctx->Flush();1640}16411642void EmuScreen::autoLoad() {1643int autoSlot = -1;16441645//check if save state has save, if so, load1646switch (g_Config.iAutoLoadSaveState) {1647case (int)AutoLoadSaveState::OFF: // "AutoLoad Off"1648return;1649case (int)AutoLoadSaveState::OLDEST: // "Oldest Save"1650autoSlot = SaveState::GetOldestSlot(gamePath_);1651break;1652case (int)AutoLoadSaveState::NEWEST: // "Newest Save"1653autoSlot = SaveState::GetNewestSlot(gamePath_);1654break;1655default: // try the specific save state slot specified1656autoSlot = (SaveState::HasSaveInSlot(gamePath_, g_Config.iAutoLoadSaveState - 3)) ? (g_Config.iAutoLoadSaveState - 3) : -1;1657break;1658}16591660if (g_Config.iAutoLoadSaveState && autoSlot != -1) {1661SaveState::LoadSlot(gamePath_, autoSlot, &AfterSaveStateAction);1662g_Config.iCurrentStateSlot = autoSlot;1663}1664}16651666void EmuScreen::resized() {1667RecreateViews();1668}166916701671