Files
ppsspp/UI/DebugOverlay.cpp
T
LunaMooandClaude Opus 5.5 90cea302b9 HOME button quit screen, with the firmware's art when available
As on a PSP: pressing HOME in a game shows "Do you want to quit the game?"
over it. The game keeps running but gets no input while it's up (sceCtrl
samples are blanked). Left/right picks Yes or No, the confirm button (our
Confirmation button setting, or what compat forces) chooses, the other face
button or HOME closes it, and Yes/No can be tapped. The volume keys change
the game volume a step at a time, shown on the volume bar.

Yes runs the game's registered exit callback first, like the PSP does,
showing "Please wait..." meanwhile. Then, if the XMB launched the game, the
XMB is booted again; otherwise it's back to the game browser, like Exit to
menu. If the callback hasn't ended the game in 10 s, it's quit anyway. Not
in the XMB itself, the PS1 emulator (its own HOME menu) or GE dumps.

It lives in UI/ImposeHome.cpp; EmuScreen only feeds it input and frames and
draws it over the PSP display.

With a firmware dump installed, it's drawn with the firmware's own art from
flash0:/kd/resource/impose.rsc, which is what impose.prx draws it from:
prerendered text for each system language (the question, Yes/No normal and
highlighted, Enter/Back, Please wait...), the button icons, the lines, the
volume bar, the battery and the clock glyphs. The format is documented in
ImposeHome.cpp. Without one, the same screen is drawn with our font and
simple shapes at the same positions.

