From d0e45b94d940bf15083e0b9fc820cf703ebe8a2f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Henrik=20Rydg=C3=A5rd?= Date: Tue, 22 Sep 2026 10:16:11 -0600 Subject: [PATCH] Android: Handle rotation and window resizing without recreating the activity Every rotation, fold or window resize destroyed the activity and with it all of graphics, then built it all up again. Declare those config changes handled, so they arrive as a surfaceChanged and go through the same resize path as a desktop window resize. That path needed three things it didn't have on Android: - The GPU_DISPLAY_RESIZED message, left out for Android since 2020 when DeviceRestore covered every size change. - A fixed (hardware scaled) surface size is now recomputed on each in-place surface change, since surfaceCreated no longer runs on a rotation. - A 180 degree flip keeps the size but changes the surface transform, so the display rotation is passed along and a change in it is a resize too. Co-Authored-By: Claude Fable 5.1 --- UI/NativeApp.cpp | 4 +++- android/AndroidManifest.xml | 2 +- android/jni/app-android.cpp | 14 +++++++---- android/src/org/ppsspp/ppsspp/NativeApp.java | 4 +++- .../src/org/ppsspp/ppsspp/PpssppActivity.java | 14 +++++++++-- .../src/org/ppsspp/ppsspp/SizeManager.java | 24 ++++++++++++++++++- 6 files changed, 52 insertions(+), 10 deletions(-) diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index ab2d522120..0bc223ee09 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -1345,7 +1345,9 @@ void NativeFrame(GraphicsContext *graphicsContext) { g_screenManager->resized(); // TODO: Move this to the GraphicsContext objects for each backend. -#if !PPSSPP_PLATFORM(WINDOWS) && !defined(ANDROID) + // (Android used to be left out here, since every size change there was a full graphics restart, + // and DeviceRestore covers it. Rotation and window resizing are in-place now.) +#if !PPSSPP_PLATFORM(WINDOWS) PSP_CoreParameter().pixelWidth = g_display.pixel_xres; PSP_CoreParameter().pixelHeight = g_display.pixel_yres; System_PostUIMessage(UIMessage::GPU_DISPLAY_RESIZED); diff --git a/android/AndroidManifest.xml b/android/AndroidManifest.xml index cb83fc8004..f715a4c4da 100644 --- a/android/AndroidManifest.xml +++ b/android/AndroidManifest.xml @@ -53,7 +53,7 @@ inputPlayers = new ArrayList<>(); @@ -898,10 +901,15 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList return; } + if (renderLoopRunning) { + Log.i(TAG, "startRenderLoopThread: already running, surface changed in place"); + return; + } + Log.w(TAG, "startRenderLoopThread: Starting thread"); applyFrameRate(mSurface, 60.0f); - runRenderLoop(mSurface); + renderLoopRunning = runRenderLoop(mSurface); } private synchronized void joinRenderLoopThread() { @@ -915,6 +923,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList // This will wait until the thread has exited. Log.i(TAG, "requestExitRenderLoop"); requestExitRenderLoop(); + renderLoopRunning = false; } void setupSystemUiCallback() { @@ -1085,7 +1094,8 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList @Override public void onConfigurationChanged(@NonNull Configuration newConfig) { super.onConfigurationChanged(newConfig); - Log.i(TAG, "onConfigurationChanged"); + Log.i(TAG, "onConfigurationChanged: orientation=" + newConfig.orientation + " density=" + newConfig.densityDpi + + " screen=" + newConfig.screenWidthDp + "x" + newConfig.screenHeightDp + "dp"); if (m_hasNoNativeBinary) { return; } diff --git a/android/src/org/ppsspp/ppsspp/SizeManager.java b/android/src/org/ppsspp/ppsspp/SizeManager.java index 7e730cb594..585a2b9d0b 100644 --- a/android/src/org/ppsspp/ppsspp/SizeManager.java +++ b/android/src/org/ppsspp/ppsspp/SizeManager.java @@ -110,9 +110,31 @@ public class SizeManager implements SurfaceHolder.Callback { Log.i(TAG, "Surface changed. Resolution: " + width + "x" + height + " Format: " + format); // The window size might have changed (immersive mode, native fullscreen on some devices) - NativeApp.backbufferResize(width, height, format); updateDisplayMeasurements(); + // A rotation or a window resize no longer recreates the activity, so a fixed-size (hardware scaled) + // surface has to be re-fixed here for the new display size and aspect. The fixed size is what the + // surface will report, so only do it when it doesn't already match, or this would loop. + if (!holder.isCreating()) { + Point newDesired = new Point(); + getDesiredBackbufferSize(newDesired); + boolean fixed = desiredSize.x > 0 && desiredSize.y > 0; + boolean wantFixed = newDesired.x > 0 && newDesired.y > 0; + if (wantFixed && (newDesired.x != width || newDesired.y != height)) { + Log.i(TAG, "Display changed, re-fixing surface size to " + newDesired.x + " x " + newDesired.y); + desiredSize.set(newDesired.x, newDesired.y); + holder.setFixedSize(newDesired.x, newDesired.y); + return; // Another surfaceChanged is coming, with the new size. + } else if (!wantFixed && fixed) { + Log.i(TAG, "Display changed, surface no longer needs a fixed size"); + desiredSize.set(0, 0); + holder.setSizeFromLayout(); + return; + } + } + + NativeApp.backbufferResize(width, height, format, activity.getWindowManager().getDefaultDisplay().getRotation()); + if (!paused) { activity.notifySurface(holder.getSurface()); } else {