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/scripts/gfxrecon.py b/android/scripts/gfxrecon.py index 6f98ce82d5..bfb4c7db0c 100644 --- a/android/scripts/gfxrecon.py +++ b/android/scripts/gfxrecon.py @@ -41,7 +41,7 @@ # Application info app_name = 'com.lunarg.gfxreconstruct.replay' -app_activity = '"com.lunarg.gfxreconstruct.replay/android.app.NativeActivity"' +app_activity = '"com.lunarg.gfxreconstruct.replay/.ReplayActivity"' app_action = 'android.intent.action.MAIN' app_category = 'android.intent.category.LAUNCHER' diff --git a/android/tools/replay/src/main/AndroidManifest.xml b/android/tools/replay/AndroidManifest.xml similarity index 93% rename from android/tools/replay/src/main/AndroidManifest.xml rename to android/tools/replay/AndroidManifest.xml index 041531c665..bf64d756fa 100644 --- a/android/tools/replay/src/main/AndroidManifest.xml +++ b/android/tools/replay/AndroidManifest.xml @@ -14,8 +14,8 @@ android:theme="@style/AppTheme" android:supportsRtl="true" android:extractNativeLibs="true" - android:hasCode="false"> - + diff --git a/android/tools/replay/build.gradle b/android/tools/replay/build.gradle index 73b66dfe87..f85850b642 100644 --- a/android/tools/replay/build.gradle +++ b/android/tools/replay/build.gradle @@ -36,6 +36,13 @@ android { 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" diff --git a/android/tools/replay/src/main/res/drawable-v24/ic_launcher_foreground.xml b/android/tools/replay/res/drawable-v24/ic_launcher_foreground.xml similarity index 100% rename from android/tools/replay/src/main/res/drawable-v24/ic_launcher_foreground.xml rename to android/tools/replay/res/drawable-v24/ic_launcher_foreground.xml diff --git a/android/tools/replay/src/main/res/drawable/ic_launcher_background.xml b/android/tools/replay/res/drawable/ic_launcher_background.xml similarity index 100% rename from android/tools/replay/src/main/res/drawable/ic_launcher_background.xml rename to android/tools/replay/res/drawable/ic_launcher_background.xml diff --git a/android/tools/replay/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/android/tools/replay/res/mipmap-anydpi-v26/ic_launcher.xml similarity index 100% rename from android/tools/replay/src/main/res/mipmap-anydpi-v26/ic_launcher.xml rename to android/tools/replay/res/mipmap-anydpi-v26/ic_launcher.xml diff --git a/android/tools/replay/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/android/tools/replay/res/mipmap-anydpi-v26/ic_launcher_round.xml similarity index 100% rename from android/tools/replay/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml rename to android/tools/replay/res/mipmap-anydpi-v26/ic_launcher_round.xml diff --git a/android/tools/replay/src/main/res/mipmap-hdpi/ic_launcher.png b/android/tools/replay/res/mipmap-hdpi/ic_launcher.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-hdpi/ic_launcher.png rename to android/tools/replay/res/mipmap-hdpi/ic_launcher.png diff --git a/android/tools/replay/src/main/res/mipmap-hdpi/ic_launcher_round.png b/android/tools/replay/res/mipmap-hdpi/ic_launcher_round.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-hdpi/ic_launcher_round.png rename to android/tools/replay/res/mipmap-hdpi/ic_launcher_round.png diff --git a/android/tools/replay/src/main/res/mipmap-mdpi/ic_launcher.png b/android/tools/replay/res/mipmap-mdpi/ic_launcher.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-mdpi/ic_launcher.png rename to android/tools/replay/res/mipmap-mdpi/ic_launcher.png diff --git a/android/tools/replay/src/main/res/mipmap-mdpi/ic_launcher_round.png b/android/tools/replay/res/mipmap-mdpi/ic_launcher_round.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-mdpi/ic_launcher_round.png rename to android/tools/replay/res/mipmap-mdpi/ic_launcher_round.png diff --git a/android/tools/replay/src/main/res/mipmap-xhdpi/ic_launcher.png b/android/tools/replay/res/mipmap-xhdpi/ic_launcher.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-xhdpi/ic_launcher.png rename to android/tools/replay/res/mipmap-xhdpi/ic_launcher.png diff --git a/android/tools/replay/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/android/tools/replay/res/mipmap-xhdpi/ic_launcher_round.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-xhdpi/ic_launcher_round.png rename to android/tools/replay/res/mipmap-xhdpi/ic_launcher_round.png diff --git a/android/tools/replay/src/main/res/mipmap-xxhdpi/ic_launcher.png b/android/tools/replay/res/mipmap-xxhdpi/ic_launcher.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-xxhdpi/ic_launcher.png rename to android/tools/replay/res/mipmap-xxhdpi/ic_launcher.png diff --git a/android/tools/replay/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/android/tools/replay/res/mipmap-xxhdpi/ic_launcher_round.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-xxhdpi/ic_launcher_round.png rename to android/tools/replay/res/mipmap-xxhdpi/ic_launcher_round.png diff --git a/android/tools/replay/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/android/tools/replay/res/mipmap-xxxhdpi/ic_launcher.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-xxxhdpi/ic_launcher.png rename to android/tools/replay/res/mipmap-xxxhdpi/ic_launcher.png diff --git a/android/tools/replay/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/android/tools/replay/res/mipmap-xxxhdpi/ic_launcher_round.png similarity index 100% rename from android/tools/replay/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png rename to android/tools/replay/res/mipmap-xxxhdpi/ic_launcher_round.png diff --git a/android/tools/replay/res/values-v27/styles.xml b/android/tools/replay/res/values-v27/styles.xml new file mode 100644 index 0000000000..2d9748dd44 --- /dev/null +++ b/android/tools/replay/res/values-v27/styles.xml @@ -0,0 +1,12 @@ + + + + \ No newline at end of file diff --git a/android/tools/replay/src/main/res/values/colors.xml b/android/tools/replay/res/values/colors.xml similarity index 100% rename from android/tools/replay/src/main/res/values/colors.xml rename to android/tools/replay/res/values/colors.xml diff --git a/android/tools/replay/src/main/res/values/strings.xml b/android/tools/replay/res/values/strings.xml similarity index 100% rename from android/tools/replay/src/main/res/values/strings.xml rename to android/tools/replay/res/values/strings.xml diff --git a/android/tools/replay/src/main/res/values/styles.xml b/android/tools/replay/res/values/styles.xml similarity index 100% rename from android/tools/replay/src/main/res/values/styles.xml rename to android/tools/replay/res/values/styles.xml diff --git a/android/tools/replay/src/main/com/lunarg/gfxreconstruct/replay/ReplayActivity.java b/android/tools/replay/src/main/com/lunarg/gfxreconstruct/replay/ReplayActivity.java new file mode 100644 index 0000000000..ff83522f8b --- /dev/null +++ b/android/tools/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/framework/application/android_context.cpp b/framework/application/android_context.cpp index 68e63402e8..ea9b7596e2 100644 --- a/framework/application/android_context.cpp +++ b/framework/application/android_context.cpp @@ -108,5 +108,43 @@ void AndroidContext::SetOrientation(ScreenOrientation orientation) } } +void AndroidContext::requestNativeWindow(int width, int height) +{ + 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(); + } +} + +void AndroidContext::destroyNativeWindow(int window_index) +{ + 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(); + } +} + 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..539a90dd46 --- /dev/null +++ b/framework/application/android_jni.cpp @@ -0,0 +1,36 @@ +/* +** 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. +*/ + +#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); +} diff --git a/framework/application/android_window.cpp b/framework/application/android_window.cpp index 1f39f5b7e8..0cb377588f 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) @@ -158,13 +159,25 @@ decode::Window* AndroidWindowFactory::Create( GFXRECON_UNREFERENCED_PARAMETER(height); GFXRECON_UNREFERENCED_PARAMETER(force_windowed); - return android_context_->GetWindow(); + 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; } void AndroidWindowFactory::Destroy(decode::Window* window) { - // Android currently has a single window whose lifetime is managed by AndroidContext. - GFXRECON_UNREFERENCED_PARAMETER(window); + int32_t windowidx = created_window_.at(window); + android_context_->destroyNativeWindow(windowidx); } VkBool32 AndroidWindowFactory::GetPhysicalDevicePresentationSupport(const encode::VulkanInstanceTable* table, diff --git a/framework/application/android_window.h b/framework/application/android_window.h index e36fa5b9c1..c32e881a6b 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 0fcb2733fb..8941d50095 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) @@ -108,6 +109,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)