diff --git a/Core/Config.cpp b/Core/Config.cpp index 1248c363e915..895d05a1b21d 100644 --- a/Core/Config.cpp +++ b/Core/Config.cpp @@ -411,7 +411,7 @@ static int DefaultAndroidHwScale() { int xres = System_GetPropertyInt(SYSPROP_DISPLAY_XRES); int yres = System_GetPropertyInt(SYSPROP_DISPLAY_YRES); - if (xres < 960) { + if (xres <= 960) { // Smaller than the PSP*2, let's go native. return 0; } else if (xres <= 480 * 3) { // 720p xres diff --git a/GPU/GLES/TransformPipeline.cpp b/GPU/GLES/TransformPipeline.cpp index 7ef3f0d08d1e..b23d5239bc4a 100644 --- a/GPU/GLES/TransformPipeline.cpp +++ b/GPU/GLES/TransformPipeline.cpp @@ -209,8 +209,8 @@ void TransformDrawEngine::DestroyDeviceObjects() { } } -void TransformDrawEngine::GLLost() { - ILOG("TransformDrawEngine::GLLost()"); +void TransformDrawEngine::GLRestore() { + ILOG("TransformDrawEngine::GLRestore()"); // The objects have already been deleted. bufferNameCache_.clear(); bufferNameInfo_.clear(); diff --git a/GPU/GLES/TransformPipeline.h b/GPU/GLES/TransformPipeline.h index a1873820052f..12fdade6ec30 100644 --- a/GPU/GLES/TransformPipeline.h +++ b/GPU/GLES/TransformPipeline.h @@ -129,7 +129,7 @@ class TransformDrawEngine : public DrawEngineCommon, public GfxResourceHolder { void RestoreVAO(); void InitDeviceObjects(); void DestroyDeviceObjects(); - void GLLost() override; + void GLRestore() override; void Resized(); void DecimateTrackedVertexArrays(); diff --git a/UI/GameSettingsScreen.cpp b/UI/GameSettingsScreen.cpp index be63de07a173..ecb0f45cabdb 100644 --- a/UI/GameSettingsScreen.cpp +++ b/UI/GameSettingsScreen.cpp @@ -193,6 +193,8 @@ void GameSettingsScreen::CreateViews() { #ifdef ANDROID static const char *deviceResolutions[] = { "Native device resolution", "Auto (same as Rendering)", "1x PSP", "2x PSP", "3x PSP", "4x PSP", "5x PSP" }; int max_res_temp = std::max(System_GetPropertyInt(SYSPROP_DISPLAY_XRES), System_GetPropertyInt(SYSPROP_DISPLAY_YRES)) / 480 + 2; + if (max_res_temp == 3) + max_res_temp = 4; // At least allow 2x int max_res = std::min(max_res_temp, (int)ARRAY_SIZE(deviceResolutions)); UI::PopupMultiChoice *hwscale = graphicsSettings->Add(new PopupMultiChoice(&g_Config.iAndroidHwScale, gr->T("Display Resolution (HW scaler)"), deviceResolutions, 0, max_res, gr->GetName(), screenManager())); hwscale->OnChoice.Handle(this, &GameSettingsScreen::OnHwScaleChange); // To refresh the display mode @@ -647,14 +649,18 @@ UI::EventReturn GameSettingsScreen::OnHardwareTransform(UI::EventParams &e) { UI::EventReturn GameSettingsScreen::OnScreenRotation(UI::EventParams &e) { ILOG("New display rotation: %d", g_Config.iScreenRotation); + ILOG("Sending rotate"); System_SendMessage("rotate", ""); + ILOG("Got back from rotate"); return UI::EVENT_DONE; } static void RecreateActivity() { const int SYSTEM_JELLYBEAN = 16; if (System_GetPropertyInt(SYSPROP_SYSTEMVERSION) >= SYSTEM_JELLYBEAN) { + ILOG("Sending recreate"); System_SendMessage("recreate", ""); + ILOG("Got back from recreate"); } else { I18NCategory *gr = GetI18NCategory("Graphics"); System_SendMessage("toast", gr->T("Must Restart", "You must restart PPSSPP for this change to take effect")); diff --git a/UI/NativeApp.cpp b/UI/NativeApp.cpp index 521e90cba4d1..d481fb342916 100644 --- a/UI/NativeApp.cpp +++ b/UI/NativeApp.cpp @@ -766,12 +766,13 @@ void NativeUpdate(InputState &input) { screenManager->update(input); } -void NativeDeviceLost() { - // g_gameInfoCache.Clear(); +void NativeDeviceRestore() { + if (g_gameInfoCache) + g_gameInfoCache->Clear(); screenManager->deviceLost(); if (GetGPUBackend() == GPUBackend::OPENGL) { - gl_lost(); + gl_restore(); } } diff --git a/android/jni/app-android.cpp b/android/jni/app-android.cpp index e5ca1cff9c4e..aa66e7923bfa 100644 --- a/android/jni/app-android.cpp +++ b/android/jni/app-android.cpp @@ -121,6 +121,21 @@ void AndroidEGLGraphicsContext::SwapBuffers() { gl->Swap(); } +// Doesn't do much. Just to fit in. +class AndroidJavaEGLGraphicsContext : public GraphicsContext { +public: + AndroidJavaEGLGraphicsContext() {} + bool Init(ANativeWindow *wnd, int desiredBackbufferSizeX, int desiredBackbufferSizeY, int backbufferFormat, int androidVersion) { return true; } + void Shutdown() override {} + void SwapBuffers() override {} + void SwapInterval(int interval) override {} + void Resize() {} + Thin3DContext *CreateThin3DContext() { + CheckGLExtensions(); + return T3DCreateGLContext(); + } +}; + static recursive_mutex frameCommandLock; static std::queue frameCommands; @@ -165,10 +180,14 @@ InputState input_state; static bool renderer_inited = false; static bool first_lost = true; +static bool javaGL = true; + static std::string library_path; static std::map permissions; -AndroidEGLGraphicsContext *graphicsContext; +GraphicsContext *graphicsContext; + +static void ProcessFrameCommands(JNIEnv *env); void PushCommand(std::string cmd, std::string param) { lock_guard guard(frameCommandLock); @@ -295,9 +314,9 @@ extern "C" jstring Java_org_ppsspp_ppsspp_NativeApp_queryConfig extern "C" void Java_org_ppsspp_ppsspp_NativeApp_init (JNIEnv *env, jclass, jstring jmodel, jint jdeviceType, jstring jlangRegion, jstring japkpath, jstring jdataDir, jstring jexternalDir, jstring jlibraryDir, jstring jcacheDir, jstring jshortcutParam, - jint jAndroidVersion) { + jint jAndroidVersion, jboolean jjavaGL) { jniEnvUI = env; - + javaGL = jjavaGL; setCurrentThreadName("androidInit"); ILOG("NativeApp.init() -- begin"); @@ -402,8 +421,110 @@ extern "C" void Java_org_ppsspp_ppsspp_NativeApp_shutdown(JNIEnv *, jclass) { ILOG("NativeApp.shutdown() -- end"); } +// JavaEGL +extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayInit(JNIEnv * env, jobject obj) { + ILOG("NativeApp.displayInit()"); + if (javaGL && !graphicsContext) { + graphicsContext = new AndroidJavaEGLGraphicsContext(); + } + + if (!renderer_inited) { + NativeInitGraphics(graphicsContext); + renderer_inited = true; + } else { + NativeDeviceRestore(); // ??? + ILOG("displayInit: NativeDeviceRestore completed."); + } +} + +// JavaEGL +extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayResize(JNIEnv *, jobject clazz, jint w, jint h, jint dpi, jfloat refreshRate) { + ILOG("NativeApp.displayResize(%i x %i, dpi=%i, refresh=%0.2f)", w, h, dpi, refreshRate); + + /* + g_dpi = dpi; + g_dpi_scale = 240.0f / (float)g_dpi; + + pixel_xres = w; + pixel_yres = h; + dp_xres = pixel_xres * g_dpi_scale; + dp_yres = pixel_yres * g_dpi_scale; + dp_xscale = (float)dp_xres / pixel_xres; + dp_yscale = (float)dp_yres / pixel_yres; + */ + // display_hz = refreshRate; + + pixel_xres = w; + pixel_yres = h; + // backbuffer_format = format; + + g_dpi = (int)display_dpi; + g_dpi_scale = 240.0f / (float)g_dpi; + + dp_xres = display_xres * g_dpi_scale; + dp_yres = display_yres * g_dpi_scale; + + // Touch scaling is from display pixels to dp pixels. + dp_xscale = (float)dp_xres / (float)display_xres; + dp_yscale = (float)dp_yres / (float)display_yres; + + pixel_in_dps = (float)pixel_xres / dp_xres; + + NativeResized(); +} + +// JavaEGL +extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayRender(JNIEnv *env, jobject obj) { + static bool hasSetThreadName = false; + if (!hasSetThreadName) { + hasSetThreadName = true; + setCurrentThreadName("AndroidRender"); + } + + if (renderer_inited) { + // TODO: Look into if these locks are a perf loss + { + lock_guard guard(input_state.lock); + + input_state.pad_lstick_x = left_joystick_x_async; + input_state.pad_lstick_y = left_joystick_y_async; + input_state.pad_rstick_x = right_joystick_x_async; + input_state.pad_rstick_y = right_joystick_y_async; + + UpdateInputState(&input_state); + } + NativeUpdate(input_state); + + { + lock_guard guard(input_state.lock); + EndInputState(&input_state); + } + + NativeRender(graphicsContext); + time_update(); + } else { + ELOG("BAD: Ended up in nativeRender even though app has quit.%s", ""); + // Shouldn't really get here. Let's draw magenta. + glDepthMask(GL_TRUE); + glColorMask(GL_TRUE, GL_TRUE, GL_TRUE, GL_TRUE); + glClearColor(1.0, 0.0, 1.0f, 1.0f); + glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT | GL_STENCIL_BUFFER_BIT); + } + + lock_guard guard(frameCommandLock); + if (!nativeActivity) { + while (!frameCommands.empty()) + frameCommands.pop(); + return; + } + + ProcessFrameCommands(env); +} + extern "C" void Java_org_ppsspp_ppsspp_NativeRenderer_displayShutdown(JNIEnv *env, jobject obj) { + ILOG("NativeApp.displayShutdown()"); if (renderer_inited) { + NativeShutdownGraphics(); renderer_inited = false; NativeMessageReceived("recreateviews", ""); } @@ -427,6 +548,7 @@ PermissionStatus System_GetPermissionStatus(SystemPermission permission) { extern "C" jboolean JNICALL Java_org_ppsspp_ppsspp_NativeApp_touch (JNIEnv *, jclass, float x, float y, int code, int pointerId) { + float scaledX = x * dp_xscale; float scaledY = y * dp_yscale; @@ -679,7 +801,7 @@ extern "C" jint JNICALL Java_org_ppsspp_ppsspp_NativeApp_getDesiredBackbufferHei return desiredBackbufferSizeY; } -void ProcessFrameCommands(JNIEnv *env) { +static void ProcessFrameCommands(JNIEnv *env) { lock_guard guard(frameCommandLock); while (!frameCommands.empty()) { FrameCommand frameCmd; @@ -757,9 +879,6 @@ extern "C" bool JNICALL Java_org_ppsspp_ppsspp_NativeActivity_runEGLRenderLoop(J ILOG("After render loop."); g_gameInfoCache->WorkQueue()->Flush(); - NativeDeviceLost(); - ILOG("NativeDeviceLost completed."); - NativeShutdownGraphics(); renderer_inited = false; diff --git a/android/src/org/ppsspp/ppsspp/NativeActivity.java b/android/src/org/ppsspp/ppsspp/NativeActivity.java index 206de462aee8..305a02f87a0b 100644 --- a/android/src/org/ppsspp/ppsspp/NativeActivity.java +++ b/android/src/org/ppsspp/ppsspp/NativeActivity.java @@ -9,6 +9,7 @@ import android.annotation.SuppressLint; import android.annotation.TargetApi; import android.app.Activity; +import android.app.ActivityManager; import android.app.AlertDialog; import android.app.UiModeManager; import android.content.Context; @@ -16,9 +17,11 @@ import android.content.Intent; import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; +import android.content.pm.ConfigurationInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.Configuration; +import android.graphics.PixelFormat; import android.graphics.Point; import android.media.AudioManager; import android.net.Uri; @@ -38,6 +41,7 @@ import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceHolder; +import android.view.SurfaceView; import android.view.View; import android.view.View.OnSystemUiVisibilityChangeListener; import android.view.Window; @@ -56,11 +60,18 @@ public class NativeActivity extends Activity implements SurfaceHolder.Callback { // Allows us to skip a lot of initialization on secondary calls to onCreate. private static boolean initialized = false; - // Graphics and audio interfaces + // Change this to false to switch to C++ EGL. + private static boolean javaGL = true; + + // Graphics and audio interfaces for EGL (javaGL = false) private NativeSurfaceView mSurfaceView; private Surface mSurface; private Thread mRenderLoopThread = null; + // Graphics and audio interfaces for Java EGL (javaGL = true) + private NativeGLView mGLSurfaceView; + protected NativeRenderer nativeRenderer; + private String shortcutParam = ""; public static String runCommand; @@ -95,6 +106,10 @@ public boolean useLowProfileButtons() { return true; } + NativeRenderer getRenderer() { + return nativeRenderer; + } + @TargetApi(17) private void detectOptimalAudioSettings() { try { @@ -207,8 +222,13 @@ public void Initialize() { detectOptimalAudioSettings(); } - // Get system information + // isLandscape is used to trigger GetAppInfo currently, we + boolean landscape = NativeApp.isLandscape(); + Log.d(TAG, "Landscape: " + landscape); + + // Get system information ApplicationInfo appInfo = null; + PackageManager packMgmr = getPackageManager(); String packageName = getPackageName(); try { @@ -246,10 +266,28 @@ public void Initialize() { String languageRegion = Locale.getDefault().getLanguage() + "_" + Locale.getDefault().getCountry(); NativeApp.audioConfig(optimalFramesPerBuffer, optimalSampleRate); - NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, cacheDir, shortcutParam, Build.VERSION.SDK_INT); + NativeApp.init(model, deviceType, languageRegion, apkFilePath, dataDir, externalStorageDir, libraryDir, cacheDir, shortcutParam, Build.VERSION.SDK_INT, javaGL); sendInitialGrants(); + // OK, config should be initialized, we can query for screen rotation. + if (Build.VERSION.SDK_INT >= 9) { + updateScreenRotation(); + } + + // Detect OpenGL support. + // We don't currently use this detection for anything but good to have in the log. + if (!detectOpenGLES20()) { + Log.i(TAG, "OpenGL ES 2.0 NOT detected. Things will likely go badly."); + } else { + if (detectOpenGLES30()) { + Log.i(TAG, "OpenGL ES 3.0 detected."); + } + else { + Log.i(TAG, "OpenGL ES 2.0 detected."); + } + } + vibrator = (Vibrator)getSystemService(VIBRATOR_SERVICE); if (Build.VERSION.SDK_INT >= 11) { checkForVibrator(); @@ -352,6 +390,13 @@ void updateDisplayMetrics(Point outSize) { NativeApp.setDisplayParameters(outSize.x, outSize.y, metrics.densityDpi, refreshRate); } + public void getDesiredBackbufferSize(Point sz) { + NativeApp.computeDesiredBackbufferDimensions(); + sz.x = NativeApp.getDesiredBackbufferWidth(); + sz.y = NativeApp.getDesiredBackbufferHeight(); + } + + @SuppressWarnings("deprecation") @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -376,37 +421,78 @@ public void onCreate(Bundle savedInstanceState) { gainAudioFocus(this.audioManager, this.audioFocusChangeListener); NativeApp.audioInit(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - updateSystemUiVisibility(); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { - setupSystemUiCallback(); - } - } updateDisplayMetrics(null); + if (javaGL) { + mGLSurfaceView = new NativeGLView(this); + nativeRenderer = new NativeRenderer(this); + + Point sz = new Point(); + getDesiredBackbufferSize(sz); + if (sz.x > 0) { + Log.i(TAG, "Requesting fixed size buffer: " + sz.x + "x" + sz.y); + // Auto-calculates new DPI and forwards to the correct call on mGLSurfaceView.getHolder() + nativeRenderer.setFixedSize(sz.x, sz.y, mGLSurfaceView); + } + mGLSurfaceView.setEGLContextClientVersion(2); + + // Setup the GLSurface and ask android for the correct + // Number of bits for r, g, b, a, depth and stencil components + // The PSP only has 16-bit Z so that should be enough. + // Might want to change this for other apps (24-bit might be useful). + // Actually, we might be able to do without both stencil and depth in + // the back buffer, but that would kill non-buffered rendering. + + // It appears some gingerbread devices blow up if you use a config chooser at all ???? (Xperia Play) + //if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) { + + // On some (especially older devices), things blow up later (EGL_BAD_MATCH) if we don't set the format here, + // if we specify that we want destination alpha in the config chooser, which we do. + // http://grokbase.com/t/gg/android-developers/11bj40jm4w/fall-back + + // Needed to avoid banding on Ouya? + if (Build.MANUFACTURER == "OUYA") { + mGLSurfaceView.getHolder().setFormat(PixelFormat.RGBX_8888); + mGLSurfaceView.setEGLConfigChooser(new NativeEGLConfigChooser()); + } + // Tried to mess around with config choosers here but fail completely on Xperia Play. - NativeApp.computeDesiredBackbufferDimensions(); - int bbW = NativeApp.getDesiredBackbufferWidth(); - int bbH = NativeApp.getDesiredBackbufferHeight(); + mGLSurfaceView.setRenderer(nativeRenderer); + setContentView(mGLSurfaceView); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + updateSystemUiVisibility(); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { + setupSystemUiCallback(); + } + } - mSurfaceView = new NativeSurfaceView(NativeActivity.this, bbW, bbH); - mSurfaceView.getHolder().addCallback(NativeActivity.this); - Log.i(TAG, "setcontentview before"); - setContentView(mSurfaceView); - Log.i(TAG, "setcontentview after"); + NativeApp.computeDesiredBackbufferDimensions(); + int bbW = NativeApp.getDesiredBackbufferWidth(); + int bbH = NativeApp.getDesiredBackbufferHeight(); - ensureRenderLoop(); + mSurfaceView = new NativeSurfaceView(NativeActivity.this, bbW, bbH); + mSurfaceView.getHolder().addCallback(NativeActivity.this); + Log.i(TAG, "setcontentview before"); + setContentView(mSurfaceView); + Log.i(TAG, "setcontentview after"); + + ensureRenderLoop(); + } } @Override - public void surfaceCreated(SurfaceHolder holder) - { + public void surfaceCreated(SurfaceHolder holder) { Log.d(TAG, "Surface created."); } // @Override - public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) - { + public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) { + if (javaGL) { + Log.e(TAG, "JavaGL - should not get into surfaceChanged."); + return; + } + Log.w(TAG, "Surface changed. Resolution: " + width + "x" + height + " Format: " + format); // Make sure we have fresh display metrics so the computations go right. // This is needed on some very old devices, I guess event order is different or something... @@ -425,6 +511,10 @@ public void surfaceChanged(SurfaceHolder holder, int format, int width, int heig // Invariants: After this, mRenderLoopThread will be set, and the thread will be running. protected synchronized void ensureRenderLoop() { + if (javaGL) { + Log.e(TAG, "JavaGL - should not get into ensureRenderLoop."); + return; + } if (mSurface == null) { Log.w(TAG, "ensureRenderLoop - not starting thread, needs surface"); return; @@ -439,6 +529,11 @@ protected synchronized void ensureRenderLoop() { // Invariants: After this, mRenderLoopThread will be null, and the thread has exited. private synchronized void joinRenderLoopThread() { + if (javaGL) { + Log.e(TAG, "JavaGL - should not get into joinRenderLoopThread."); + return; + } + if (mRenderLoopThread != null) { // This will wait until the thread has exited. Log.i(TAG, "exitEGLRenderLoop"); @@ -457,12 +552,16 @@ private synchronized void joinRenderLoopThread() { @Override public void surfaceDestroyed(SurfaceHolder holder) { + if (javaGL) { + Log.e(TAG, "JavaGL - should not get into surfaceDestroyed."); + return; + } + mSurface = null; Log.w(TAG, "Surface destroyed."); joinRenderLoopThread(); } - @TargetApi(19) void setupSystemUiCallback() { getWindow().getDecorView().setOnSystemUiVisibilityChangeListener(new OnSystemUiVisibilityChangeListener() { @@ -484,15 +583,17 @@ protected void onStop() { @Override protected void onDestroy() { super.onDestroy(); - Log.i(TAG, "onDestroy"); - mSurfaceView.onDestroy(); - NativeApp.audioShutdown(); - // Probably vain attempt to help the garbage collector... - mSurfaceView = null; - audioFocusChangeListener = null; - audioManager = null; - unregisterCallbacks(); - + if (javaGL) { + Log.i(TAG, "onDestroy"); + mGLSurfaceView.onDestroy(); + nativeRenderer.onDestroyed(); + NativeApp.audioShutdown(); + // Probably vain attempt to help the garbage collector... + mGLSurfaceView = null; + audioFocusChangeListener = null; + audioManager = null; + unregisterCallbacks(); + } if (shuttingDown) { NativeApp.shutdown(); } @@ -503,14 +604,35 @@ protected void onPause() { super.onPause(); Log.i(TAG, "onPause"); loseAudioFocus(this.audioManager, this.audioFocusChangeListener); - Log.i(TAG, "Pausing surface view"); NativeApp.pause(); - mSurfaceView.onPause(); - Log.i(TAG, "Joining render thread"); - joinRenderLoopThread(); + if (!javaGL) { + Log.i(TAG, "Pausing surface view"); + mSurfaceView.onPause(); + Log.i(TAG, "Joining render thread"); + joinRenderLoopThread(); + } else { + if (mGLSurfaceView != null) { + mGLSurfaceView.onPause(); + } else { + Log.e(TAG, "mGLSurfaceView really shouldn't be null in onPause"); + } + } Log.i(TAG, "onPause completed"); + } + + private boolean detectOpenGLES20() { + ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo info = am.getDeviceConfigurationInfo(); + return info.reqGlEsVersion >= 0x20000; } + private boolean detectOpenGLES30() { + ActivityManager am = (ActivityManager) getSystemService(Context.ACTIVITY_SERVICE); + ConfigurationInfo info = am.getDeviceConfigurationInfo(); + return info.reqGlEsVersion >= 0x30000; + } + + @Override protected void onResume() { super.onResume(); @@ -518,20 +640,26 @@ protected void onResume() { updateSystemUiVisibility(); } // OK, config should be initialized, we can query for screen rotation. - updateScreenRotation(); + if (javaGL || Build.VERSION.SDK_INT >= 9) { + updateScreenRotation(); + } Log.i(TAG, "onResume"); - if (mSurfaceView != null) { - mSurfaceView.onResume(); - } else { - Log.e(TAG, "mGLSurfaceView really shouldn't be null in onResume"); + if (javaGL) { + if (mGLSurfaceView != null) { + mGLSurfaceView.onResume(); + } else { + Log.e(TAG, "mGLSurfaceView really shouldn't be null in onResume"); + } } gainAudioFocus(this.audioManager, this.audioFocusChangeListener); NativeApp.resume(); - // Restart the render loop. - ensureRenderLoop(); + if (!javaGL) { + // Restart the render loop. + ensureRenderLoop(); + } } @Override @@ -542,6 +670,13 @@ public void onConfigurationChanged(Configuration newConfig) { updateSystemUiVisibility(); } updateDisplayMetrics(null); + if (javaGL) { + Point sz = new Point(); + getDesiredBackbufferSize(sz); + if (sz.x > 0) { + mGLSurfaceView.getHolder().setFixedSize(sz.x/2, sz.y/2); + } + } } //keep this static so we can call this even if we don't @@ -775,7 +910,6 @@ public boolean onKeyUp(int keyCode, KeyEvent event) { } } - @TargetApi(11) @SuppressWarnings("deprecation") private AlertDialog.Builder createDialogBuilderWithTheme() { @@ -823,14 +957,14 @@ else if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) .setTitle(title) .setPositiveButton(defaultAction, new DialogInterface.OnClickListener(){ @Override - public void onClick(DialogInterface d, int which) { - NativeApp.sendMessage("inputbox_completed", title + ":" + input.getText().toString()); + public void onClick(DialogInterface d, int which) { + NativeApp.sendMessage("inputbox_completed", input.getText().toString()); d.dismiss(); } }) .setNegativeButton("Cancel", new DialogInterface.OnClickListener(){ @Override - public void onClick(DialogInterface d, int which) { + public void onClick(DialogInterface d, int which) { NativeApp.sendMessage("inputbox_failed", ""); d.cancel(); } @@ -841,6 +975,7 @@ public void onClick(DialogInterface d, int which) { } public boolean processCommand(String command, String params) { + SurfaceView surfView = javaGL ? mGLSurfaceView : mSurfaceView; if (command.equals("launchBrowser")) { try { Intent i = new Intent(Intent.ACTION_VIEW, Uri.parse(params)); @@ -916,18 +1051,18 @@ public boolean processCommand(String command, String params) { toast.show(); Log.i(TAG, params); return true; - } else if (command.equals("showKeyboard") && mSurfaceView != null) { + } else if (command.equals("showKeyboard") && surfView != null) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); // No idea what the point of the ApplicationWindowToken is or if it // matters where we get it from... inputMethodManager.toggleSoftInputFromWindow( - mSurfaceView.getApplicationWindowToken(), + surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; - } else if (command.equals("hideKeyboard") && mSurfaceView != null) { + } else if (command.equals("hideKeyboard") && surfView != null) { InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE); inputMethodManager.toggleSoftInputFromWindow( - mSurfaceView.getApplicationWindowToken(), + surfView.getApplicationWindowToken(), InputMethodManager.SHOW_FORCED, 0); return true; } else if (command.equals("inputbox")) { @@ -941,7 +1076,7 @@ public boolean processCommand(String command, String params) { Log.i(TAG, "Launching inputbox: " + title + " " + defString); inputBox(title, defString, "OK"); return true; - } else if (command.equals("vibrate") && mSurfaceView != null) { + } else if (command.equals("vibrate") && surfView != null) { int milliseconds = -1; if (params != "") { try { @@ -958,13 +1093,13 @@ public boolean processCommand(String command, String params) { // permission. switch (milliseconds) { case -1: - mSurfaceView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); + surfView.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP); break; case -2: - mSurfaceView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); + surfView.performHapticFeedback(HapticFeedbackConstants.VIRTUAL_KEY); break; case -3: - mSurfaceView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); + surfView.performHapticFeedback(HapticFeedbackConstants.LONG_PRESS); break; default: if (vibrator != null) { @@ -978,16 +1113,21 @@ public boolean processCommand(String command, String params) { shuttingDown = true; finish(); } else if (command.equals("rotate")) { - updateScreenRotation(); - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { - Log.i(TAG, "Must recreate activity on rotation"); - } + if (javaGL) { + updateScreenRotation(); + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + Log.i(TAG, "Must recreate activity on rotation"); + } + } else { + if (Build.VERSION.SDK_INT >= 9) { + updateScreenRotation(); + } + } } else if (command.equals("immersive")) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { updateSystemUiVisibility(); } } else if (command.equals("recreate")) { - exitEGLRenderLoop(); recreate(); } else if (command.equals("ask_permission") && params.equals("storage")) { askForStoragePermission(); diff --git a/android/src/org/ppsspp/ppsspp/NativeApp.java b/android/src/org/ppsspp/ppsspp/NativeApp.java index 6d9d686aa5a5..8b00692cfdfc 100644 --- a/android/src/org/ppsspp/ppsspp/NativeApp.java +++ b/android/src/org/ppsspp/ppsspp/NativeApp.java @@ -13,8 +13,7 @@ public class NativeApp { public final static int DEVICE_TYPE_TV = 1; public final static int DEVICE_TYPE_DESKTOP = 2; - public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String cacheDir, String shortcutParam, int androidVersion); - + public static native void init(String model, int deviceType, String languageRegion, String apkPath, String dataDir, String externalDir, String libraryDir, String cacheDir, String shortcutParam, int androidVersion, boolean javaGL); public static native void audioInit(); public static native void audioShutdown(); public static native void audioConfig(int optimalFramesPerBuffer, int optimalSampleRate); @@ -57,3 +56,4 @@ public class NativeApp { public static native String queryConfig(String queryName); } + diff --git a/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java b/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java new file mode 100644 index 000000000000..329e734ebae7 --- /dev/null +++ b/android/src/org/ppsspp/ppsspp/NativeEGLConfigChooser.java @@ -0,0 +1,213 @@ +package org.ppsspp.ppsspp; + +import javax.microedition.khronos.egl.EGL10; +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.egl.EGLDisplay; + +import android.graphics.PixelFormat; +import android.opengl.GLSurfaceView.EGLConfigChooser; +import android.util.Log; + +public class NativeEGLConfigChooser implements EGLConfigChooser { + private static final String TAG = "NativeEGLConfigChooser"; + + private static final int EGL_OPENGL_ES2_BIT = 4; + + NativeEGLConfigChooser() { + } + + private class ConfigAttribs { + EGLConfig config; + public int red; + public int green; + public int blue; + public int alpha; + public int stencil; + public int depth; + public int samples; + public void Log() { + Log.i(TAG, "EGLConfig: red=" + red + " green=" + green + " blue=" + blue + " alpha=" + alpha + " depth=" + depth + " stencil=" + stencil + " samples=" + samples); + } + } + + int getEglConfigAttrib(EGL10 egl, EGLDisplay display, EGLConfig config, int attr) { + int[] value = new int[1]; + try { + if (egl.eglGetConfigAttrib(display, config, attr, value)) + return value[0]; + else + return -1; + } catch (IllegalArgumentException e) { + if (config == null) { + Log.e(TAG, "Called getEglConfigAttrib with null config. Bad developer."); + } else { + Log.e(TAG, "Illegal argument to getEglConfigAttrib: attr=" + attr); + } + return -1; + } + } + + ConfigAttribs[] getConfigAttribs(EGL10 egl, EGLDisplay display, EGLConfig[] configs) { + ConfigAttribs[] attr = new ConfigAttribs[configs.length]; + for (int i = 0; i < configs.length; i++) { + ConfigAttribs cfg = new ConfigAttribs(); + cfg.config = configs[i]; + cfg.red = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_RED_SIZE); + cfg.green = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_GREEN_SIZE); + cfg.blue = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_BLUE_SIZE); + cfg.alpha = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_ALPHA_SIZE); + cfg.depth = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_DEPTH_SIZE); + cfg.stencil = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_STENCIL_SIZE); + cfg.samples = getEglConfigAttrib(egl, display, configs[i], EGL10.EGL_SAMPLES); + attr[i] = cfg; + } + return attr; + } + + @Override + public EGLConfig chooseConfig(EGL10 egl, EGLDisplay display) { + // The absolute minimum. We will do our best to choose a better config though. + int[] configSpec = { + EGL10.EGL_RED_SIZE, 5, + EGL10.EGL_GREEN_SIZE, 6, + EGL10.EGL_BLUE_SIZE, 5, + EGL10.EGL_DEPTH_SIZE, 16, + EGL10.EGL_STENCIL_SIZE, 0, + EGL10.EGL_SURFACE_TYPE, EGL10.EGL_WINDOW_BIT, + EGL10.EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, + // EGL10.EGL_TRANSPARENT_TYPE, EGL10.EGL_NONE + EGL10.EGL_NONE + }; + + int[] num_config = new int[1]; + if (!egl.eglChooseConfig(display, configSpec, null, 0, num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed when counting"); + } + + int numConfigs = num_config[0]; + Log.i(TAG, "There are " + numConfigs + " egl configs"); + if (numConfigs <= 0) { + throw new IllegalArgumentException("No configs match configSpec"); + } + + EGLConfig[] eglConfigs = new EGLConfig[numConfigs]; + if (!egl.eglChooseConfig(display, configSpec, eglConfigs, numConfigs, num_config)) { + throw new IllegalArgumentException("eglChooseConfig failed when retrieving"); + } + + ConfigAttribs [] configs = getConfigAttribs(egl, display, eglConfigs); + + ConfigAttribs chosen = null; + + // Log them all. + for (int i = 0; i < configs.length; i++) { + configs[i].Log(); + } + + + // We now ignore destination alpha as a workaround for the Mali issue + // where we get badly composited if we use it. + // Though, that may be possible to fix by using EGL10.EGL_TRANSPARENT_TYPE, EGL10.EGL_NONE. + + // First, find our ideal configuration. Prefer depth. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.stencil >= 8 && c.depth >= 24) { + chosen = c; + break; + } + } + + if (chosen == null) { + // Then, prefer one with 20-bit depth (Tegra 3) + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.stencil >= 8 && c.depth >= 20) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Second, accept one with 16-bit depth. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.stencil >= 8 && c.depth >= 16) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Third, accept one with no stencil. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 0 && c.depth >= 16) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Third, accept one with alpha but with stencil, 24-bit depth. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 8 && c.stencil >= 8 && c.depth >= 24) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Third, accept one with alpha but with stencil, 16-bit depth. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red == 8 && c.green == 8 && c.blue == 8 && c.alpha == 8 && c.stencil >= 8 && c.depth >= 16) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Fourth, accept one with 16-bit color but depth and stencil required. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red >= 5 && c.green >= 6 && c.blue >= 5 && c.depth >= 16 && c.stencil >= 8) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Fifth, accept one with 16-bit color but depth required. + for (int i = 0; i < configs.length; i++) { + ConfigAttribs c = configs[i]; + if (c.red >= 5 && c.green >= 6 && c.blue >= 5 && c.depth >= 16) { + chosen = c; + break; + } + } + } + + if (chosen == null) { + // Final, accept the first one in the list. + if (configs.length > 0) + chosen = configs[0]; + } + + if (chosen == null) { + throw new IllegalArgumentException("Failed to find a valid EGL config"); + } + + Log.i(TAG, "Final chosen config: "); + chosen.Log(); + return chosen.config; + } + +} diff --git a/android/src/org/ppsspp/ppsspp/NativeGLView.java b/android/src/org/ppsspp/ppsspp/NativeGLView.java new file mode 100644 index 000000000000..0581ef57fe1c --- /dev/null +++ b/android/src/org/ppsspp/ppsspp/NativeGLView.java @@ -0,0 +1,243 @@ +package org.ppsspp.ppsspp; + +// Touch- and sensor-enabled GLSurfaceView. +// Used when javaGL = true. +// +// Supports simple multitouch and pressure. +// DPI scaling is handled by the native code. + +import android.annotation.SuppressLint; +import android.annotation.TargetApi; +import android.app.Activity; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; +import android.opengl.GLSurfaceView; +import android.os.Build; +import android.os.Handler; +import android.util.Log; +import android.view.MotionEvent; +import com.bda.controller.*; + +public class NativeGLView extends GLSurfaceView implements SensorEventListener, ControllerListener { + private static String TAG = "NativeGLView"; + private SensorManager mSensorManager; + private Sensor mAccelerometer; + + // Moga controller + private Controller mController = null; + private boolean isMogaPro = false; + + NativeActivity mActivity; + + public NativeGLView(NativeActivity activity) { + super(activity); + mActivity = activity; + + /*// TODO: This would be nice. + if (Build.VERSION.SDK_INT >= 11) { + try { + Method method_setPreserveEGLContextOnPause = GLSurfaceView.class.getMethod( + "setPreserveEGLContextOnPause", new Class[] { Boolean.class }); + Log.i(TAG, "Invoking setPreserveEGLContextOnPause"); + method_setPreserveEGLContextOnPause.invoke(this, true); + } catch (NoSuchMethodException e) { + e.printStackTrace(); + } catch (IllegalArgumentException e) { + e.printStackTrace(); + } catch (IllegalAccessException e) { + e.printStackTrace(); + } catch (InvocationTargetException e) { + e.printStackTrace(); + } + }*/ + + mSensorManager = (SensorManager)activity.getSystemService(Activity.SENSOR_SERVICE); + mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); + + mController = Controller.getInstance(activity); + try { + MogaHack.init(mController, activity); + Log.i(TAG, "MOGA initialized"); + mController.setListener(this, new Handler()); + } catch (Exception e) { + Log.i(TAG, "Moga failed to initialize"); + } + } + + @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) + private int getToolType(final MotionEvent ev, int pointer) { + return ev.getToolType(pointer); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouchEvent(final MotionEvent ev) { + boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; + + int numTouchesHandled = 0; + for (int i = 0; i < ev.getPointerCount(); i++) { + int pid = ev.getPointerId(i); + int code = 0; + + final int action = ev.getActionMasked(); + + // These code bits are now the same as the constants in input_state.h. + switch (action) { + case MotionEvent.ACTION_DOWN: + case MotionEvent.ACTION_POINTER_DOWN: + if (ev.getActionIndex() == i) + code = 2; + break; + case MotionEvent.ACTION_UP: + case MotionEvent.ACTION_POINTER_UP: + if (ev.getActionIndex() == i) + code = 4; + break; + case MotionEvent.ACTION_MOVE: + code = 1; + break; + default: + break; + } + + if (code != 0) { + if (canReadToolType) { + int tool = getToolType(ev, i); + code |= tool << 10; // We use the Android tool type codes + } + // Can't use || due to short circuit evaluation + numTouchesHandled += NativeApp.touch(ev.getX(i), ev.getY(i), code, pid) ? 1 : 0; + } + } + return numTouchesHandled > 0; + } + + // Sensor management + @Override + public void onAccuracyChanged(Sensor sensor, int arg1) { + } + + @Override + public void onSensorChanged(SensorEvent event) { + if (event.sensor.getType() != Sensor.TYPE_ACCELEROMETER) { + return; + } + // Can also look at event.timestamp for accuracy magic + NativeApp.accelerometer(event.values[0], event.values[1], event.values[2]); + } + + @Override + public void onPause() { + Log.i(TAG, "onPause"); + super.onPause(); + mSensorManager.unregisterListener(this); + if (mController != null) { + mController.onPause(); + } + } + + @Override + public void onResume() { + Log.i(TAG, "onResume"); + super.onResume(); + mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_GAME); + if (mController != null) { + mController.onResume(); + + // According to the docs, the Moga's state can be inconsistent here. + // We should do a one time poll. TODO + } + } + + public void onDestroy() { + Log.i(TAG, "onDestroy"); + if (mController != null) { + mController.exit(); + } + } + + // MOGA Controller - from ControllerListener + @Override + public void onKeyEvent(KeyEvent event) { + // The Moga left stick doubles as a D-pad. This creates mapping conflicts so let's turn it off. + // Unfortunately this breaks menu navigation in PPSSPP currently but meh. + // This is different on Moga Pro though. + + if (!isMogaPro) { + switch (event.getKeyCode()) { + case KeyEvent.KEYCODE_DPAD_DOWN: + case KeyEvent.KEYCODE_DPAD_UP: + case KeyEvent.KEYCODE_DPAD_LEFT: + case KeyEvent.KEYCODE_DPAD_RIGHT: + return; + default: + break; + } + } + + boolean repeat = false; // Moga has no repeats? + switch (event.getAction()) { + case KeyEvent.ACTION_DOWN: + NativeApp.keyDown(NativeApp.DEVICE_ID_PAD_0, event.getKeyCode(), repeat); + break; + case KeyEvent.ACTION_UP: + NativeApp.keyUp(NativeApp.DEVICE_ID_PAD_0, event.getKeyCode()); + break; + } + } + + // MOGA Controller - from ControllerListener + @Override + public void onMotionEvent(com.bda.controller.MotionEvent event) { + NativeApp.joystickAxis(NativeApp.DEVICE_ID_PAD_0, com.bda.controller.MotionEvent.AXIS_X, event.getAxisValue(com.bda.controller.MotionEvent.AXIS_X)); + NativeApp.joystickAxis(NativeApp.DEVICE_ID_PAD_0, com.bda.controller.MotionEvent.AXIS_Y, event.getAxisValue(com.bda.controller.MotionEvent.AXIS_Y)); + NativeApp.joystickAxis(NativeApp.DEVICE_ID_PAD_0, com.bda.controller.MotionEvent.AXIS_Z, event.getAxisValue(com.bda.controller.MotionEvent.AXIS_Z)); + NativeApp.joystickAxis(NativeApp.DEVICE_ID_PAD_0, com.bda.controller.MotionEvent.AXIS_RZ, event.getAxisValue(com.bda.controller.MotionEvent.AXIS_RZ)); + NativeApp.joystickAxis(NativeApp.DEVICE_ID_PAD_0, com.bda.controller.MotionEvent.AXIS_LTRIGGER, event.getAxisValue(com.bda.controller.MotionEvent.AXIS_LTRIGGER)); + NativeApp.joystickAxis(NativeApp.DEVICE_ID_PAD_0, com.bda.controller.MotionEvent.AXIS_RTRIGGER, event.getAxisValue(com.bda.controller.MotionEvent.AXIS_RTRIGGER)); + } + + // MOGA Controller - from ControllerListener + @Override + public void onStateEvent(StateEvent state) { + switch (state.getState()) { + case StateEvent.STATE_CONNECTION: + switch (state.getAction()) { + case StateEvent.ACTION_CONNECTED: + Log.i(TAG, "Moga Connected"); + if (mController.getState(Controller.STATE_CURRENT_PRODUCT_VERSION) == Controller.ACTION_VERSION_MOGA) { + NativeApp.sendMessage("moga", "Moga"); + } else { + Log.i(TAG, "MOGA Pro detected"); + isMogaPro = true; + NativeApp.sendMessage("moga", "MogaPro"); + } + break; + case StateEvent.ACTION_CONNECTING: + Log.i(TAG, "Moga Connecting..."); + break; + case StateEvent.ACTION_DISCONNECTED: + Log.i(TAG, "Moga Disconnected (or simply Not connected)"); + NativeApp.sendMessage("moga", ""); + break; + } + break; + + case StateEvent.STATE_POWER_LOW: + switch (state.getAction()) { + case StateEvent.ACTION_TRUE: + Log.i(TAG, "Moga Power Low"); + break; + case StateEvent.ACTION_FALSE: + Log.i(TAG, "Moga Power OK"); + break; + } + break; + + default: + break; + } + } +} diff --git a/android/src/org/ppsspp/ppsspp/NativeRenderer.java b/android/src/org/ppsspp/ppsspp/NativeRenderer.java new file mode 100644 index 000000000000..ea2a0219586d --- /dev/null +++ b/android/src/org/ppsspp/ppsspp/NativeRenderer.java @@ -0,0 +1,94 @@ +package org.ppsspp.ppsspp; + +import javax.microedition.khronos.egl.EGLConfig; +import javax.microedition.khronos.opengles.GL10; + +import android.graphics.Point; +import android.opengl.GLES20; +import android.opengl.GLSurfaceView; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.Display; + +public class NativeRenderer implements GLSurfaceView.Renderer { + private static String TAG = "NativeRenderer"; + private NativeActivity mActivity; + private boolean isDark = false; + private int dpi; + private float refreshRate; + + private double dpi_scale_x; + private double dpi_scale_y; + + int last_width, last_height; + + NativeRenderer(NativeActivity act) { + mActivity = act; + DisplayMetrics metrics = new DisplayMetrics(); + Display display = act.getWindowManager().getDefaultDisplay(); + display.getMetrics(metrics); + dpi = metrics.densityDpi; + + refreshRate = display.getRefreshRate(); + } + + double getDpiScaleX() { + return dpi_scale_x; + } + double getDpiScaleY() { + return dpi_scale_y; + } + + public void setDark(boolean d) { + isDark = d; + } + + public void setFixedSize(int xres, int yres, GLSurfaceView surfaceView) { + Log.i(TAG, "Setting surface to fixed size " + xres + "x" + yres); + surfaceView.getHolder().setFixedSize(xres, yres); + } + + public void onDrawFrame(GL10 unused /*use GLES20*/) { + if (isDark) { + GLES20.glDisable(GLES20.GL_SCISSOR_TEST); + GLES20.glClearColor(0.0f, 0.0f, 0.0f, 0.0f); + GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT | GLES20.GL_STENCIL_BUFFER_BIT); + } else { + displayRender(); + } + } + + public void onSurfaceCreated(GL10 unused, EGLConfig config) { + // Log.i(TAG, "onSurfaceCreated - EGL context is new or was lost"); + // Actually, it seems that it is here we should recreate lost GL objects. + displayInit(); + } + + public void onSurfaceChanged(GL10 unused, int width, int height) { + Point sz = new Point(); + mActivity.GetScreenSize(sz); + double actualW = sz.x; + double actualH = sz.y; + dpi_scale_x = ((double)width / (double)actualW); + dpi_scale_y = ((double)height / (double)actualH); + Log.i(TAG, "onSurfaceChanged: " + dpi_scale_x + "x" + dpi_scale_y + " (width=" + width + ", actualW=" + actualW); + int scaled_dpi = (int)((double)dpi * dpi_scale_x); + displayResize(width, height, scaled_dpi, refreshRate); + last_width = width; + last_height = height; + } + + // Not override, it's custom. + public void onDestroyed() { + displayShutdown(); + } + + // NATIVE METHODS + + // Note: This also means "device lost" and you should reload + // all buffered objects. + public native void displayInit(); + public native void displayResize(int w, int h, int dpi, float refreshRate); + public native void displayRender(); + public native void displayShutdown(); +} \ No newline at end of file diff --git a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java index b0dd93a3180e..9e74c5a3a769 100644 --- a/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java +++ b/android/src/org/ppsspp/ppsspp/NativeSurfaceView.java @@ -13,6 +13,8 @@ import android.hardware.SensorManager; import android.os.Build; import android.os.Handler; +// import android.os.Build; +// import android.util.Log; import android.util.Log; import android.view.MotionEvent; import android.view.SurfaceView; @@ -68,6 +70,8 @@ public boolean onTouchEvent(final MotionEvent ev) { boolean canReadToolType = Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH; int numTouchesHandled = 0; + //float scaleX = (float)mActivity.getRenderer().getDpiScaleX(); + //float scaleY = (float)mActivity.getRenderer().getDpiScaleY(); for (int i = 0; i < ev.getPointerCount(); i++) { int pid = ev.getPointerId(i); int code = 0; diff --git a/android/src/org/ppsspp/ppsspp/PpssppActivity.java b/android/src/org/ppsspp/ppsspp/PpssppActivity.java index 6975c5fbf17e..7073cb86186c 100644 --- a/android/src/org/ppsspp/ppsspp/PpssppActivity.java +++ b/android/src/org/ppsspp/ppsspp/PpssppActivity.java @@ -2,6 +2,7 @@ import android.app.AlertDialog; import android.content.Intent; +import android.graphics.Point; import android.os.Build; import android.os.Bundle; import android.os.Looper; @@ -72,11 +73,11 @@ public void run() { // (from app drawer or file explorer). Intent intent = getIntent(); String action = intent.getAction(); - if(intent.ACTION_VIEW.equals(action)) - { + if(Intent.ACTION_VIEW.equals(action)) + { String path = intent.getData().getPath(); super.setShortcutParam(path); - Toast.makeText(getApplicationContext(), path, Toast.LENGTH_SHORT).show(); + Toast.makeText(getApplicationContext(), path, Toast.LENGTH_SHORT).show(); } else super.setShortcutParam(getIntent().getStringExtra(SHORTCUT_EXTRA_KEY)); @@ -95,4 +96,4 @@ public void run() { } }); } -} \ No newline at end of file +} diff --git a/ext/native/base/NativeApp.h b/ext/native/base/NativeApp.h index a4b6e9e1e83d..b9de74d0952c 100644 --- a/ext/native/base/NativeApp.h +++ b/ext/native/base/NativeApp.h @@ -52,10 +52,13 @@ void NativeInit(int argc, const char *argv[], const char *savegame_dir, const ch // Should not initialize anything screen-size-dependent - do that in NativeResized. void NativeInitGraphics(GraphicsContext *graphicsContext); -// Signals that you need to destroy and recreate all buffered OpenGL resources, +// Signals that you need to forget all buffered OpenGL resources, // like textures, vbo etc. void NativeDeviceLost(); +// Signals that it's time to recreate buffered OpenGL resources +void NativeDeviceRestore(); + // If you want to change DPI stuff (such as modifying dp_xres and dp_yres), this is the // place to do it. You should only read g_dpi_scale and pixel_xres and pixel_yres in this, // and only write dp_xres and dp_yres. diff --git a/ext/native/gfx/gl_lost_manager.cpp b/ext/native/gfx/gl_lost_manager.cpp index 0462f2e89052..3057b37ccb01 100644 --- a/ext/native/gfx/gl_lost_manager.cpp +++ b/ext/native/gfx/gl_lost_manager.cpp @@ -7,10 +7,11 @@ std::vector *holders; static bool inLost; +static bool inRestore; void register_gl_resource_holder(GfxResourceHolder *holder) { - if (inLost) { - FLOG("BAD: Should not call register_gl_resource_holder from lost path"); + if (inLost || inRestore) { + FLOG("BAD: Should not call register_gl_resource_holder from lost/restore path"); return; } if (holders) { @@ -21,8 +22,8 @@ void register_gl_resource_holder(GfxResourceHolder *holder) { } void unregister_gl_resource_holder(GfxResourceHolder *holder) { - if (inLost) { - FLOG("BAD: Should not call unregister_gl_resource_holder from lost path"); + if (inLost || inRestore) { + FLOG("BAD: Should not call unregister_gl_resource_holder from lost/restore path"); return; } if (holders) { @@ -38,22 +39,22 @@ void unregister_gl_resource_holder(GfxResourceHolder *holder) { } } -void gl_lost() { - inLost = true; +void gl_restore() { + inRestore = true; if (!holders) { - WLOG("GL resource holder not initialized, cannot process lost request"); - inLost = false; + WLOG("GL resource holder not initialized, cannot process restore request"); + inRestore = false; return; } // TODO: We should really do this when we get the context back, not during gl_lost... - ILOG("gl_lost() restoring %i items:", (int)holders->size()); + ILOG("gl_restore() restoring %i items:", (int)holders->size()); for (size_t i = 0; i < holders->size(); i++) { - ILOG("GLLost(%i / %i, %p, %08x)", (int)(i + 1), (int) holders->size(), (*holders)[i], *((uint32_t *)((*holders)[i]))); - (*holders)[i]->GLLost(); + ILOG("gl_restore(%i / %i, %p, %08x)", (int)(i + 1), (int)holders->size(), (*holders)[i], *((uint32_t *)((*holders)[i]))); + (*holders)[i]->GLRestore(); } - ILOG("gl_lost() completed on %i items:", (int)holders->size()); - inLost = false; + ILOG("gl_restore() completed on %i items:", (int)holders->size()); + inRestore = false; } void gl_lost_manager_init() { diff --git a/ext/native/gfx/gl_lost_manager.h b/ext/native/gfx/gl_lost_manager.h index 3d26ec3f3cdf..29ade49d6634 100644 --- a/ext/native/gfx/gl_lost_manager.h +++ b/ext/native/gfx/gl_lost_manager.h @@ -6,7 +6,7 @@ class GfxResourceHolder { public: virtual ~GfxResourceHolder() {} - virtual void GLLost() = 0; + virtual void GLRestore() = 0; }; void gl_lost_manager_init(); @@ -15,5 +15,5 @@ void gl_lost_manager_shutdown(); void register_gl_resource_holder(GfxResourceHolder *holder); void unregister_gl_resource_holder(GfxResourceHolder *holder); -// Notifies all objects about the loss. -void gl_lost(); +// Notifies all objects that it's time to be restored. +void gl_restore(); diff --git a/ext/native/gfx_es2/glsl_program.cpp b/ext/native/gfx_es2/glsl_program.cpp index c5e534adaa8c..2616a27a5c0d 100644 --- a/ext/native/gfx_es2/glsl_program.cpp +++ b/ext/native/gfx_es2/glsl_program.cpp @@ -220,7 +220,7 @@ bool glsl_recompile(GLSLProgram *program, std::string *error_message) { return true; } -void GLSLProgram::GLLost() { +void GLSLProgram::GLRestore() { // Quoth http://developer.android.com/reference/android/opengl/GLSurfaceView.Renderer.html; // "Note that when the EGL context is lost, all OpenGL resources associated with that context will be automatically deleted. // You do not need to call the corresponding "glDelete" methods such as glDeleteTextures to manually delete these lost resources." @@ -228,17 +228,16 @@ void GLSLProgram::GLLost() { // glDeleteShader(this->vsh_); // glDeleteShader(this->fsh_); // glDeleteProgram(this->program_); - ILOG("Restoring GLSL program %s/%s", - strlen(this->vshader_filename) > 0 ? this->vshader_filename : "(mem)", - strlen(this->fshader_filename) > 0 ? this->fshader_filename : "(mem)"); this->program_ = 0; this->vsh_ = 0; this->fsh_ = 0; + ILOG("Restoring GLSL program %s/%s", + strlen(this->vshader_filename) > 0 ? this->vshader_filename : "(mem)", + strlen(this->fshader_filename) > 0 ? this->fshader_filename : "(mem)"); glsl_recompile(this); - // Note that uniforms are still lost, hopefully the client sets them every frame at a minimum... + // Note that any shader uniforms are still lost, hopefully the client sets them every frame at a minimum... } - int glsl_attrib_loc(const GLSLProgram *program, const char *name) { return glGetAttribLocation(program->program_, name); } diff --git a/ext/native/gfx_es2/glsl_program.h b/ext/native/gfx_es2/glsl_program.h index cb4861aa81c5..7a0dad0801a3 100644 --- a/ext/native/gfx_es2/glsl_program.h +++ b/ext/native/gfx_es2/glsl_program.h @@ -43,7 +43,7 @@ struct GLSLProgram : public GfxResourceHolder { GLuint fsh_; GLuint program_; - void GLLost(); + void GLRestore() override; }; // C API, old skool. Not much point either... diff --git a/ext/native/thin3d/thin3d_gl.cpp b/ext/native/thin3d/thin3d_gl.cpp index 04330a2c96e5..0bcf1ccfec08 100644 --- a/ext/native/thin3d/thin3d_gl.cpp +++ b/ext/native/thin3d/thin3d_gl.cpp @@ -164,7 +164,7 @@ class Thin3DGLBuffer : public Thin3DBuffer, GfxResourceHolder { glBindBuffer(target_, buffer_); } - void GLLost() override { + void GLRestore() override { ILOG("Recreating vertex buffer after glLost"); knownSize_ = 0; // Will cause a new glBufferData call. Should genBuffers again though? glGenBuffers(1, &buffer_); @@ -239,7 +239,7 @@ class Thin3DGLVertexFormat : public Thin3DVertexFormat, GfxResourceHolder { void Apply(const void *base = nullptr); void Unapply(); void Compile(); - void GLLost() override; + void GLRestore() override; bool RequiresBuffer() override { return id_ != 0; } @@ -280,7 +280,7 @@ class Thin3DGLShaderSet : public Thin3DShaderSet, GfxResourceHolder { void SetVector(const char *name, float *value, int n) override; void SetMatrix4x4(const char *name, const Matrix4x4 &value) override; - void GLLost() override { + void GLRestore() override { vshader->Compile(vshader->GetSource().c_str()); fshader->Compile(fshader->GetSource().c_str()); Link(); @@ -466,13 +466,22 @@ class Thin3DGLTexture : public Thin3DTexture, GfxResourceHolder { glBindTexture(target_, tex_); } - void GLLost() override { - // We lost our GL context - zero out the tex_. + void GLRestore() override { + // We can assume that the texture is gone. tex_ = 0; generatedMips_ = false; - // Don't try to restore stuff, that's not what this is for. Lost just means - // that all our textures and buffers are invalid. + if (!filename_.empty()) { + if (LoadFromFile(filename_.c_str())) { + ILOG("Reloaded lost texture %s", filename_.c_str()); + } else { + ELOG("Failed to reload lost texture %s", filename_.c_str()); + } + } else { + WLOG("Texture %p cannot be restored - has no filename", this); + tex_ = 0; + } } + void Finalize(int zim_flags) override; private: @@ -573,7 +582,7 @@ void Thin3DGLVertexFormat::Compile() { lastBase_ = -1; } -void Thin3DGLVertexFormat::GLLost() { +void Thin3DGLVertexFormat::GLRestore() { Compile(); } diff --git a/ext/native/ui/ui_context.cpp b/ext/native/ui/ui_context.cpp index d2c2d319b66c..c81228808671 100644 --- a/ext/native/ui/ui_context.cpp +++ b/ext/native/ui/ui_context.cpp @@ -120,7 +120,7 @@ void UIContext::SetFontStyle(const UI::FontStyle &fontStyle) { *fontStyle_ = fontStyle; if (textDrawer_) { textDrawer_->SetFontScale(fontScaleX_, fontScaleY_); - Text()->SetFont(fontStyle.fontName.c_str(), fontStyle.sizePts, fontStyle.flags); + textDrawer_->SetFont(fontStyle.fontName.c_str(), fontStyle.sizePts, fontStyle.flags); } }