From cea0d20725e83bdee49d08343ee88ac2b3eb469c Mon Sep 17 00:00:00 2001 From: "Matias N. Goldberg" Date: Mon, 15 Apr 2024 16:54:17 -0300 Subject: [PATCH] [Android] Show Android sample caching if Swappy is buggy Fix wrong logic determining if Swappy is buggy Loosened up the thresholds a little bit Android Sample now uses a Kotlin activity Android Sample shows how to deal with devices that do not support Vulkan --- .../Android/OgreVulkanAndroidWindow.cpp | 4 +- .../Template/app/build.gradle | 15 ++- .../Template/app/src/main/AndroidManifest.xml | 11 +- .../com/ogre3d/core/DemoNativeActivity.kt | 57 ++++++++++ .../Template/app/src/main/jni/AndroidMain.cpp | 103 +++++++++++++++++- .../AndroidAppTemplate/Template/build.gradle | 4 + 6 files changed, 186 insertions(+), 8 deletions(-) create mode 100644 Samples/2.0/AndroidAppTemplate/Template/app/src/main/java/com/ogre3d/core/DemoNativeActivity.kt diff --git a/RenderSystems/Vulkan/src/Windowing/Android/OgreVulkanAndroidWindow.cpp b/RenderSystems/Vulkan/src/Windowing/Android/OgreVulkanAndroidWindow.cpp index d0c50fd73ee..c3d3b309d44 100644 --- a/RenderSystems/Vulkan/src/Windowing/Android/OgreVulkanAndroidWindow.cpp +++ b/RenderSystems/Vulkan/src/Windowing/Android/OgreVulkanAndroidWindow.cpp @@ -231,10 +231,10 @@ namespace Ogre else { int64 currTimestamp = timeInMilliseconds(); - if( currTimestamp - mFirstRecreateTimestamp <= 1000 ) + if( currTimestamp - mFirstRecreateTimestamp <= 1500 ) { ++mRecreateCount; - if( mRecreateCount < 5u ) + if( mRecreateCount >= 5u ) { // We're disabling Swappy. It's likely buggy. LogManager::getSingleton().logMessage( diff --git a/Samples/2.0/AndroidAppTemplate/Template/app/build.gradle b/Samples/2.0/AndroidAppTemplate/Template/app/build.gradle index f1eb3365b7f..68f12d464a7 100644 --- a/Samples/2.0/AndroidAppTemplate/Template/app/build.gradle +++ b/Samples/2.0/AndroidAppTemplate/Template/app/build.gradle @@ -1,9 +1,20 @@ -apply plugin: 'com.android.application' +plugins { + id 'com.android.application' + id 'kotlin-android' +} android { - compileSdkVersion 33 + compileSdk 33 ndkVersion "25.2.9519653" + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } + defaultConfig { applicationId "com.ogre3d.%%sampleName%%" minSdkVersion 24 diff --git a/Samples/2.0/AndroidAppTemplate/Template/app/src/main/AndroidManifest.xml b/Samples/2.0/AndroidAppTemplate/Template/app/src/main/AndroidManifest.xml index e7efda1bca5..3ed4e353d0c 100644 --- a/Samples/2.0/AndroidAppTemplate/Template/app/src/main/AndroidManifest.xml +++ b/Samples/2.0/AndroidAppTemplate/Template/app/src/main/AndroidManifest.xml @@ -2,13 +2,20 @@ + + - + android:configChanges="orientation|screenSize|keyboardHidden" + android:resizeableActivity="false" + android:launchMode="singleTop"> diff --git a/Samples/2.0/AndroidAppTemplate/Template/app/src/main/java/com/ogre3d/core/DemoNativeActivity.kt b/Samples/2.0/AndroidAppTemplate/Template/app/src/main/java/com/ogre3d/core/DemoNativeActivity.kt new file mode 100644 index 00000000000..12646f13fe8 --- /dev/null +++ b/Samples/2.0/AndroidAppTemplate/Template/app/src/main/java/com/ogre3d/core/DemoNativeActivity.kt @@ -0,0 +1,57 @@ +/* +----------------------------------------------------------------------------- +This source file is part of OGRE-Next +(Object-oriented Graphics Rendering Engine) +For the latest info, see http://www.ogre3d.org + +Copyright (c) 2000-present Torus Knot Software Ltd + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. +----------------------------------------------------------------------------- +*/ + +package com.ogre3d.core + +import android.app.AlertDialog +import android.app.NativeActivity +import android.graphics.Rect + +class DemoNativeActivity : NativeActivity() { + fun showNoVulkanAlert(errorMsg: String, detailedErrorMsg: String) { + runOnUiThread { + var dlg = AlertDialog.Builder(this) + .setMessage("This game requires Vulkan device support. We are sorry.\nThis app cannot be run on this device. Rendering Engine said:\n\n" + errorMsg) + .setCancelable(false) + .create(); + dlg.show(); + + // The default dialog is waaay too small. Make it bigger. + val displayRectangle = Rect() + window.getDecorView().getWindowVisibleDisplayFrame(displayRectangle); + dlg.window?.setLayout( + displayRectangle.width() * 80 / 100, + displayRectangle.height() * 80 / 100 + ); + } + } + + fun getCacheFolder(): String { + return applicationContext.cacheDir.canonicalPath + } +} diff --git a/Samples/2.0/AndroidAppTemplate/Template/app/src/main/jni/AndroidMain.cpp b/Samples/2.0/AndroidAppTemplate/Template/app/src/main/jni/AndroidMain.cpp index c8ad81759d8..38b3b5e99b9 100644 --- a/Samples/2.0/AndroidAppTemplate/Template/app/src/main/jni/AndroidMain.cpp +++ b/Samples/2.0/AndroidAppTemplate/Template/app/src/main/jni/AndroidMain.cpp @@ -46,6 +46,36 @@ THE SOFTWARE. #include "OgreVulkanPlugin.h" #include "Windowing/Android/OgreVulkanAndroidWindow.h" +#include + +//----------------------------------------------------------------------------- +std::string Android_getCacheFolder() +{ + android_app *app = Demo::AndroidSystems::getAndroidApp(); + + JNIEnv *jni = 0; + app->activity->vm->AttachCurrentThread( &jni, nullptr ); + + ANativeActivity *nativeActivity = app->activity; + jclass classDef = jni->GetObjectClass( nativeActivity->clazz ); + jmethodID getCacheFolder = jni->GetMethodID( classDef, "getCacheFolder", "()Ljava/lang/String;" ); + jstring cacheDir = (jstring)jni->CallObjectMethod( nativeActivity->clazz, getCacheFolder ); + + const char *cacheDirCStr = jni->GetStringUTFChars( cacheDir, nullptr ); + std::string retVal( cacheDirCStr ); + jni->ReleaseStringUTFChars( cacheDir, cacheDirCStr ); + + jni->DeleteLocalRef( cacheDir ); + jni->DeleteLocalRef( classDef ); + + if( !retVal.empty() && retVal.back() != '/' ) + retVal += "/"; + + app->activity->vm->DetachCurrentThread(); + + return retVal; +} + namespace Demo { struct AndroidAppController @@ -59,26 +89,84 @@ namespace Demo Ogre::uint64 mStartTime; double mAccumulator; + bool mCriticalFailure; + AndroidAppController() : mGraphicsGameState( 0 ), mGraphicsSystem( 0 ), mLogicGameState( 0 ), mLogicSystem( 0 ), mStartTime( 0 ), - mAccumulator( 0 ) + mAccumulator( 0 ), + mCriticalFailure( false ) { } void init() { + if( mCriticalFailure ) + return; + if( !mGraphicsSystem ) { MainEntryPoints::createSystems( &mGraphicsGameState, &mGraphicsSystem, &mLogicGameState, &mLogicSystem ); - mGraphicsSystem->initialize( MainEntryPoints::getWindowTitle() ); + try + { + mGraphicsSystem->initialize( MainEntryPoints::getWindowTitle() ); + } + catch( Ogre::RenderingAPIException &e ) + { + ANativeActivity *nativeActivity = Demo::AndroidSystems::getAndroidApp()->activity; + + JNIEnv *jni = 0; + nativeActivity->vm->AttachCurrentThread( &jni, nullptr ); + + jclass classDef = jni->GetObjectClass( nativeActivity->clazz ); + jstring errorMsgJStr = jni->NewStringUTF( e.getDescription().c_str() ); + jstring detailedErrorMsgJStr = jni->NewStringUTF( e.getFullDescription().c_str() ); + jmethodID showNoVulkanAlertMethod = jni->GetMethodID( + classDef, "showNoVulkanAlert", "(Ljava/lang/String;Ljava/lang/String;)V" ); + jni->CallVoidMethod( nativeActivity->clazz, showNoVulkanAlertMethod, errorMsgJStr, + detailedErrorMsgJStr ); + jni->DeleteLocalRef( errorMsgJStr ); + jni->DeleteLocalRef( detailedErrorMsgJStr ); + jni->DeleteLocalRef( classDef ); + + // Let them leak. We can't do a clean shutdown. If we're here then there's + // nothing we can do. Let's just avoid crashing. + mLogicSystem = 0; + mGraphicsSystem = 0; + + mCriticalFailure = true; + + Demo::AndroidSystems::setNativeWindow( 0 ); + nativeActivity->vm->DetachCurrentThread(); + return; + } + if( mLogicSystem ) mLogicSystem->initialize(); + { + // Disable Swappy if a previous run determined it was buggy. + struct stat buffer; + if( stat( ( Android_getCacheFolder() + "SwappyDisabled" ).c_str(), &buffer ) == 0 ) + { + Ogre::LogManager::getSingleton().logMessage( + "Disabling Swappy Frame Pacing because previous runs determined it's buggy " + "on this " + "device.", + Ogre::LML_CRITICAL ); + + OGRE_ASSERT_HIGH( dynamic_cast( + mGraphicsSystem->getSceneManager()->getDestinationRenderSystem() ) ); + Ogre::VulkanRenderSystem *renderSystem = static_cast( + mGraphicsSystem->getSceneManager()->getDestinationRenderSystem() ); + renderSystem->setSwappyFramePacing( false ); + } + } + mGraphicsSystem->createScene01(); if( mLogicSystem ) mLogicSystem->createScene01(); @@ -110,6 +198,17 @@ namespace Demo Ogre::VulkanAndroidWindow *window = static_cast( windowBase ); window->setNativeWindow( 0 ); + + // Cache the fact that Swappy is buggy on this device, using an empty file. + OGRE_ASSERT_HIGH( dynamic_cast( + mGraphicsSystem->getSceneManager()->getDestinationRenderSystem() ) ); + Ogre::VulkanRenderSystem *renderSystem = static_cast( + mGraphicsSystem->getSceneManager()->getDestinationRenderSystem() ); + if( !renderSystem->getSwappyFramePacing() ) + { + std::ofstream outFile( Android_getCacheFolder() + "SwappyDisabled", + std::ios::out | std::ios::binary ); + } } } diff --git a/Samples/2.0/AndroidAppTemplate/Template/build.gradle b/Samples/2.0/AndroidAppTemplate/Template/build.gradle index 7aa6aec7c8e..8d267eb6a58 100644 --- a/Samples/2.0/AndroidAppTemplate/Template/build.gradle +++ b/Samples/2.0/AndroidAppTemplate/Template/build.gradle @@ -5,8 +5,12 @@ buildscript { google() jcenter() } + ext { + version_kotlin = '1.9.22' + } dependencies { classpath 'com.android.tools.build:gradle:8.0.2' + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$version_kotlin" // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files }