Overlay: follow display offset/scale, avoid needless reloads

- Apply the display layout's scale and offset to the overlay itself instead
  of always centering it, so moving/resizing the display in Display Layout
  moves the overlay with the game inside it, and the portrait default
  (screen high up) is respected.
- Rescanning overlays (done whenever the display layout screen recreates its
  views, e.g. after closing the picker popup) no longer unloads the active
  overlay unless its definition changed.
- Re-picking the current overlay no longer posts GPU config change
  notifications, which caused an audio glitch with a game running behind.

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 3ce3973781
commit 519f282f99
4 files changed
+34 -13

No files matched your search

+21 -5
View File
@@ -172,7 +172,19 @@ void ReloadAllOverlayInfo() {
std::lock_guard<std::mutex> guard(g_overlayLock);
ScanOverlays(&g_overlayInfo);
g_overlayInfoLoaded = true;
UnloadActiveLocked();
// Keep the loaded overlay if its definition didn't change, so that rescanning (which the
// display layout screen does every time its views are recreated) doesn't cause a reload.
if (!g_activeId.empty()) {
auto iter = std::find_if(g_overlayInfo.begin(), g_overlayInfo.end(), [](const OverlayInfo &info) { return info.id == g_activeId; });
const bool unchanged = iter != g_overlayInfo.end() && !g_activeFailed &&
iter->imagePath == g_activeInfo.imagePath && iter->layer == g_activeInfo.layer &&
iter->holeFromIni == g_activeInfo.holeFromIni && iter->holeX == g_activeInfo.holeX &&
iter->holeY == g_activeInfo.holeY && iter->holeW == g_activeInfo.holeW && iter->holeH == g_activeInfo.holeH;
if (!unchanged) {
UnloadActiveLocked();
}
}
}
std::vector<OverlayInfo> GetAllOverlayInfo() {
@@ -216,6 +228,7 @@ bool ImportOverlayImage(const Path &source, std::string *id) {
return false;
}
INFO_LOG(Log::G3D, "Imported overlay %s as %s", source.c_str(), filename.c_str());
// The file name is new, so this can't affect the loaded overlay.
ReloadAllOverlayInfo();
*id = filename;
return true;
@@ -305,12 +318,15 @@ bool GetOverlayLayout(const DisplayLayoutConfig &config, float frameX, float fra
const float imgW = (float)g_activeImage->width;
const float imgH = (float)g_activeImage->height;
// Fit the overlay to the frame, keeping the aspect ratio, centered.
const float scale = std::min(frameW / imgW, frameH / imgH);
// Fit the overlay to the frame, keeping the aspect ratio. Then apply the display layout's scale
// and offset to the overlay, the same way they'd apply to the game without one, so moving and
// resizing the display moves the overlay (with the game inside it). This also keeps the portrait
// default of having the screen high up.
const float scale = std::min(frameW / imgW, frameH / imgH) * config.fDisplayScale;
layout->overlayW = imgW * scale;
layout->overlayH = imgH * scale;
layout->overlayX = frameX + (frameW - layout->overlayW) * 0.5f;
layout->overlayY = frameY + (frameH - layout->overlayH) * 0.5f;
layout->overlayX = frameX + frameW * config.fDisplayOffsetX - layout->overlayW * 0.5f;
layout->overlayY = frameY + frameH * config.fDisplayOffsetY - layout->overlayH * 0.5f;
layout->layer = g_activeInfo.layer;
layout->hasHole = g_activeHasHole;
+6 -5
View File
@@ -33,10 +33,11 @@
// Layer=Foreground ; or Background (drawn behind the game)
// Hole=0,0,640,362 ; optional, in image pixels. Auto-detected if missing.
//
// Placement: the overlay is fit to the screen (aspect preserved, centered). The hole is mapped
// to the screen with the same scale and offset, and the PSP screen is then fit into the hole
// using the normal display layout options (stretch, integer scale, aspect ratio).
// The display offset and scale options are ignored while an overlay with a hole is active.
// Placement: the overlay is fit to the screen (aspect preserved), then the display layout's scale
// and offset are applied to it, like they normally are to the game. So moving or resizing the
// display moves the overlay, and the portrait default (screen high up) works. The hole is mapped
// with the same scale and offset, and the PSP screen is fit into the hole using the remaining
// display options (stretch, integer scale, aspect ratio).
#pragma once
@@ -86,7 +87,7 @@ struct OverlayLayout {
uint32_t generation = 0;
};
// Rescans the overlays folder. Also unloads the active overlay, so it gets reloaded from disk.
// Rescans the overlays folder. The loaded overlay is kept unless its definition changed or it's gone.
void ReloadAllOverlayInfo();
std::vector<OverlayInfo> GetAllOverlayInfo();
+2 -2
View File
@@ -108,8 +108,8 @@ void CalculateDisplayOutputRect(const DisplayLayoutConfig &config, FRect *rc, fl
float scale = config.fDisplayScale;
// With an overlay that has a hole, the overlay is fit to the screen and the game is fit into
// the hole instead. The offset and scale options don't apply then, the overlay decides those.
// With an overlay that has a hole, the game is fit into the hole instead. The offset and scale
// options were already applied to the overlay itself, see GetOverlayLayout.
FRect frame = screenFrame;
OverlayLayout overlay;
const bool inOverlayHole = GetOverlayLayout(config, screenFrame.x, screenFrame.y, screenFrame.w, screenFrame.h, &overlay) && overlay.hasHole;
+5 -1
View File
@@ -370,7 +370,11 @@ void DisplayLayoutScreen::CreateViews() {
}
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 &) {
overlayChoice->OnChoice.Add([](UI::EventParams &e) {
// e.b is set if the value actually changed. Re-picking the current one shouldn't
// cause a reload, since that glitches the audio of a game running in the background.
if (!e.b)
return;
// The game rect may have moved, which matters for post shaders that run at output resolution.
NotifyPostChanges();
});