Add overlay (bezel) support

Draws a user image around or behind the PSP screen, to fill the black bars
on displays whose aspect ratio doesn't match the PSP's (#20325).

- Overlays are PNGs in PSP/OVERLAYS. A plain PNG there just works: it's
  drawn in front of the game, and its transparent hole is found by walking
  from the image center to each edge until a nearly opaque pixel. Existing
  RetroArch overlay images can be dropped in as-is.
- A subfolder with an overlay.ini can define overlays with a Name, Layer
  (Foreground/Background) and an explicit Hole=x,y,w,h.
- "Load overlay image..." in the display layout screen imports any PNG
  (including Android content URIs) into the overlays folder and selects it.
- The overlay is fit to the screen (aspect preserved, centered). The hole is
  mapped with the same scale, and the game is fit into it with the normal
  stretch / integer scale / aspect ratio options. Offset and scale don't
  apply while an overlay with a hole is active. This goes through
  CalculateDisplayOutputRect, so touch mapping and post shaders agree.
- The selection is a per-orientation, per-game DisplayLayout setting.
- Disabled in VR, Cardboard, and with skip buffer effects (except software
  rendering), since the presentation path doesn't run there.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01QGBg32EFPsTXjAFu7dnTcz
This commit is contained in:
LunaMooandClaude Opus 5.5 committed 2026-10-22 14:09:51 +02:00
1 parent ff3d184b43
commit 3ce3973781
18 files changed
+697 -6

No files matched your search

+53
View File
@@ -30,6 +30,10 @@
#include "Common/System/NativeApp.h"
#include "Common/VR/PPSSPPVR.h"
#include "Common/StringUtils.h"
#include "Common/File/FileUtil.h"
#include "Common/System/OSD.h"
#include "Common/System/Request.h"
#include "Core/Util/PathUtil.h"
#include "Common/Data/Color/RGBAUtil.h"
#include "Common/Data/Text/I18n.h"
@@ -41,6 +45,7 @@
#include "Core/System.h"
#include "GPU/GPUCommon.h"
#include "GPU/Common/FramebufferManagerCommon.h"
#include "GPU/Common/Overlay.h"
#include "GPU/Common/PresentationCommon.h"
static const int leftColumnWidth = 200;
@@ -345,6 +350,54 @@ void DisplayLayoutScreen::CreateViews() {
if (!IsVREnabled()) {
static const char *bufFilters[] = { "Linear", "Nearest", };
leftColumn->Add(new PopupMultiChoice(&config.iDisplayFilter, gr->T("Screen Scaling Filter"), bufFilters, 1, ARRAY_SIZE(bufFilters), I18NCat::GRAPHICS, screenManager()));
// Overlays (bezels), from PSP/OVERLAYS. Rescanned each time, so new files show up without a restart.
ReloadAllOverlayInfo();
const std::vector<OverlayInfo> overlays = GetAllOverlayInfo();
std::vector<std::string> overlayNames{ std::string(sy->T("Off")) };
std::vector<std::string> overlayIds{ "" };
bool selectedFound = config.sOverlay.empty();
for (const auto &overlay : overlays) {
overlayNames.push_back(overlay.name);
overlayIds.push_back(overlay.id);
if (overlay.id == config.sOverlay)
selectedFound = true;
}
if (!selectedFound) {
// Keep showing a selected overlay whose files went missing, so it can be turned off.
overlayNames.push_back(config.sOverlay);
overlayIds.push_back(config.sOverlay);
}
if (overlayIds.size() > 1) {
auto overlayChoice = leftColumn->Add(new PopupMultiChoiceDynamic(&config.sOverlay, gr->T("Overlay"), overlayNames, I18NCat::NONE, screenManager(), &overlayIds));
overlayChoice->OnChoice.Add([](UI::EventParams &) {
// The game rect may have moved, which matters for post shaders that run at output resolution.
NotifyPostChanges();
});
}
// Just pick any PNG: it's copied into the overlays folder and everything is auto-detected.
Choice *browseOverlay = leftColumn->Add(new Choice(gr->T("Load overlay image...")));
const DeviceOrientation orientation = GetDeviceOrientation();
browseOverlay->OnClick.Add([this, orientation](UI::EventParams &) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
const Path savePath = GetSysDirectory(DIRECTORY_CUSTOM_OVERLAYS) / "imported.png";
System_BrowseForImage(GetRequesterToken(), gr->T("Load overlay image..."), savePath, [this, orientation, savePath](std::string_view value, int converted) {
// If the platform converted the image, it's been saved to savePath already.
const Path source = converted == 1 ? savePath : Path(value);
std::string id;
if (!source.empty() && ImportOverlayImage(source, &id)) {
if (converted == 1)
File::Delete(savePath);
g_Config.GetDisplayLayoutConfig(orientation).sOverlay = id;
NotifyPostChanges();
RecreateViews();
} else if (!value.empty() || converted == 1) {
auto gr = GetI18NCategory(I18NCat::GRAPHICS);
g_OSD.Show(OSDType::MESSAGE_ERROR, gr->T("Overlay images must be PNG files"));
}
});
});
}
Draw::DrawContext *draw = screenManager()->getDrawContext();