Where impose.rsc doesn't say where things go, the layout follows
impose_02g.prx (6.61), decrypted and disassembled:
- The screen is filled with 0x9CE7 (#393939); the images are opaque.
- Lines at (80, 96) and (80, 175); impose.prx grows them from the middle
  when it opens, which isn't done here.
- Volume bar: the speaker at (28, 223), 30 steps from x 62, 12 apart. It
  shows our game volume in the PSP's 30 steps.
- Button guide: the cross icon always left, the circle right, "Enter" and
  "Back" next to whichever confirms and cancels.
- Status bar: the battery at (450, 5) (60% and up full, 30% two bars, 12%
  one, then empty), the clock ending next to it at y 5: "M/D H:MM", or
  "D/M" for the DD/MM/YYYY format, no leading zero on the month, day or
  hour, AM/PM for the 12-hour clock.

The clock is the emulated PSP's local time (__RtcGetCurrentLocalTime, new),
in the system's date and time formats. The battery is this device's, since
the emulated PSP's is always full; a device without one shows it full.
For that, Windows now reports -1 for SYSPROP_BATTERY_PERCENTAGE without a
battery (was 100), as SDL already did; SDL polls the battery even when the
status overlay doesn't show it, and the overlay leaves it out when there's
none.

Core side: __CtrlSetGameInputBlocked; __KernelCanExitToVSH and
__KernelRequestExitToVSH to quit to the XMB from outside a syscall; and
__KernelInvokeRegisteredExitCallback(toVSH), so a callback that returns
without exiting ends in the XMB rather than powering down.

New strings: "Do you want to quit the game?", "Please wait..." (Dialog).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NB244iFpvm14JH2FbwHXeQ
2026-10-26 11:39:48 +01:00

576 lines
19 KiB
C++

#include "Common/Render/DrawBuffer.h"
#include "Common/GPU/thin3d.h"
#include "Common/System/System.h"
#include "Common/Data/Text/I18n.h"
#include "Common/CPUDetect.h"
#include "Common/StringUtils.h"
#include "Common/Data/Text/StringWriter.h"
#include "Core/MIPS/MIPS.h"
#include "Core/MIPS/JitCommon/JitCommon.h"
#include "Core/HW/Display.h"
#include "Core/FrameTiming.h"
#include "Core/HLE/sceSas.h"
#include "Core/HLE/sceKernel.h"
#include "Core/HLE/scePower.h"
#include "Core/HLE/Plugins.h"
#include "Core/ControlMapper.h"
#include "Core/Config.h"
#include "Core/MemFault.h"
#include "Core/Reporting.h"
#include "Core/CwCheat.h"
#include "Core/Core.h"
#include "Core/ELF/ParamSFO.h"
#include "Core/System.h"
#include "Core/Util/GameDB.h"
#include "GPU/GPU.h"
#include "GPU/GPUCommon.h"
// TODO: This should be moved here or to Common, doesn't belong in /GPU
#include "GPU/Vulkan/DebugVisVulkan.h"
#include "GPU/Common/FramebufferManagerCommon.h"
#include "UI/DevScreens.h"
#include "UI/DebugOverlay.h"
// For std::max
#include <algorithm>
static void DrawDebugStats(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
float left = std::max(bounds.w / 2 - 20.0f, 550.0f);
float right = bounds.w - left - 20.0f;
char statbuf[4096];
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(.7f, .7f);
StringWriter w(statbuf, sizeof(statbuf));
w.F("Kernel processing time: %0.2f ms\n"
"Slowest syscall: %s : %0.2f ms\n"
"Most active syscall: %s : %0.2f ms\n",
kernelStats.msInSyscalls * 1000.0f,
kernelStats.slowestSyscallName ? kernelStats.slowestSyscallName : "(none)",
kernelStats.slowestSyscallTime * 1000.0f,
kernelStats.summedSlowestSyscallName ? kernelStats.summedSlowestSyscallName : "(none)",
kernelStats.summedSlowestSyscallTime * 1000.0f);
__DisplayGetDebugStats(w);
ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 11, bounds.y + 31, left, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 10, bounds.y + 30, left, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}
static void DrawJitStats(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
char statbuf[1024];
StringWriter w(statbuf, sizeof(statbuf));
if (mipsr4k.jit) {
MIPSComp::JitStats stats;
mipsr4k.jit->GetStats(&stats);
const double irPercent = stats.irArenaSize ? 100.0 * stats.irArenaUsed / stats.irArenaSize : 0.0;
w.F("Blocks: %d (%d valid)\n", stats.numBlocks, stats.numValidBlocks);
if (stats.codeSize) {
w.F("Native code: %0.2f / %0.2f MB (%0.1f%%)\n", stats.codeUsed / (1024.0 * 1024.0), stats.codeSize / (1024.0 * 1024.0),
100.0 * stats.codeUsed / stats.codeSize);
w.F("Cleared because full: %d\n", stats.codeFullClears);
} else {
w.F("IR arena: %d / %d instructions (%0.1f%%)\n", (int)stats.irArenaUsed, (int)stats.irArenaSize, irPercent);
w.F("Cleared because full: %d\n", stats.irArenaFullClears);
}
w.F("Invalidations: %d (%d blocks)\n", stats.invalidations, stats.invalidatedBlocks);
} else {
w.W("No JIT (interpreter)\n");
}
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(.7f, .7f);
ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}
static void DrawAudioDebugStats(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
char statbuf[4096];
System_AudioGetDebugStats(statbuf, sizeof(statbuf));
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.5f, 0.5f);
ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
float left = std::max(bounds.w / 2 - 20.0f, 500.0f);
__SasGetDebugStats(statbuf, sizeof(statbuf));
ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + left + 21, bounds.y + 31, bounds.w - left, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawTextRect(ubuntu24, statbuf, bounds.x + left + 20, bounds.y + 30, bounds.w - left, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}
static void DrawControlDebug(UIContext *ctx, const ControlMapper &mapper, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
char statbuf[2048];
StringWriter w(statbuf, sizeof(statbuf));
mapper.GetDebugString(w);
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.5f, 0.5f);
ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 11, bounds.y + 31, bounds.w - 20, bounds.h - 30, 0xc0000000, FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawTextRect(ubuntu24, w.as_view(), bounds.x + 10, bounds.y + 30, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}
static void DrawFrameTimes(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
float *sleepHistory;
int valid, pos;
float *history = __DisplayGetFrameTimes(&valid, &pos, &sleepHistory);
int scale = 7000;
int width = 600;
ctx->Flush();
ctx->BeginNoTex();
int bottom = bounds.y2();
for (int i = 0; i < valid; ++i) {
double activeTime = history[i] - sleepHistory[i];
ctx->Draw()->vLine(bounds.x + i, bottom, bottom - activeTime * scale, 0xFF3FFF3F);
ctx->Draw()->vLine(bounds.x + i, bottom - activeTime * scale, bottom - history[i] * scale, 0x7F3FFF3F);
}
ctx->Draw()->vLine(bounds.x + pos, bottom, bottom - 512, 0xFFff3F3f);
ctx->Draw()->hLine(bounds.x, bottom - 0.0333 * scale, bounds.x + width, 0xFF3f3Fff);
ctx->Draw()->hLine(bounds.x, bottom - 0.0167 * scale, bounds.x + width, 0xFF3f3Fff);
ctx->Flush();
ctx->Begin();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.5f, 0.5f);
ctx->Draw()->DrawText(ubuntu24, "33.3ms", bounds.x + width, bottom - 0.0333 * scale, 0xFF3f3Fff, ALIGN_BOTTOMLEFT | FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawText(ubuntu24, "16.7ms", bounds.x + width, bottom - 0.0167 * scale, 0xFF3f3Fff, ALIGN_BOTTOMLEFT | FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}
static void DrawFrameTiming(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
char statBuf[1024]{};
Draw::DrawContext *draw = ctx->GetDrawContext();
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.5f, 0.5f);
// NOTE: This is not necessarily the same as the actual present mode.
snprintf(statBuf, sizeof(statBuf),
"Presentation mode: %s Needs skip for ff: %s\n"
"Actual presentation mode: %s",
Draw::PresentModeToString(g_frameTiming.PresentMode()),
g_frameTiming.FastForwardNeedsSkipFlip() ? "true" : "false",
Draw::PresentModeToString(draw->GetCurrentPresentMode()));
ctx->Draw()->DrawTextRect(ubuntu24, statBuf, bounds.x + 10, bounds.y + 50, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
for (int i = 0; i < 5; i++) {
size_t curIndex = i + 6;
size_t prevIndex = i + 7;
FrameTimeData data = ctx->GetDrawContext()->FrameTimeHistory().Back(curIndex);
FrameTimeData prevData = ctx->GetDrawContext()->FrameTimeHistory().Back(prevIndex);
if (data.frameBegin == 0.0) {
snprintf(statBuf, sizeof(statBuf), "(No frame time data)");
} else {
double stride = data.frameBegin - prevData.frameBegin;
double fenceLatency_s = data.afterFenceWait - data.frameBegin;
double submitLatency_s = data.firstSubmit - data.frameBegin;
double queuePresentLatency_s = data.queuePresent - data.frameBegin;
double actualPresentLatency_s = data.actualPresent - data.frameBegin;
double presentMargin = data.presentMargin;
double computedMargin = data.actualPresent - data.queuePresent;
char presentStats[256] = "";
if (data.actualPresent != 0.0) {
snprintf(presentStats, sizeof(presentStats),
"* Present: %0.1f ms\n"
"* Margin: %0.1f ms\n"
"* Margin(c): %0.1f ms\n",
actualPresentLatency_s * 1000.0,
presentMargin * 1000.0,
computedMargin * 1000.0);
}
snprintf(statBuf, sizeof(statBuf),
"* Stride: %0.1f (waits: %d)\n"
"%llu: From start:\n"
"* Past fence: %0.1f ms\n"
"* Submit #1: %0.1f ms\n"
"* Queue-p: %0.1f ms\n"
"%s",
stride * 1000.0,
data.waitCount,
(long long)data.frameId,
fenceLatency_s * 1000.0,
submitLatency_s * 1000.0,
queuePresentLatency_s * 1000.0,
presentStats
);
}
ctx->Draw()->DrawTextRect(ubuntu24, statBuf, bounds.x + 10 + i * 150, bounds.y + 150, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
}
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}
void DrawFramebufferList(UIContext *ctx, GPUCommon *gpu, const Bounds &bounds) {
if (!gpu) {
return;
}
FontID ubuntu24("UBUNTU24");
auto list = gpu->GetFramebufferList();
ctx->Flush();
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.7f, 0.7f);
int i = 0;
for (const VirtualFramebuffer *vfb : list) {
char buf[512];
snprintf(buf, sizeof(buf), "%08x (Z %08x): %dx%d (stride %d, %d)",
vfb->fb_address, vfb->z_address, vfb->width, vfb->height, vfb->fb_stride, vfb->z_stride);
ctx->Draw()->DrawTextRect(ubuntu24, buf, bounds.x + 10, bounds.y + 20 + i * 50, bounds.w - 20, bounds.h - 30, 0xFFFFFFFF, FLAG_DYNAMIC_ASCII);
i++;
}
ctx->Flush();
}
void DrawControlMapperOverlay(UIContext *ctx, const Bounds &bounds, const ControlMapper &controlMapper) {
DrawControlDebug(ctx, controlMapper, ctx->GetLayoutBounds(ViewLayoutMode::ApplyInsets, false));
}
void DrawDebugOverlay(UIContext *ctx, const Bounds &bounds, DebugOverlay overlay) {
bool inGame = GetUIState() == UISTATE_INGAME;
const Bounds layoutBounds = ctx->GetLayoutBounds(ViewLayoutMode::ApplyInsets, false);
switch (overlay) {
case DebugOverlay::DEBUG_STATS:
if (inGame)
DrawDebugStats(ctx, layoutBounds);
break;
case DebugOverlay::FRAME_GRAPH:
if (inGame)
DrawFrameTimes(ctx, layoutBounds);
break;
case DebugOverlay::FRAME_TIMING:
DrawFrameTiming(ctx, layoutBounds);
break;
case DebugOverlay::Audio:
DrawAudioDebugStats(ctx, layoutBounds);
break;
#if !PPSSPP_PLATFORM(UWP) && !PPSSPP_PLATFORM(SWITCH)
case DebugOverlay::GPU_PROFILE:
if (inGame && (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL)) {
DrawGPUProfilerVis(ctx, gpu);
}
break;
case DebugOverlay::GPU_ALLOCATOR:
if (inGame && (g_Config.iGPUBackend == (int)GPUBackend::VULKAN || g_Config.iGPUBackend == (int)GPUBackend::OPENGL)) {
DrawGPUMemoryVis(ctx, gpu);
}
break;
#endif
case DebugOverlay::FRAMEBUFFER_LIST:
if (inGame)
DrawFramebufferList(ctx, gpu, layoutBounds);
break;
case DebugOverlay::JIT_STATS:
if (inGame)
DrawJitStats(ctx, layoutBounds);
break;
default:
break;
}
}
static const char *CPUCoreAsString(int core) {
switch (core) {
case 0: return "Interpreter";
case 1: return "JIT";
case 2: return "IR Interpreter";
case 3: return "JIT using IR";
default: return "N/A";
}
}
void DrawCrashDump(UIContext *ctx, const Path &gamePath) {
const MIPSExceptionInfo &info = Core_GetExceptionInfo();
auto sy = GetI18NCategory(I18NCat::SYSTEM);
FontID ubuntu24("UBUNTU24");
std::string discID = g_paramSFO.GetDiscID();
std::string activeFlags = PSP_CoreParameter().compat.GetActiveFlagsString();
if (activeFlags.empty()) {
activeFlags = "(no compat flags active)";
}
int x = 20 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_LEFT);
int y = 20 + System_GetPropertyFloat(SYSPROP_DISPLAY_SAFE_INSET_TOP);
ctx->Flush();
if (ctx->Draw()->GetFontAtlas()->getFont(ubuntu24))
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(1.1f, 1.1f);
ctx->Draw()->DrawTextShadow(ubuntu24, sy->T_cstr("Game crashed"), x, y, 0xFFFFFFFF);
char statbuf[4096];
char versionString[256];
snprintf(versionString, sizeof(versionString), "%s", PPSSPP_GIT_VERSION);
bool checkingISO = false;
bool isoOK = false;
char crcStr[50]{};
switch (PSP_CoreParameter().fileType) {
case IdentifiedFileType::PSP_ISO:
case IdentifiedFileType::PSP_ISO_NP:
{
if (Reporting::HasCRC(gamePath)) {
u32 crc = Reporting::RetrieveCRC(gamePath);
std::vector<GameDBInfo> dbInfos;
if (discID.size() >= 9 && g_gameDB.GetGameInfos(discID, &dbInfos)) {
for (auto &dbInfo : dbInfos) {
if (dbInfo.crc == crc) {
isoOK = true;
}
}
}
snprintf(crcStr, sizeof(crcStr), "CRC: %08x %s\n", crc, isoOK ? "(Known good!)" : "(not identified)");
} else {
// Queue it for calculation, we want it!
// It's OK to call this repeatedly until we have it, which is natural here.
Reporting::QueueCRC(gamePath);
checkingISO = true;
}
break;
}
default:
// Don't show ISO warning for other file types.
isoOK = true;
break;
};
// TODO: Draw a lot more information. Full register set, and so on.
#ifdef _DEBUG
const char * const build = "debug";
#else
const char * const build = "release";
#endif
std::string sysName = System_GetProperty(SYSPROP_NAME);
int sysVersion = System_GetPropertyInt(SYSPROP_SYSTEMVERSION);
// First column
y += 65;
int columnWidth = (ctx->GetBounds().w - x - 10) / 2;
int height = ctx->GetBounds().h;
ctx->PushScissor(Bounds(x, y, columnWidth, height));
// INFO_LOG(Log::System, "DrawCrashDump (%d %d %d %d)", x, y, columnWidth, height);
snprintf(statbuf, sizeof(statbuf), R"(%s
%s (%s)
%s (%s)
%s v%d (%s)
%s
)",
ExceptionTypeAsString(info.type),
discID.c_str(), g_paramSFO.GetValueString("TITLE").c_str(),
versionString, build,
sysName.c_str(), sysVersion, GetCompilerABI(),
crcStr
);
ctx->Draw()->SetFontScale(.7f, .7f);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 160;
if (info.type == MIPSExceptionType::MEMORY) {
snprintf(statbuf, sizeof(statbuf), R"(
Access: %s at %08x (sz: %d)
PC: %08x
%s)",
MemoryExceptionTypeAsString(info.memory_type),
info.address,
info.accessSize,
info.pc,
info.info.c_str());
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 180;
} else if (info.type == MIPSExceptionType::BAD_EXEC_ADDR) {
snprintf(statbuf, sizeof(statbuf), R"(
Destination: %s to %08x
PC: %08x
RA: %08x)",
ExecExceptionTypeAsString(info.exec_type),
info.address,
info.pc,
info.ra);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 180;
} else if (info.type == MIPSExceptionType::FPU) {
snprintf(statbuf, sizeof(statbuf), R"(
FPU: %s
PC: %08x
)", FPUExceptionTypeAsString(info.fpu_type), info.pc);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 180;
} else if (info.type == MIPSExceptionType::BREAK) {
snprintf(statbuf, sizeof(statbuf), R"(
BREAK
PC: %08x
)", info.pc);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 180;
} else if (info.type == MIPSExceptionType::HLE) {
snprintf(statbuf, sizeof(statbuf), R"(
%s
PC: %08x
RA: %08x)", info.info.c_str(), info.pc, info.ra);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 180;
} else {
snprintf(statbuf, sizeof(statbuf), R"(
Invalid / Unknown (%d)
)", (int)info.type);
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
y += 180;
}
std::string kernelState = __KernelStateSummary();
ctx->Draw()->DrawTextShadow(ubuntu24, kernelState.c_str(), x, y, 0xFFFFFFFF);
y += 40;
ctx->Draw()->SetFontScale(.5f, .5f);
ctx->Draw()->DrawTextShadow(ubuntu24, info.stackTrace.c_str(), x, y, 0xFFFFFFFF);
ctx->Draw()->SetFontScale(.7f, .7f);
ctx->PopScissor();
// Draw some additional stuff to the right, explaining why the background is purple, if it is.
// Should try to be in sync with Reporting::IsSupported().
std::string tips;
if (CheatsInEffect()) {
tips += "* Turn off cheats.\n";
}
if (HLEPlugins::HasEnabled()) {
tips += "* Turn off plugins.\n";
}
if (g_Config.uJitDisableFlags) {
tips += StringFromFormat("* Don't use JitDisableFlags: %08x\n", g_Config.uJitDisableFlags);
}
if (GetLockedCPUSpeedMhz()) {
tips += "* Set CPU clock to default (0)\n";
}
if (checkingISO) {
tips += "* (waiting for CRC...)\n";
} else if (!isoOK) {
tips += "* Verify and possibly re-dump your ISO\n (CRC not recognized)\n";
}
if (g_paramSFO.GetValueString("DISC_VERSION").empty()) {
tips += "\n(DISC_VERSION is empty)\n";
}
if (!tips.empty()) {
tips = "Things to try:\n" + tips;
}
x += columnWidth + 10;
y = 85;
snprintf(statbuf, sizeof(statbuf),
"CPU Core: %s (flags: %08x)\n"
"Locked CPU freq: %d MHz\n"
"Cheats: %s, Plugins: %s\n%s\n\n%s",
CPUCoreAsString(g_Config.iCpuCore), g_Config.uJitDisableFlags,
GetLockedCPUSpeedMhz(),
CheatsInEffect() ? "Y" : "N", HLEPlugins::HasEnabled() ? "Y" : "N", activeFlags.c_str(), tips.c_str());
ctx->Draw()->DrawTextShadow(ubuntu24, statbuf, x, y, 0xFFFFFFFF);
ctx->Flush();
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->RebindTexture();
}
void DrawFPS(UIContext *ctx, const Bounds &bounds) {
FontID ubuntu24("UBUNTU24");
float vps, fps, actual_fps;
__DisplayGetFPS(&vps, &fps, &actual_fps);
char temp[256];
StringWriter w(temp);
if ((g_Config.iShowStatusFlags & ((int)ShowStatusFlags::FPS_COUNTER | (int)ShowStatusFlags::SPEED_COUNTER)) == ((int)ShowStatusFlags::FPS_COUNTER | (int)ShowStatusFlags::SPEED_COUNTER)) {
// Both at the same time gets a shorter formulation.
w.F("%0.0f/%0.0f (%0.1f%%)", actual_fps, fps, vps / ((g_Config.iDisplayRefreshRate / 60.0f * 59.94f) / 100.0f));
} else {
if (g_Config.iShowStatusFlags & (int)ShowStatusFlags::FPS_COUNTER) {
w.F("FPS: %0.1f", actual_fps);
} else if (g_Config.iShowStatusFlags & (int)ShowStatusFlags::SPEED_COUNTER) {
w.F("Speed: %0.1f%%", vps / (59.94f / 100.0f));
}
}
if (System_GetPropertyBool(SYSPROP_CAN_READ_BATTERY_PERCENTAGE)) {
if (g_Config.iShowStatusFlags & (int)ShowStatusFlags::BATTERY_PERCENT) {
const int percentage = System_GetPropertyInt(SYSPROP_BATTERY_PERCENTAGE);
// Just plain append battery. Add linebreak? Nothing without a battery.
if (percentage >= 0) {
w.F(" Battery: %d%%", percentage);
}
}
}
ctx->Flush();
float offset = 10.0f;
#ifdef MOBILE_DEVICE
offset += 10.0f;
#endif
ctx->BindFontTexture();
ctx->Draw()->SetFontScale(0.7f, 0.7f);
ctx->Draw()->DrawText(ubuntu24, w.as_view(), bounds.x2() - offset, bounds.y + 12, 0xc0000000, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII);
ctx->Draw()->DrawText(ubuntu24, w.as_view(), bounds.x2() - offset - 2, bounds.y + 10, 0xFF3fFF3f, ALIGN_TOPRIGHT | FLAG_DYNAMIC_ASCII);
ctx->Draw()->SetFontScale(1.0f, 1.0f);
ctx->Flush();
ctx->RebindTexture();
}