From ac8440e35dd7ab345f87d5e47f4965c65b7d101d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Mon, 26 Oct 2026 15:00:41 +0100 Subject: [PATCH] Settings: Don't keep the draw and UI contexts in page callbacks Android replaces both when it loses the surface (a recreate, which changing the display resolution does, or switching away), but the settings screen and its views live on. The external display setting's enabled check, which runs on every draw, then called into the deleted DrawContext ("Pure virtual function called!"). The UI scale and "Reload UI atlas" handlers had the same problem with the UIContext. Look them up through the screen manager when needed instead. Co-Authored-By: Claude Opus 5.5 (1M context) --- UI/DeveloperSettingsPages.cpp | 6 +++--- UI/Settings/GeneralPage.cpp | 6 +++--- UI/Settings/GraphicsPage.cpp | 6 ++++-- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/UI/DeveloperSettingsPages.cpp b/UI/DeveloperSettingsPages.cpp index 56531eb815..55ba3a65fc 100644 --- a/UI/DeveloperSettingsPages.cpp +++ b/UI/DeveloperSettingsPages.cpp @@ -403,9 +403,9 @@ static void BuildUIPage(SettingsPage *page) { using namespace UI; SettingsPageHost *host = page->Host(); - UIContext *uiContext = host->PageScreenManager()->getUIContext(); - page->Choice("Reload UI atlas").OnClick([uiContext](UI::EventParams &) { - uiContext->InvalidateAtlas(); + // Looked up when clicked, the UI context is recreated along with the graphics. + page->Choice("Reload UI atlas").OnClick([host](UI::EventParams &) { + host->PageScreenManager()->getUIContext()->InvalidateAtlas(); }); // TODO: Move most of these strings out of the SysInfo category. diff --git a/UI/Settings/GeneralPage.cpp b/UI/Settings/GeneralPage.cpp index 9f548f13bf..9664efbadc 100644 --- a/UI/Settings/GeneralPage.cpp +++ b/UI/Settings/GeneralPage.cpp @@ -154,11 +154,11 @@ static void BuildUISettingsPage(SettingsPage *page) { using namespace UI; SettingsPageHost *host = page->Host(); - UIContext *ctx = host->PageScreenManager()->getUIContext(); - page->Setting(&g_Config.iUIScaleFactor).OnChange([ctx](UI::EventParams &e) { + // The UI context is looked up when used, since it's recreated along with the graphics. + page->Setting(&g_Config.iUIScaleFactor).OnChange([host](UI::EventParams &e) { const float dpiMul = UIScaleFactorToMultiplier(g_Config.iUIScaleFactor); g_display.Recalculate(-1, -1, -1, -1, dpiMul); - ctx->InvalidateAtlas(); + host->PageScreenManager()->getUIContext()->InvalidateAtlas(); NativeResized(); }); diff --git a/UI/Settings/GraphicsPage.cpp b/UI/Settings/GraphicsPage.cpp index 00e2b65232..d6c36a5ca4 100644 --- a/UI/Settings/GraphicsPage.cpp +++ b/UI/Settings/GraphicsPage.cpp @@ -175,8 +175,10 @@ void BuildGraphicsSettings(SettingsPage *page) { #if (PPSSPP_PLATFORM(IOS) && !PPSSPP_PLATFORM(TVOS)) || PPSSPP_PLATFORM(ANDROID) // Showing the game there needs a second swapchain. Otherwise the system mirrors the screen. - page->Setting(&g_Config.iExternalDisplay).EnabledFunc([draw] { - return draw->GetDeviceCaps().multipleSwapchains; + // Looked up each time: the draw context is replaced when Android loses the surface, and this page isn't rebuilt. + page->Setting(&g_Config.iExternalDisplay).EnabledFunc([host] { + Draw::DrawContext *draw = host->PageScreenManager()->getDrawContext(); + return draw && draw->GetDeviceCaps().multipleSwapchains; }).OnChange([previous = g_Config.iExternalDisplay](UI::EventParams &e) mutable { // Picking the current value again would rebuild the TV's output for nothing (iOS starts over on a change). if (g_Config.iExternalDisplay == previous) {