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 @@
0 && desiredSize.y > 0) {
+ // We have called setFixedSize which will trigger another surfaceChanged after the initial
+ // one. This one is the original one, and we don't care about it.
+ Log.w(TAG, "holder.isCreating = true, ignoring. width=" + width + " height=" + height + " desWidth=" + desiredSize.x + " desHeight=" + desiredSize.y);
+ return;
+ }
+
+ Log.i(TAG, "Surface changed. Resolution: " + width + "x" + height + " Format: " + format);
+ // The window size might have changed (rotation, immersive mode, multi-window)
+ updateDisplayMeasurements();
+
+ // A rotation or a window resize doesn't recreate 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;
+ }
+ }
+
+ reportSurfaceSize(width, height, format);
+ activity.notifySurface(holder.getSurface());
+ }
+
+ @Override
+ public void surfaceDestroyed(SurfaceHolder holder) {
+ activity.notifySurface(null);
+ reportedWidth = 0;
+ reportedHeight = 0;
+
+ // Autosize the next created surface.
+ holder.setSizeFromLayout();
+ }
+
+ private void reportSurfaceSize(int width, int height, int format) {
+ reportedWidth = width;
+ reportedHeight = height;
+ reportedFormat = format;
+ reportedDensityDpi = densityDpi;
+ NativeApp.backbufferResize(width, height, format, activity.getWindowManager().getDefaultDisplay().getRotation());
+ }
+
+ // A density change rescales the UI but leaves the surface size alone, so no surfaceChanged
+ // follows it to carry the new density to the native side - the DPI is only recalculated in
+ // backbufferResize. Re-report the surface we already have, or the UI keeps the old scale
+ // until the next real resize. Call after updateDisplayMeasurements has picked up the change.
+ public void densityChanged() {
+ if (reportedWidth <= 0 || densityDpi == reportedDensityDpi) {
+ return;
+ }
+ Log.i(TAG, "Density is now " + densityDpi + ", re-reporting surface " + reportedWidth + "x" + reportedHeight);
+ reportSurfaceSize(reportedWidth, reportedHeight, reportedFormat);
+ }
+
+ public void updateDisplayMeasurements() {
+ Display display = activity.getWindowManager().getDefaultDisplay();
+ DisplayMetrics metrics = new DisplayMetrics();
+ display.getRealMetrics(metrics);
+
+ // Early in startup, we don't have a view to query, and the display size is the best we have.
+ // It's only used for config default heuristics then. Later on, we have the exact size.
+ if (surfaceView != null && surfaceView.getWidth() > 0) {
+ metrics.widthPixels = surfaceView.getWidth();
+ metrics.heightPixels = surfaceView.getHeight();
+ }
+ densityDpi = metrics.densityDpi;
+
+ NativeApp.setDisplayParameters(metrics.widthPixels, metrics.heightPixels, (int)densityDpi, display.getRefreshRate());
+ }
+
+ private void getDesiredBackbufferSize(Point sz) {
+ NativeApp.computeDesiredBackbufferDimensions();
+ sz.x = NativeApp.getDesiredBackbufferWidth();
+ sz.y = NativeApp.getDesiredBackbufferHeight();
+ }
+}
diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java
index 1ebc81caed..a8b159faf3 100644
--- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java
+++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java
@@ -42,7 +42,6 @@ import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.Surface;
import android.view.SurfaceView;
-import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.view.inputmethod.EditorInfo;
@@ -100,8 +99,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
private boolean sustainedPerfSupported;
- private View navigationCallbackView = null;
-
// audioFocusChangeListener to listen to changes in audio state
private AudioFocusChangeListener audioFocusChangeListener;
private AudioManager audioManager;
@@ -111,12 +108,15 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
// switched-away from or rotated etc.
private boolean shuttingDown;
+ // surfaceChanged now also fires for rotations and resizes, where the thread just keeps going.
+ private boolean renderLoopRunning = false;
+
// Allow for multiple connected gamepads but just consider them the same for now.
// Actually this is not entirely true, see the code.
private final ArrayList inputPlayers = new ArrayList<>();
private PowerSaveModeReceiver mPowerSaveModeReceiver = null;
- private SizeManager sizeManager = null;
+ private NativeSurfaceManager surfaceManager = null;
private static LocationHelper mLocationHelper;
private static InfraredHelper mInfraredHelper;
private static CameraHelper mCameraHelper;
@@ -555,7 +555,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
return;
}
- // WARNING: when adding new modes here, check SizeManager's workaround in surfaceCreated.j
+ // WARNING: when adding new modes here, check NativeSurfaceManager.surfaceCreated.
int nativeRotation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED;
switch (rot) {
@@ -632,7 +632,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
}
}
- sizeManager.checkDisplayMeasurements();
+ surfaceManager.updateDisplayMeasurements();
}
// Starts the native render loop thread, which owns the graphics context and its surface.
@@ -680,7 +680,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
mSensorManager = (SensorManager)getSystemService(Activity.SENSOR_SERVICE);
mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER);
- sizeManager = new SizeManager(this);
+ surfaceManager = new NativeSurfaceManager(this);
TextRenderer.init(this);
shuttingDown = false;
registerCallbacks();
@@ -688,7 +688,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
// This calls NativeApp.setDisplayParameters. Make sure that's done early in order
// to be able to set defaults when loading config for the first time. Like figuring out
// whether to start at 1x or 2x.
- sizeManager.updateDisplayMeasurements();
+ surfaceManager.updateDisplayMeasurements();
// On the first run, the shortcut parameter is passed to NativeApp.init in here.
final boolean firstRun = !initialized;
@@ -713,7 +713,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
updateSystemUiVisibility();
mSurfaceView = new NativeSurfaceView(this);
- sizeManager.setSurfaceView(mSurfaceView);
+ surfaceManager.setSurfaceView(mSurfaceView);
setInsetsListener(mSurfaceView);
setContentView(mSurfaceView);
@@ -898,10 +898,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,16 +920,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
// This will wait until the thread has exited.
Log.i(TAG, "requestExitRenderLoop");
requestExitRenderLoop();
- }
-
- void setupSystemUiCallback() {
- final View decorView = getWindow().peekDecorView();
- if (decorView == null || decorView == navigationCallbackView) {
- return;
- }
-
- sizeManager.setupSystemUiCallback(decorView);
- navigationCallbackView = decorView;
+ renderLoopRunning = false;
}
@Override
@@ -945,7 +941,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
audioFocusChangeListener = null;
audioManager = null;
- sizeManager.setSurfaceView(null);
+ surfaceManager.setSurfaceView(null);
if (mPowerSaveModeReceiver != null) {
mPowerSaveModeReceiver.destroy(this);
mPowerSaveModeReceiver = null;
@@ -971,8 +967,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
Log.i(TAG, "in onDestroy, but not shutting down.");
}
}
- navigationCallbackView = null;
-
// Really ugly workaround for VR issues when PPSSPP restarts
if (isVRDevice()) {
System.exit(0);
@@ -1011,7 +1005,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
mSensorManager.unregisterListener(this);
loseAudioFocus(this.audioManager, this.audioFocusChangeListener);
- sizeManager.onPause();
Log.i(TAG, "Calling NativeApp.pause...");
NativeApp.pause();
if (mCameraHelper != null) {
@@ -1033,7 +1026,6 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
Log.i(TAG, "onResume begin");
updateSustainedPerformanceMode();
- sizeManager.onResume();
updateSystemUiVisibility();
// OK, config should be initialized, we can query for screen rotation.
@@ -1072,25 +1064,21 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
NativeApp.accelerometer(event.values[0], event.values[1], event.values[2]);
}
- @Override
- public void onAttachedToWindow() {
- Log.i(TAG, "onAttachedToWindow");
- super.onAttachedToWindow();
- if (m_hasNoNativeBinary) {
- return;
- }
- setupSystemUiCallback();
- }
-
@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;
}
+ // The surface size change that comes with it is what actually updates the native side, except
+ // for this, which the native side was told once at init. Changes on fold/unfold.
+ NativeApp.sendMessageFromJava("smallestScreenWidthDp", String.valueOf(newConfig.smallestScreenWidthDp));
+ // This re-measures the display, so the density check below sees the new value.
updateSystemUiVisibility();
- sizeManager.updateDpi((float)newConfig.densityDpi);
+ // A density change doesn't resize the surface, so it needs separate handling.
+ surfaceManager.densityChanged();
}
@Override
@@ -1101,7 +1089,7 @@ public class PpssppActivity extends AppCompatActivity implements SensorEventList
if (m_hasNoNativeBinary) {
return;
}
- sizeManager.checkDisplayMeasurements();
+ surfaceManager.updateDisplayMeasurements();
}
// keep this static so we can call this even if we don't
diff --git a/android/src/org/ppsspp/ppsspp/SizeManager.java b/android/src/org/ppsspp/ppsspp/SizeManager.java
deleted file mode 100644
index 7e730cb594..0000000000
--- a/android/src/org/ppsspp/ppsspp/SizeManager.java
+++ /dev/null
@@ -1,192 +0,0 @@
-package org.ppsspp.ppsspp;
-
-import android.content.pm.ActivityInfo;
-import android.graphics.Point;
-import android.util.DisplayMetrics;
-import android.util.Log;
-import android.view.Display;
-import android.view.Surface;
-import android.view.SurfaceHolder;
-import android.view.SurfaceView;
-import android.view.View;
-
-public class SizeManager implements SurfaceHolder.Callback {
- private static final String TAG = "PPSSPPSizeManager";
-
- final PpssppActivity activity;
- SurfaceView surfaceView = null;
-
- private float densityDpi;
- private float refreshRate;
-
- private boolean navigationHidden = false;
- private boolean displayUpdatePending = false;
-
- private final Point desiredSize = new Point();
- private int badOrientationCount = 0;
-
- // Used to fix a race condition on some Android versions.
- private Surface earlySurface = null;
-
- private boolean paused = false;
-
- public SizeManager(final PpssppActivity a) {
- activity = a;
- }
-
- public void onResume() {
- if (earlySurface != null) {
- Log.i(TAG, "Applying deferred surface");
- activity.notifySurface(earlySurface);
- earlySurface = null;
- }
- paused = false;
- }
-
- public void onPause() {
- paused = true;
- // Make sure.
- earlySurface = null;
- }
-
- public void setSurfaceView(SurfaceView view) {
- surfaceView = view;
- if (surfaceView == null)
- return;
-
- surfaceView.getHolder().addCallback(this);
- }
-
- @Override
- public void surfaceCreated(SurfaceHolder holder) {
- int pixelWidth = holder.getSurfaceFrame().width();
- int pixelHeight = holder.getSurfaceFrame().height();
-
- // Workaround for terrible bug when locking and unlocking the screen in landscape mode on Nexus 5X.
- // TODO: Look into removing this.
- int requestedOr = activity.getRequestedOrientation();
- boolean requestedPortrait = requestedOr == ActivityInfo.SCREEN_ORIENTATION_PORTRAIT || requestedOr == ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT;
- boolean detectedPortrait = pixelHeight > pixelWidth;
- if (badOrientationCount < 3 && requestedPortrait != detectedPortrait && requestedOr != ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED && requestedOr != ActivityInfo.SCREEN_ORIENTATION_SENSOR_LANDSCAPE && requestedOr != ActivityInfo.SCREEN_ORIENTATION_SENSOR) {
- Log.e(TAG, "Bad orientation detected (w=" + pixelWidth + " h=" + pixelHeight + "! Recreating activity.");
- badOrientationCount++;
- activity.recreate();
- return;
- } else if (requestedPortrait == detectedPortrait) {
- Log.i(TAG, "Correct orientation detected, resetting orientation counter.");
- badOrientationCount = 0;
- } else {
- Log.i(TAG, "Bad orientation detected but ignored");
- }
-
- Display display = activity.getWindowManager().getDefaultDisplay();
-
- refreshRate = display.getRefreshRate();
-
- Log.d(TAG, "Surface created. pixelWidth=" + pixelWidth + ", pixelHeight=" + pixelHeight + " holder: " + holder + " or: " + requestedOr + " " + refreshRate + "Hz");
- NativeApp.setDisplayParameters(pixelWidth, pixelHeight, (int)densityDpi, refreshRate);
- getDesiredBackbufferSize(desiredSize);
-
- // Note that desiredSize might be 0,0 here - but that's fine when calling setFixedSize! It means auto.
- if (desiredSize.x == 0) {
- Log.d(TAG, "Setting auto surface size (not fixed)");
- } else {
- Log.d(TAG, "Setting fixed surface size " + desiredSize.x + " x " + desiredSize.y);
- }
- holder.setFixedSize(desiredSize.x, desiredSize.y);
- }
-
- @Override
- public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
- Log.v(TAG, "surfaceChanged: isCreating:" + holder.isCreating() + " holder: " + holder);
- if (holder.isCreating() && desiredSize.x > 0 && desiredSize.y > 0) {
- // We have called setFixedSize which will trigger another surfaceChanged after the initial
- // one. This one is the original one, and we don't care about it.
- Log.w(TAG, "holder.isCreating = true, ignoring. width=" + width + " height=" + height + " desWidth=" + desiredSize.x + " desHeight=" + desiredSize.y);
-
- // TODO: Should we still set earlySurface here, to be sure?
- return;
- }
-
- 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();
-
- if (!paused) {
- activity.notifySurface(holder.getSurface());
- } else {
- earlySurface = holder.getSurface();
- Log.i(TAG, "Skipping notifySurface while paused - deferring to resume");
- }
- }
-
- @Override
- public void surfaceDestroyed(SurfaceHolder holder) {
- // Don't hand a dead surface over on resume.
- earlySurface = null;
- activity.notifySurface(null);
-
- // Autosize the next created surface.
- holder.setSizeFromLayout();
- }
-
- public void checkDisplayMeasurements() {
- if (displayUpdatePending) {
- return;
- }
- displayUpdatePending = true;
-
- activity.runOnUiThread(() -> {
- Log.d(TAG, "checkDisplayMeasurements: checking now");
- updateDisplayMeasurements();
- });
- }
-
- public void updateDisplayMeasurements() {
- displayUpdatePending = false;
-
- Display display = activity.getWindowManager().getDefaultDisplay();
- // Early in startup, we don't have a view to query. Do our best to get some kind of size
- // that can be used by config default heuristics, and so on.
- DisplayMetrics metrics = new DisplayMetrics();
- if (navigationHidden) {
- display.getRealMetrics(metrics);
- } else {
- display.getMetrics(metrics);
- }
-
- // Later on, we have the exact pixel size so let's just use it.
- if (surfaceView != null) {
- metrics.widthPixels = surfaceView.getWidth();
- metrics.heightPixels = surfaceView.getHeight();
- }
- densityDpi = metrics.densityDpi;
- refreshRate = display.getRefreshRate();
-
- NativeApp.setDisplayParameters(metrics.widthPixels, metrics.heightPixels, (int)densityDpi, refreshRate);
- }
-
- public void setupSystemUiCallback(final View view) {
- view.setOnSystemUiVisibilityChangeListener(visibility -> {
- // Called when the system UI's visibility changes, regardless of
- // whether it's because of our or system actions.
- // We will try to force it to follow our preference but will not stupidly
- // act as if it's visible if it's not.
- navigationHidden = ((visibility & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0);
- // TODO: Check here if it's the state we want.
- Log.i(TAG, "SystemUiVisibilityChange! visibility=" + visibility + " navigationHidden: " + navigationHidden + " decorView: " + view.getWidth() + "x" + view.getHeight());
- checkDisplayMeasurements();
- });
- }
-
- public void updateDpi(float dpi) {
- densityDpi = dpi;
- }
-
- private void getDesiredBackbufferSize(Point sz) {
- NativeApp.computeDesiredBackbufferDimensions();
- sz.x = NativeApp.getDesiredBackbufferWidth();
- sz.y = NativeApp.getDesiredBackbufferHeight();
- }
-}