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(); - } -}