diff --git a/HOWTO_android.md b/HOWTO_android.md index dec043e514..f87176e764 100644 --- a/HOWTO_android.md +++ b/HOWTO_android.md @@ -326,13 +326,32 @@ adb shell "settings put global gpu_debug_layers ''" Now, we need to install the Replay application that we built as part of the GFXReconstruct source. -From the top of the source pulled down from the repo by using the + +Depending on what version of Android you are using, there are two version of the +replay app that can be installed: + + * replay + * multiwin-replay + +`multiwin-replay` is more functional and will eventually become the default +`replay` application, however, it currently only works properly in all our test +cases on Android 14 or newer devices. + +So install the replay application that will work best for you using the `gfxrecon.py` script: +**Install Replay APK** + ```bash ./android/scripts/gfxrecon.py install-apk android/tools/replay/build/outputs/apk/debug/replay-debug.apk ``` +**Install Multi-Windowed Replay APK** + +```bash +./android/scripts/gfxrecon.py install-apk android/tools/multi-win-replay/build/outputs/apk/debug/multi-win-replay-debug.apk +``` + #### Additional Permissions A recent change to enable the replay tool on Android 12 and greater has resulted @@ -362,12 +381,22 @@ adb shell appops set com.lunarg.gfxreconstruct.replay MANAGE_EXTERNAL_STORAGE al ### 9. Run the replay -Try running the replay using the `gfxrecon.py` script: +Depending if you are using the original `replay` application or the `multiwin-replay` +version of the application, you can execute the replay in the following way +using the `gfxrecon.py` script: + +**Original Replay App** ```bash ./android/scripts/gfxrecon.py replay /sdcard/Download/gfxrecon_capture_frames_500_through_700_20221211T130328.gfxr ``` +**Multi-windowed Replay App** + +```bash +./android/scripts/gfxrecon.py multiwin-replay /sdcard/Download/gfxrecon_capture_frames_500_through_700_20221211T130328.gfxr +``` + Voila! It worked, the replay executes properly. @@ -568,21 +597,49 @@ adb shell "setprop debug.hwui.renderer 'skiavk'" Now, we need to install the Replay application that we built as part of the GFXReconstruct source. -Install the replay APK from the root of the built source tree by using the + +As mentioned in the section above, there are currently 2 replay applications: + + * replay + * multiwin-replay + +`multiwin-replay` is more functional and will eventually become the default +`replay` application, however, it currently only works properly in all our test +cases on Android 14 or newer devices. + +So install the replay application that will work best for you using the `gfxrecon.py` script: +**Install Replay APK** + ```bash ./android/scripts/gfxrecon.py install-apk android/tools/replay/build/outputs/apk/debug/replay-debug.apk ``` +**Install Multi-Windowed Replay APK** + +```bash +./android/scripts/gfxrecon.py install-apk android/tools/multi-win-replay/build/outputs/apk/debug/multi-win-replay-debug.apk +``` + ### 12. Run the replay -Try running the replay using the `gfxrecon.py` script: +Depending if you are using the original `replay` application or the `multiwin-replay` +version of the application, you can execute the replay in the following way +using the `gfxrecon.py` script: + +**Original Replay App** ```bash ./android/scripts/gfxrecon.py replay /storage/emulated/0/Download/sacredpath_capture_frames_100_through_200_20221215T174939.gfxr ``` +**Multi-windowed Replay App** + +```bash +./android/scripts/gfxrecon.py multiwin-replay /storage/emulated/0/Download/sacredpath_capture_frames_100_through_200_20221215T174939.gfxr +``` + **NOTE:** Please refer to [Additional Permissions](#additional-permissions) above for additional permissions that may need to be enabled to run the replay application on certain versions of Android. diff --git a/android/framework/application-multi-win/CMakeLists.txt b/android/framework/application-multi-win/CMakeLists.txt new file mode 100644 index 0000000000..9bad730c30 --- /dev/null +++ b/android/framework/application-multi-win/CMakeLists.txt @@ -0,0 +1,32 @@ +add_library(gfxrecon_application_multiwin STATIC "") + +target_compile_definitions(gfxrecon_application_multiwin + PUBLIC + GFXR_MULTI_WINDOW_REPLAY=1 + ) + +target_sources(gfxrecon_application_multiwin + PUBLIC + ${GFXRECON_SOURCE_DIR}/framework/application/android_context.h + ${GFXRECON_SOURCE_DIR}/framework/application/android_context.cpp + ${GFXRECON_SOURCE_DIR}/framework/application/android_window.h + ${GFXRECON_SOURCE_DIR}/framework/application/android_window.cpp + ${GFXRECON_SOURCE_DIR}/framework/application/application.h + ${GFXRECON_SOURCE_DIR}/framework/application/application.cpp + ${GFXRECON_SOURCE_DIR}/framework/application/wsi_context.h + ${GFXRECON_SOURCE_DIR}/framework/application/wsi_context.cpp + ${GFXRECON_SOURCE_DIR}/framework/application/android_jni.cpp + ) + +target_include_directories(gfxrecon_application_multiwin + PUBLIC + ${GFXRECON_SOURCE_DIR}/framework + ${ANDROID_NDK}/sources/android/native_app_glue) + +target_link_libraries(gfxrecon_application_multiwin + gfxrecon_decode + gfxrecon_graphics + gfxrecon_format + gfxrecon_util + vulkan_registry + platform_specific) diff --git a/android/framework/application/CMakeLists.txt b/android/framework/application/CMakeLists.txt index cacd4a13d4..695b5fca82 100644 --- a/android/framework/application/CMakeLists.txt +++ b/android/framework/application/CMakeLists.txt @@ -1,7 +1,7 @@ add_library(gfxrecon_application STATIC "") target_sources(gfxrecon_application - PRIVATE + PUBLIC ${GFXRECON_SOURCE_DIR}/framework/application/android_context.h ${GFXRECON_SOURCE_DIR}/framework/application/android_context.cpp ${GFXRECON_SOURCE_DIR}/framework/application/android_window.h @@ -10,6 +10,7 @@ target_sources(gfxrecon_application ${GFXRECON_SOURCE_DIR}/framework/application/application.cpp ${GFXRECON_SOURCE_DIR}/framework/application/wsi_context.h ${GFXRECON_SOURCE_DIR}/framework/application/wsi_context.cpp + ${GFXRECON_SOURCE_DIR}/framework/application/android_jni.cpp ) target_include_directories(gfxrecon_application diff --git a/android/gradle.properties b/android/gradle.properties index 82618cecb4..90494270be 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -13,3 +13,5 @@ org.gradle.jvmargs=-Xmx1536m # org.gradle.parallel=true +android.useAndroidX=true +android.enableJetifier=true \ No newline at end of file diff --git a/android/scripts/gfxrecon.py b/android/scripts/gfxrecon.py index 1b71c2538c..f3dab342a3 100644 --- a/android/scripts/gfxrecon.py +++ b/android/scripts/gfxrecon.py @@ -32,23 +32,25 @@ # Supported commands valid_commands = [ 'install-apk', + 'multiwin-replay', 'replay' ] # Arguments # gfxrecon install-apk # gfxrecon replay [-p | --push-file ] +# gfxrecon multiwin-replay [-p | --push-file ] # Application info app_name = 'com.lunarg.gfxreconstruct.replay' app_activity = '"com.lunarg.gfxreconstruct.replay/android.app.NativeActivity"' +multiwin_app_activity = '"com.lunarg.gfxreconstruct.replay/.ReplayActivity"' app_action = 'android.intent.action.MAIN' app_category = 'android.intent.category.LAUNCHER' # ADB commands adb_install = 'adb install -g -t -r' adb_sdk_version = 'adb shell getprop ro.build.version.sdk' -adb_start = 'adb shell am start -n {} -a {} -c {}'.format(app_activity, app_action, app_category) adb_stop = 'adb shell am force-stop {}'.format(app_name) adb_push = 'adb push' adb_devices = 'adb devices' @@ -354,7 +356,7 @@ def InstallApk(install_args): print('Executing:', cmd) subprocess.check_call(shlex.split(cmd, posix='win' not in sys.platform)) -def Replay(replay_args): +def ReplayCommon(replay_args, activity): replay_parser = CreateReplayParser() args = replay_parser.parse_args(replay_args) @@ -371,12 +373,20 @@ def Replay(replay_args): print('Executing:', adb_stop) subprocess.check_call(shlex.split(adb_stop, posix='win' not in sys.platform)) + adb_start = 'adb shell am start -n {} -a {} -c {}'.format(activity, app_action, app_category) + cmd = ' '.join([adb_start, '--es', '"args"', '"{}"'.format(extras)]) print('Executing:', cmd) # Specify posix=False to prevent removal of quotes from adb extras. subprocess.check_call(shlex.split(cmd, posix=False)) +def Replay(replay_args): + ReplayCommon(replay_args, app_activity) + +def MultiWinReplay(replay_args): + ReplayCommon(replay_args, multiwin_app_activity) + if __name__ == '__main__': devices = QueryAvailableDevices() @@ -387,3 +397,5 @@ def Replay(replay_args): InstallApk(command.args) elif command.command == 'replay': Replay(command.args) + elif command.command == 'multiwin-replay': + MultiWinReplay(command.args) diff --git a/android/settings.gradle b/android/settings.gradle index 68c1270268..f748bf904e 100644 --- a/android/settings.gradle +++ b/android/settings.gradle @@ -1,3 +1,5 @@ include ':layer' include ':replay' project(':replay').projectDir = file('tools/replay') +include ':multi-win-replay' +project(':multi-win-replay').projectDir = file('tools/multi-win-replay') diff --git a/android/tools/multi-win-replay/.gitignore b/android/tools/multi-win-replay/.gitignore new file mode 100644 index 0000000000..796b96d1c4 --- /dev/null +++ b/android/tools/multi-win-replay/.gitignore @@ -0,0 +1 @@ +/build diff --git a/android/tools/multi-win-replay/AndroidManifest.xml b/android/tools/multi-win-replay/AndroidManifest.xml new file mode 100644 index 0000000000..bf64d756fa --- /dev/null +++ b/android/tools/multi-win-replay/AndroidManifest.xml @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + diff --git a/android/tools/multi-win-replay/CMakeLists.txt b/android/tools/multi-win-replay/CMakeLists.txt new file mode 100644 index 0000000000..0c0b2918ef --- /dev/null +++ b/android/tools/multi-win-replay/CMakeLists.txt @@ -0,0 +1,50 @@ +# For more information about using CMake with Android Studio, read the +# documentation: https://d.android.com/studio/projects/add-native-code.html + +cmake_minimum_required(VERSION 3.4.1) + +project(gfxrecon-replay) + +get_filename_component(GFXRECON_SOURCE_DIR ../../.. ABSOLUTE) + +add_library(native_app_glue STATIC + ${ANDROID_NDK}/sources/android/native_app_glue/android_native_app_glue.c) + +# Export ANativeActivity_onCreate(), +# Refer to: https://github.com/android-ndk/ndk/issues/381. +set(CMAKE_SHARED_LINKER_FLAGS "${CMAKE_SHARED_LINKER_FLAGS} -u ANativeActivity_onCreate") + +include(../../framework/cmake-config/PlatformConfig.cmake) +add_subdirectory(../../framework/util ${CMAKE_SOURCE_DIR}/../../framework/util/build/tools/replay/${ANDROID_ABI}) +add_subdirectory(../../framework/graphics ${CMAKE_SOURCE_DIR}/../../framework/graphics/build/tools/replay/${ANDROID_ABI}) +add_subdirectory(../../framework/format ${CMAKE_SOURCE_DIR}/../../framework/format/build/tools/replay/${ANDROID_ABI}) +add_subdirectory(../../framework/decode ${CMAKE_SOURCE_DIR}/../../framework/decode/build/tools/replay/${ANDROID_ABI}) +add_subdirectory(../../framework/application-multi-win ${CMAKE_SOURCE_DIR}/../../framework/application/build/tools/replay/${ANDROID_ABI}) + +add_library(gfxrecon-replay + SHARED + ${GFXRECON_SOURCE_DIR}/tools/tool_settings.h + ${GFXRECON_SOURCE_DIR}/tools/replay/parse_dump_resources_cli.h + ${GFXRECON_SOURCE_DIR}/tools/replay/parse_dump_resources_cli.cpp + ${GFXRECON_SOURCE_DIR}/tools/replay/replay_settings.h + ${GFXRECON_SOURCE_DIR}/tools/replay/replay_pre_processing.h + ${GFXRECON_SOURCE_DIR}/tools/replay/android_main.cpp) + +target_include_directories(gfxrecon-replay + PUBLIC + ${ANDROID_NDK}/sources/android/native_app_glue + ${GFXRECON_SOURCE_DIR}/external/precompiled/android/include + ${CMAKE_BINARY_DIR}) + +target_link_libraries( + gfxrecon-replay + nlohmann_json + gfxrecon_application_multiwin + gfxrecon_decode + gfxrecon_graphics + gfxrecon_format + gfxrecon_util + platform_specific + native_app_glue + android + log) diff --git a/android/tools/multi-win-replay/build.gradle b/android/tools/multi-win-replay/build.gradle new file mode 100644 index 0000000000..b3d1491645 --- /dev/null +++ b/android/tools/multi-win-replay/build.gradle @@ -0,0 +1,58 @@ +apply plugin: 'com.android.application' + +android { + compileSdkVersion 33 + namespace 'com.lunarg.gfxreconstruct.replay' + ndkVersion '22.1.7171670' + defaultConfig { + applicationId "com.lunarg.gfxreconstruct.replay" + minSdkVersion 26 + targetSdkVersion 33 + versionCode 1 + versionName "1.0" + ndk { + if (project.hasProperty("armeabi-v7a")) { + abiFilters 'armeabi-v7a' + } else if (project.hasProperty("arm64-v8a")) { + abiFilters 'arm64-v8a' + } else if (project.hasProperty("x86")) { + abiFilters 'x86' + } else if (project.hasProperty("x86_64")) { + abiFilters 'x86_64' + } else { + abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64' + } + } + externalNativeBuild { + cmake { + cppFlags "-fexceptions", "-std=c++17", "-Wno-nullability-completeness" + arguments "-DANDROID_TOOLCHAIN=clang", "-DANDROID_STL=c++_static" + } + } + } + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' + } + } + sourceSets { + main { + manifest.srcFile 'AndroidManifest.xml' + java.srcDirs = ['src'] + res.srcDirs = ['res'] + } + } + externalNativeBuild { + cmake { + path "CMakeLists.txt" + } + } +} + +dependencies { + implementation fileTree(dir: 'libs', include: ['*.jar']) + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.0' + implementation project(':layer') +} diff --git a/android/tools/multi-win-replay/proguard-rules.pro b/android/tools/multi-win-replay/proguard-rules.pro new file mode 100644 index 0000000000..f1b424510d --- /dev/null +++ b/android/tools/multi-win-replay/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile diff --git a/android/tools/multi-win-replay/res/drawable-v24/ic_launcher_foreground.xml b/android/tools/multi-win-replay/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..1f6bb29060 --- /dev/null +++ b/android/tools/multi-win-replay/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + diff --git a/android/tools/multi-win-replay/res/drawable/ic_launcher_background.xml b/android/tools/multi-win-replay/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..0d025f9bf6 --- /dev/null +++ b/android/tools/multi-win-replay/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/tools/multi-win-replay/res/mipmap-anydpi-v26/ic_launcher.xml b/android/tools/multi-win-replay/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000000..eca70cfe52 --- /dev/null +++ b/android/tools/multi-win-replay/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/tools/multi-win-replay/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/tools/multi-win-replay/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000000..eca70cfe52 --- /dev/null +++ b/android/tools/multi-win-replay/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/android/tools/multi-win-replay/res/mipmap-hdpi/ic_launcher.png b/android/tools/multi-win-replay/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..898f3ed59a Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-hdpi/ic_launcher.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-hdpi/ic_launcher_round.png b/android/tools/multi-win-replay/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000000..dffca3601e Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-mdpi/ic_launcher.png b/android/tools/multi-win-replay/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..64ba76f75e Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-mdpi/ic_launcher.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-mdpi/ic_launcher_round.png b/android/tools/multi-win-replay/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000000..dae5e08234 Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-xhdpi/ic_launcher.png b/android/tools/multi-win-replay/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..e5ed46597e Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-xhdpi/ic_launcher_round.png b/android/tools/multi-win-replay/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..14ed0af350 Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-xxhdpi/ic_launcher.png b/android/tools/multi-win-replay/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..b0907cac3b Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-xxhdpi/ic_launcher_round.png b/android/tools/multi-win-replay/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..d8ae031549 Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-xxxhdpi/ic_launcher.png b/android/tools/multi-win-replay/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..2c18de9e66 Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/android/tools/multi-win-replay/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/tools/multi-win-replay/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000000..beed3cdd2c Binary files /dev/null and b/android/tools/multi-win-replay/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/android/tools/multi-win-replay/res/values-v27/styles.xml b/android/tools/multi-win-replay/res/values-v27/styles.xml new file mode 100644 index 0000000000..2d9748dd44 --- /dev/null +++ b/android/tools/multi-win-replay/res/values-v27/styles.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/android/tools/multi-win-replay/res/values/colors.xml b/android/tools/multi-win-replay/res/values/colors.xml new file mode 100644 index 0000000000..69b22338c6 --- /dev/null +++ b/android/tools/multi-win-replay/res/values/colors.xml @@ -0,0 +1,6 @@ + + + #008577 + #00574B + #D81B60 + diff --git a/android/tools/multi-win-replay/res/values/strings.xml b/android/tools/multi-win-replay/res/values/strings.xml new file mode 100644 index 0000000000..5a2db752c5 --- /dev/null +++ b/android/tools/multi-win-replay/res/values/strings.xml @@ -0,0 +1,3 @@ + + gfxrecon-replay + diff --git a/android/tools/multi-win-replay/res/values/styles.xml b/android/tools/multi-win-replay/res/values/styles.xml new file mode 100644 index 0000000000..4caddd66c8 --- /dev/null +++ b/android/tools/multi-win-replay/res/values/styles.xml @@ -0,0 +1,11 @@ + + + + + + diff --git a/android/tools/multi-win-replay/src/main/com/lunarg/gfxreconstruct/replay/ReplayActivity.java b/android/tools/multi-win-replay/src/main/com/lunarg/gfxreconstruct/replay/ReplayActivity.java new file mode 100644 index 0000000000..ff83522f8b --- /dev/null +++ b/android/tools/multi-win-replay/src/main/com/lunarg/gfxreconstruct/replay/ReplayActivity.java @@ -0,0 +1,145 @@ +/* +** Copyright (c) 2025 LunarG, Inc. +** Copyright (c) 2025 Arm Limited and/or its affiliates +** +** 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.lunarg.gfxreconstruct.replay; +import java.util.List; +import java.util.ArrayList; +import android.app.NativeActivity; +import android.os.Bundle; +import android.widget.FrameLayout; +import android.view.SurfaceView; +import android.view.SurfaceHolder; +import android.view.ViewGroup.LayoutParams; +import android.view.Surface; +import android.util.Log; +import android.content.Context; +import android.view.View; + +public class ReplayActivity extends NativeActivity +{ + private FrameLayout mFrameLayout; + private static final String TAG = "gfxrecon"; + private Surface mSurface; + public native void setSurface(Surface surface); + private List mSurfaceviewList = new ArrayList(); + + @Override protected void onCreate(Bundle savedInstanceState) + { + super.onCreate(savedInstanceState); + + // Create a FrameLayout to hold SurfaceView instances + mFrameLayout = new FrameLayout(this); + setContentView(mFrameLayout); + + System.loadLibrary("gfxrecon-replay"); + } + + private class VKSurfaceView extends SurfaceView implements SurfaceHolder.Callback + { + private int width; + private int height; + + public VKSurfaceView(Context context, int w, int h) + { + super(context); + + width = w; + height = h; + + SurfaceHolder holder = getHolder(); + holder.addCallback(this); + } + + @Override public void surfaceCreated(SurfaceHolder holder) + { + mSurface = holder.getSurface(); + Log.i(TAG, "SurfaceHolder.Callback: surfaceCreated:" + mSurface); + } + + @Override public void surfaceDestroyed(SurfaceHolder holder) + { + mSurface = holder.getSurface(); + Log.i(TAG, "SurfaceHolder.Callback: surfaceDestroyed:" + mSurface); + } + + @Override public void surfaceChanged(SurfaceHolder holder, int format, int w, int h) + { + mSurface = holder.getSurface(); + Log.i(TAG, "SurfaceHolder.Callback: surfaceChanged:" + mSurface + " " + w + " " + h); + } + } + + public void addNewView(int width, int height) + { + // Create a new SurfaceView + final Context context = this; + final int wid = width; + final int hei = height; + mSurface = null; + runOnUiThread(new Runnable() { + @Override public void run() + { + System.loadLibrary("gfxrecon-replay"); + VKSurfaceView newSurfaceView = new VKSurfaceView(context, wid, hei); + mFrameLayout.addView(newSurfaceView, + new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)); + Log.i(TAG, + "Create a new surface view:" + + " width:" + wid + " height:" + hei); + mSurfaceviewList.add(newSurfaceView); + } + }); + while (mSurface == null) + { + try + { + Thread.sleep(100); + } + catch (Exception e) + { + Log.w(TAG, "Create new surface failed"); + e.printStackTrace(); + } + } + setSurface(mSurface); + } + + private void removeOneView(int surface_idx) + { + final int sur_idx = surface_idx; + runOnUiThread(new Runnable() { + @Override public void run() + { + if (mFrameLayout != null) + { + Log.i(TAG, "Remove one view"); + mFrameLayout.removeView(mSurfaceviewList.get(sur_idx)); + } + else + { + Log.w(TAG, "View container has been destroyed!"); + } + } + }); + } +} diff --git a/android/tools/replay/build.gradle b/android/tools/replay/build.gradle index 73b66dfe87..1dfbe43108 100644 --- a/android/tools/replay/build.gradle +++ b/android/tools/replay/build.gradle @@ -45,7 +45,7 @@ android { dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) - implementation 'com.android.support:appcompat-v7:27.1.1' - implementation 'com.android.support.constraint:constraint-layout:1.1.3' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.0' implementation project(':layer') } diff --git a/framework/application/android_context.cpp b/framework/application/android_context.cpp index 68e63402e8..5f0e4e6170 100644 --- a/framework/application/android_context.cpp +++ b/framework/application/android_context.cpp @@ -108,5 +108,47 @@ void AndroidContext::SetOrientation(ScreenOrientation orientation) } } +void AndroidContext::requestNativeWindow(int width, int height) +{ +#ifdef GFXR_MULTI_WINDOW_REPLAY + JavaVM* jni_vm = nullptr; + jobject jni_activity = nullptr; + JNIEnv* env = nullptr; + if ((android_app_ != nullptr) && (android_app_->activity != nullptr)) + { + jni_vm = android_app_->activity->vm; + jni_activity = android_app_->activity->clazz; + } + if ((jni_vm != nullptr) && (jni_activity != 0) && (jni_vm->AttachCurrentThread(&env, nullptr) == JNI_OK)) + { + jclass object_class = env->GetObjectClass(jni_activity); + jmethodID createsufaceview = env->GetMethodID(object_class, "addNewView", "(II)V"); + env->CallVoidMethod(jni_activity, createsufaceview, width, height); + jni_vm->DetachCurrentThread(); + } +#endif +} + +void AndroidContext::destroyNativeWindow(int window_index) +{ +#ifdef GFXR_MULTI_WINDOW_REPLAY + JavaVM* jni_vm = nullptr; + jobject jni_activity = nullptr; + JNIEnv* env = nullptr; + if ((android_app_ != nullptr) && (android_app_->activity != nullptr)) + { + jni_vm = android_app_->activity->vm; + jni_activity = android_app_->activity->clazz; + } + if ((jni_vm != nullptr) && (jni_activity != 0) && (jni_vm->AttachCurrentThread(&env, nullptr) == JNI_OK)) + { + jclass object_class = env->GetObjectClass(jni_activity); + jmethodID removesurfaceview = env->GetMethodID(object_class, "removeOneView", "(I)V"); + env->CallVoidMethod(jni_activity, removesurfaceview, window_index); + jni_vm->DetachCurrentThread(); + } +#endif +} + GFXRECON_END_NAMESPACE(application) GFXRECON_END_NAMESPACE(gfxrecon) diff --git a/framework/application/android_context.h b/framework/application/android_context.h index c82f287630..0c62eecb0d 100644 --- a/framework/application/android_context.h +++ b/framework/application/android_context.h @@ -59,6 +59,10 @@ class AndroidContext : public WsiContext void SetOrientation(ScreenOrientation orientation); + void requestNativeWindow(int width, int height); + + void destroyNativeWindow(int window_index); + private: std::unique_ptr window_{}; struct android_app* android_app_{}; diff --git a/framework/application/android_jni.cpp b/framework/application/android_jni.cpp new file mode 100644 index 0000000000..eaf2a0694a --- /dev/null +++ b/framework/application/android_jni.cpp @@ -0,0 +1,40 @@ +/* +** Copyright (c) 2025 LunarG, Inc. +** Copyright (c) 2025 Arm Limited and/or its affiliates +** +** 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. +*/ + +#ifdef GFXR_MULTI_WINDOW_REPLAY + +#include +#include +#include "application/android_window.h" +#include +#include "util/logging.h" + +extern "C" JNIEXPORT void JNICALL Java_com_lunarg_gfxreconstruct_replay_ReplayActivity_setSurface(JNIEnv* env, + jobject obj, + jobject surface) +{ + gfxrecon::application::tmp_window = ANativeWindow_fromSurface(env, surface); + GFXRECON_LOG_INFO("Created new window %p", gfxrecon::application::tmp_window); +} + +#endif // GFXR_MULTI_WINDOW_REPLAY \ No newline at end of file diff --git a/framework/application/android_window.cpp b/framework/application/android_window.cpp index c346e2eb9a..334ed41423 100644 --- a/framework/application/android_window.cpp +++ b/framework/application/android_window.cpp @@ -35,6 +35,7 @@ GFXRECON_BEGIN_NAMESPACE(gfxrecon) GFXRECON_BEGIN_NAMESPACE(application) +ANativeWindow* tmp_window = nullptr; AndroidWindow::AndroidWindow(AndroidContext* android_context, ANativeWindow* window) : android_context_(android_context), window_(window), width_(0), height_(0), pre_transform_(0) @@ -163,13 +164,34 @@ decode::Window* AndroidWindowFactory::Create( GFXRECON_UNREFERENCED_PARAMETER(height); GFXRECON_UNREFERENCED_PARAMETER(force_windowed); +#ifdef GFXR_MULTI_WINDOW_REPLAY + tmp_window = nullptr; + android_context_->requestNativeWindow(width, height); + AndroidWindow* tmpwin = nullptr; + if (tmp_window != nullptr) + { + tmpwin = new AndroidWindow(android_context_, tmp_window); + GFXRECON_LOG_INFO("Got android window %p", tmp_window); + } + else + { + GFXRECON_LOG_WARNING("Get android window failed"); + } + return tmpwin; +#else // !GFXR_MULTI_WINDOW_REPLAY return android_context_->GetWindow(); +#endif } void AndroidWindowFactory::Destroy(decode::Window* window) { - // Android currently has a single window whose lifetime is managed by AndroidContext. +#ifdef GFXR_MULTI_WINDOW_REPLAY + int32_t windowidx = created_window_.at(window); + android_context_->destroyNativeWindow(windowidx); +#else // !GFXR_MULTI_WINDOW_REPLAY + // Standard replay app only has a single window whose lifetime is managed by AndroidContext. GFXRECON_UNREFERENCED_PARAMETER(window); +#endif } VkBool32 AndroidWindowFactory::GetPhysicalDevicePresentationSupport(const encode::VulkanInstanceTable* table, diff --git a/framework/application/android_window.h b/framework/application/android_window.h index ecbbde3445..4845cc4e79 100644 --- a/framework/application/android_window.h +++ b/framework/application/android_window.h @@ -31,9 +31,11 @@ #include "util/platform.h" #include +#include GFXRECON_BEGIN_NAMESPACE(gfxrecon) GFXRECON_BEGIN_NAMESPACE(application) +extern ANativeWindow* tmp_window; class AndroidWindow : public decode::Window { diff --git a/framework/decode/vulkan_swapchain.cpp b/framework/decode/vulkan_swapchain.cpp index a5902e0d9e..9b4ddeda02 100644 --- a/framework/decode/vulkan_swapchain.cpp +++ b/framework/decode/vulkan_swapchain.cpp @@ -96,6 +96,9 @@ VkResult VulkanSwapchain::CreateSurface(VkResult orig return VK_ERROR_UNKNOWN; } + window_factory->created_window_.emplace(window, window_index_); + ++window_index_; + result = window->CreateSurface(instance_table_, instance, flags, replay_surface); if ((result == VK_SUCCESS) && (replay_surface != nullptr)) diff --git a/framework/decode/vulkan_swapchain.h b/framework/decode/vulkan_swapchain.h index 4821e13a6a..e52dc5f5f7 100644 --- a/framework/decode/vulkan_swapchain.h +++ b/framework/decode/vulkan_swapchain.h @@ -182,6 +182,7 @@ class VulkanSwapchain application::Application* application_{ nullptr }; ActiveWindows active_windows_; int32_t create_surface_count_{ 0 }; + int32_t window_index_{ 0 }; VulkanSwapchainOptions swapchain_options_; }; diff --git a/framework/decode/window.h b/framework/decode/window.h index 65c5278821..198959c94a 100644 --- a/framework/decode/window.h +++ b/framework/decode/window.h @@ -31,6 +31,7 @@ #include "vulkan/vulkan.h" #include +#include GFXRECON_BEGIN_NAMESPACE(gfxrecon) GFXRECON_BEGIN_NAMESPACE(decode) @@ -110,6 +111,8 @@ class WindowFactory virtual VkBool32 GetPhysicalDevicePresentationSupport(const encode::VulkanInstanceTable* table, VkPhysicalDevice physical_device, uint32_t queue_family_index) = 0; + + std::unordered_map created_window_; }; GFXRECON_END_NAMESPACE(decode)