From 037310f8dcc2a8d46388371cdfa76dd16e4c9b8d Mon Sep 17 00:00:00 2001 From: Triang3l Date: Mon, 11 Jul 2022 21:45:57 +0300 Subject: [PATCH 01/20] [Android] Unified xenia-app with windowed apps and build prerequisites --- .drone.star | 15 +++--- .../xenia/emulator/WindowedAppActivity.java | 3 +- premake5.lua | 11 ++-- src/xenia/app/premake5.lua | 50 +++++++++++++++---- src/xenia/app/xenia_main.cc | 8 +++ src/xenia/cpu/ppc/testing/ppc_testing_main.cc | 2 + src/xenia/cpu/testing/premake5.lua | 9 +++- src/xenia/cpu/testing/util.h | 18 ++++--- src/xenia/gpu/d3d12/premake5.lua | 2 +- src/xenia/gpu/vulkan/premake5.lua | 2 +- src/xenia/hid/hid_demo.cc | 7 +++ src/xenia/hid/premake5.lua | 10 ++-- src/xenia/ui/d3d12/premake5.lua | 2 +- src/xenia/ui/file_picker_android.cc | 37 ++++++++++++++ src/xenia/ui/vulkan/premake5.lua | 2 +- tools/build/premake5.lua | 1 + tools/build/scripts/single_library.lua | 20 ++++++++ tools/build/scripts/test_suite.lua | 20 ++++++-- 18 files changed, 176 insertions(+), 43 deletions(-) create mode 100644 src/xenia/ui/file_picker_android.cc create mode 100644 tools/build/scripts/single_library.lua diff --git a/.drone.star b/.drone.star index 60278044c..194adc533 100644 --- a/.drone.star +++ b/.drone.star @@ -39,37 +39,36 @@ def targets_android(platform): 'mspack', 'snappy', 'xxhash', - 'xenia-core', + 'xenia-app', # 'xenia-app-discord', 'xenia-apu', 'xenia-apu-nop', 'xenia-base', 'xenia-base-tests', + 'xenia-core', 'xenia-cpu', - # 'xenia-cpu-tests', - # 'xenia-cpu-ppc-tests', + 'xenia-cpu-tests', + 'xenia-cpu-ppc-tests', # 'xenia-cpu-backend-x64', # 'xenia-debug-ui', 'xenia-gpu', 'xenia-gpu-shader-compiler', 'xenia-gpu-null', 'xenia-gpu-vulkan', - # 'xenia-gpu-vulkan-trace-viewer', + 'xenia-gpu-vulkan-trace-viewer', 'xenia-gpu-vulkan-trace-dump', 'xenia-hid', - # 'xenia-hid-demo', + 'xenia-hid-demo', 'xenia-hid-nop', 'xenia-kernel', 'xenia-ui', 'xenia-ui-vulkan', - # 'xenia-ui-window-vulkan-demo', + 'xenia-ui-window-vulkan-demo', 'xenia-vfs', 'xenia-vfs-dump', ] if platform == 'Android-x86_64': targets.extend([ - 'xenia-cpu-tests', - 'xenia-cpu-ppc-tests', 'xenia-cpu-backend-x64', 'xenia-debug-ui', ]) diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java index e93baaf0e..323395057 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java @@ -13,8 +13,7 @@ import jp.xenia.XeniaRuntimeException; public abstract class WindowedAppActivity extends Activity { static { - // TODO(Triang3l): Move all demos to libxenia.so. - System.loadLibrary("xenia-ui-window-vulkan-demo"); + System.loadLibrary("xenia-app"); } private final WindowSurfaceOnLayoutChangeListener mWindowSurfaceOnLayoutChangeListener = diff --git a/premake5.lua b/premake5.lua index 1b5a8ec9d..6739d6dba 100644 --- a/premake5.lua +++ b/premake5.lua @@ -141,6 +141,12 @@ filter("platforms:Android-*") systemversion("24") cppstl("c++") staticruntime("On") + -- Hidden visibility is needed to prevent dynamic relocations in FFmpeg + -- AArch64 Neon libavcodec assembly with PIC (accesses extern lookup tables + -- using `adrp` and `add`, without the Global Object Table, expecting that all + -- FFmpeg symbols that aren't a part of the FFmpeg API are hidden by FFmpeg's + -- original build system) by resolving those relocations at link time instead. + visibility("Hidden") links({ "android", "dl", @@ -272,6 +278,7 @@ workspace("xenia") end include("src/xenia") + include("src/xenia/app") include("src/xenia/app/discord") include("src/xenia/apu") include("src/xenia/apu/nop") @@ -293,10 +300,6 @@ workspace("xenia") include("src/xenia/apu/sdl") include("src/xenia/helper/sdl") include("src/xenia/hid/sdl") - - -- TODO(Triang3l): src/xenia/app has a dependency on xenia-helper-sdl, bring - -- it back later. - include("src/xenia/app") end if os.istarget("windows") then diff --git a/src/xenia/app/premake5.lua b/src/xenia/app/premake5.lua index 3f5133d2d..86fcef758 100644 --- a/src/xenia/app/premake5.lua +++ b/src/xenia/app/premake5.lua @@ -4,25 +4,18 @@ include(project_root.."/tools/build") group("src") project("xenia-app") uuid("d7e98620-d007-4ad8-9dbd-b47c8853a17f") - kind("WindowedApp") - targetname("xenia") language("C++") links({ - "xenia-app-discord", "xenia-apu", "xenia-apu-nop", - "xenia-apu-sdl", "xenia-base", "xenia-core", "xenia-cpu", - "xenia-debug-ui", "xenia-gpu", "xenia-gpu-null", "xenia-gpu-vulkan", - "xenia-helper-sdl", "xenia-hid", "xenia-hid-nop", - "xenia-hid-sdl", "xenia-kernel", "xenia-ui", "xenia-ui-vulkan", @@ -48,7 +41,6 @@ project("xenia-app") }) local_platform_files() files({ - "xenia_main.cc", "../base/main_init_"..platform_suffix..".cc", "../ui/windowed_app_main_"..platform_suffix..".cc", }) @@ -57,19 +49,53 @@ project("xenia-app") project_root, }) + filter(SINGLE_LIBRARY_FILTER) + -- Unified library containing all apps as StaticLibs, not just the main + -- emulator windowed app. + kind("SharedLib") + links({ + "xenia-gpu-vulkan-trace-viewer", + "xenia-hid-demo", + "xenia-ui-window-vulkan-demo", + }) + filter(NOT_SINGLE_LIBRARY_FILTER) + kind("WindowedApp") + + -- `targetname` is broken if building from Gradle, works only for toggling the + -- `lib` prefix, as Gradle uses LOCAL_MODULE_FILENAME, not a derivative of + -- LOCAL_MODULE, to specify the targets to build when executing ndk-build. + filter("platforms:not Android-*") + targetname("xenia") + filter("architecture:x86_64") links({ "xenia-cpu-backend-x64", }) + -- TODO(Triang3l): The emulator itself on Android. + filter("platforms:not Android-*") + files({ + "xenia_main.cc", + }) + filter("platforms:Windows") files({ "main_resources.rc", }) - filter("files:../base/main_init_"..platform_suffix..".cc") + filter({"architecture:x86_64", "files:../base/main_init_"..platform_suffix..".cc"}) vectorextensions("IA32") -- Disable AVX for main_init_win.cc so our AVX check doesn't use AVX instructions. + filter("platforms:not Android-*") + links({ + "xenia-app-discord", + "xenia-apu-sdl", + -- TODO(Triang3l): CPU debugger on Android. + "xenia-debug-ui", + "xenia-helper-sdl", + "xenia-hid-sdl", + }) + filter("platforms:Linux") links({ "X11", @@ -87,6 +113,12 @@ project("xenia-app") "xenia-ui-d3d12", }) + filter({"platforms:Windows", SINGLE_LIBRARY_FILTER}) + links({ + "xenia-gpu-d3d12-trace-viewer", + "xenia-ui-window-d3d12-demo", + }) + filter("platforms:Windows") -- Only create the .user file if it doesn't already exist. local user_file = project_root.."/build/xenia-app.vcxproj.user" diff --git a/src/xenia/app/xenia_main.cc b/src/xenia/app/xenia_main.cc index 7986ef79b..15f01a892 100644 --- a/src/xenia/app/xenia_main.cc +++ b/src/xenia/app/xenia_main.cc @@ -36,7 +36,9 @@ // Available audio systems: #include "xenia/apu/nop/nop_audio_system.h" +#if !XE_PLATFORM_ANDROID #include "xenia/apu/sdl/sdl_audio_system.h" +#endif // !XE_PLATFORM_ANDROID #if XE_PLATFORM_WIN32 #include "xenia/apu/xaudio2/xaudio2_audio_system.h" #endif // XE_PLATFORM_WIN32 @@ -50,7 +52,9 @@ // Available input drivers: #include "xenia/hid/nop/nop_hid.h" +#if !XE_PLATFORM_ANDROID #include "xenia/hid/sdl/sdl_hid.h" +#endif // !XE_PLATFORM_ANDROID #if XE_PLATFORM_WIN32 #include "xenia/hid/winkey/winkey_hid.h" #include "xenia/hid/xinput/xinput_hid.h" @@ -253,7 +257,9 @@ std::unique_ptr EmulatorApp::CreateAudioSystem( #if XE_PLATFORM_WIN32 factory.Add("xaudio2"); #endif // XE_PLATFORM_WIN32 +#if !XE_PLATFORM_ANDROID factory.Add("sdl"); +#endif // !XE_PLATFORM_ANDROID factory.Add("nop"); return factory.Create(cvars::apu, processor); } @@ -350,7 +356,9 @@ std::vector> EmulatorApp::CreateInputDrivers( #if XE_PLATFORM_WIN32 factory.Add("xinput", xe::hid::xinput::Create); #endif // XE_PLATFORM_WIN32 +#if !XE_PLATFORM_ANDROID factory.Add("sdl", xe::hid::sdl::Create); +#endif // !XE_PLATFORM_ANDROID #if XE_PLATFORM_WIN32 // WinKey input driver should always be the last input driver added! factory.Add("winkey", xe::hid::winkey::Create); diff --git a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc index 0d54261c4..bcc7e0c6c 100644 --- a/src/xenia/cpu/ppc/testing/ppc_testing_main.cc +++ b/src/xenia/cpu/ppc/testing/ppc_testing_main.cc @@ -425,7 +425,9 @@ bool RunTests(const std::string_view test_name) { int failed_count = 0; int passed_count = 0; +#if XE_ARCH_AMD64 XELOGI("Instruction feature mask {}.", cvars::x64_extension_mask); +#endif // XE_ARCH_AMD64 auto test_path_root = cvars::test_path; std::vector test_files; diff --git a/src/xenia/cpu/testing/premake5.lua b/src/xenia/cpu/testing/premake5.lua index c951243e0..5e70fb3f8 100644 --- a/src/xenia/cpu/testing/premake5.lua +++ b/src/xenia/cpu/testing/premake5.lua @@ -8,10 +8,17 @@ test_suite("xenia-cpu-tests", project_root, ".", { "xenia-base", "xenia-core", "xenia-cpu", - "xenia-cpu-backend-x64", -- TODO(benvanik): cut these dependencies? "xenia-kernel", "xenia-ui", -- needed by xenia-base }, + filtered_links = { + { + filter = 'architecture:x86_64', + links = { + "xenia-cpu-backend-x64", + }, + } + }, }) diff --git a/src/xenia/cpu/testing/util.h b/src/xenia/cpu/testing/util.h index d5c89059c..8f6df2d57 100644 --- a/src/xenia/cpu/testing/util.h +++ b/src/xenia/cpu/testing/util.h @@ -12,6 +12,7 @@ #include +#include "xenia/base/platform.h" #include "xenia/cpu/backend/x64/x64_backend.h" #include "xenia/cpu/hir/hir_builder.h" #include "xenia/cpu/ppc/ppc_context.h" @@ -21,8 +22,6 @@ #include "third_party/catch/include/catch.hpp" -#define XENIA_TEST_X64 1 - namespace xe { namespace cpu { namespace testing { @@ -36,14 +35,17 @@ class TestFunction { memory.reset(new Memory()); memory->Initialize(); -#if XENIA_TEST_X64 { - auto backend = std::make_unique(); - auto processor = std::make_unique(memory.get(), nullptr); - processor->Setup(std::move(backend)); - processors.emplace_back(std::move(processor)); + std::unique_ptr backend; +#if XE_ARCH_AMD64 + backend.reset(new xe::cpu::backend::x64::X64Backend()); +#endif // XE_ARCH + if (backend) { + auto processor = std::make_unique(memory.get(), nullptr); + processor->Setup(std::move(backend)); + processors.emplace_back(std::move(processor)); + } } -#endif // XENIA_TEST_X64 for (auto& processor : processors) { auto module = std::make_unique( diff --git a/src/xenia/gpu/d3d12/premake5.lua b/src/xenia/gpu/d3d12/premake5.lua index 94c7f5b61..f0ee8cc02 100644 --- a/src/xenia/gpu/d3d12/premake5.lua +++ b/src/xenia/gpu/d3d12/premake5.lua @@ -22,7 +22,7 @@ project("xenia-gpu-d3d12") group("src") project("xenia-gpu-d3d12-trace-viewer") uuid("7b5b9fcb-7bf1-43ff-a774-d4c41c8706be") - kind("WindowedApp") + single_library_windowed_app_kind() language("C++") links({ "xenia-apu", diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index 4d8fc4203..90ae7c46e 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -26,7 +26,7 @@ project("xenia-gpu-vulkan") group("src") project("xenia-gpu-vulkan-trace-viewer") uuid("86a1dddc-a26a-4885-8c55-cf745225d93e") - kind("WindowedApp") + single_library_windowed_app_kind() language("C++") links({ "xenia-apu", diff --git a/src/xenia/hid/hid_demo.cc b/src/xenia/hid/hid_demo.cc index f4d616f9e..46111fc84 100644 --- a/src/xenia/hid/hid_demo.cc +++ b/src/xenia/hid/hid_demo.cc @@ -21,6 +21,7 @@ #include "xenia/base/clock.h" #include "xenia/base/cvar.h" #include "xenia/base/logging.h" +#include "xenia/base/platform.h" #include "xenia/base/threading.h" #include "xenia/hid/hid_flags.h" #include "xenia/hid/input_system.h" @@ -36,7 +37,9 @@ // Available input drivers: #include "xenia/hid/nop/nop_hid.h" +#if !XE_PLATFORM_ANDROID #include "xenia/hid/sdl/sdl_hid.h" +#endif // !XE_PLATFORM_ANDROID #if XE_PLATFORM_WIN32 #include "xenia/hid/winkey/winkey_hid.h" #include "xenia/hid/xinput/xinput_hid.h" @@ -122,11 +125,13 @@ std::vector> HidDemoApp::CreateInputDrivers( std::vector> drivers; if (cvars::hid.compare("nop") == 0) { drivers.emplace_back(xe::hid::nop::Create(window, kZOrderHidInput)); +#if !XE_PLATFORM_ANDROID } else if (cvars::hid.compare("sdl") == 0) { auto driver = xe::hid::sdl::Create(window, kZOrderHidInput); if (XSUCCEEDED(driver->Setup())) { drivers.emplace_back(std::move(driver)); } +#endif // !XE_PLATFORM_ANDROID #if XE_PLATFORM_WIN32 } else if (cvars::hid.compare("winkey") == 0) { auto driver = xe::hid::winkey::Create(window, kZOrderHidInput); @@ -140,10 +145,12 @@ std::vector> HidDemoApp::CreateInputDrivers( } #endif // XE_PLATFORM_WIN32 } else { +#if !XE_PLATFORM_ANDROID auto sdl_driver = xe::hid::sdl::Create(window, kZOrderHidInput); if (sdl_driver && XSUCCEEDED(sdl_driver->Setup())) { drivers.emplace_back(std::move(sdl_driver)); } +#endif // !XE_PLATFORM_ANDROID #if XE_PLATFORM_WIN32 auto xinput_driver = xe::hid::xinput::Create(window, kZOrderHidInput); if (xinput_driver && XSUCCEEDED(xinput_driver->Setup())) { diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index fd02fef4a..4e961f623 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -17,16 +17,14 @@ project("xenia-hid") group("demos") project("xenia-hid-demo") uuid("a56a209c-16d5-4913-85f9-86976fe7fddf") - kind("WindowedApp") + single_library_windowed_app_kind() language("C++") links({ "fmt", "imgui", "xenia-base", - "xenia-helper-sdl", "xenia-hid", "xenia-hid-nop", - "xenia-hid-sdl", "xenia-ui", "xenia-ui-vulkan", }) @@ -41,6 +39,12 @@ project("xenia-hid-demo") project_root, }) + filter("platforms:not Android-*") + links({ + "xenia-helper-sdl", + "xenia-hid-sdl", + }) + filter("platforms:Linux") links({ "SDL2", diff --git a/src/xenia/ui/d3d12/premake5.lua b/src/xenia/ui/d3d12/premake5.lua index 0e33011e5..c0d10096b 100644 --- a/src/xenia/ui/d3d12/premake5.lua +++ b/src/xenia/ui/d3d12/premake5.lua @@ -18,7 +18,7 @@ project("xenia-ui-d3d12") group("demos") project("xenia-ui-window-d3d12-demo") uuid("3b9686a7-0f04-4e17-8b00-aeb78ae1107c") - kind("WindowedApp") + single_library_windowed_app_kind() language("C++") links({ "fmt", diff --git a/src/xenia/ui/file_picker_android.cc b/src/xenia/ui/file_picker_android.cc new file mode 100644 index 000000000..9fe506685 --- /dev/null +++ b/src/xenia/ui/file_picker_android.cc @@ -0,0 +1,37 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include + +#include "xenia/ui/file_picker.h" + +namespace xe { +namespace ui { + +// TODO(Triang3l): An asynchronous file picker with a callback, starting an +// activity for an ACTION_OPEN_DOCUMENT or an ACTION_OPEN_DOCUMENT_TREE intent. +// This intent, however, provides a content URI, not the file path directly. +// Accessing the file via the Storage Access Framework doesn't require the +// READ_EXTERNAL_STORAGE permission, unlike opening a file by its path directly. +// A file descriptor can be opened for the URI using +// Context.getContentResolver().openFileDescriptor (it will return a +// ParcelFileDescriptor, and a FileDescriptor can be obtained using its +// getFileDescriptor() method). + +class AndroidFilePicker : public FilePicker { + public: + bool Show(Window* parent_window) override { return false; } +}; + +std::unique_ptr FilePicker::Create() { + return std::make_unique(); +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/vulkan/premake5.lua b/src/xenia/ui/vulkan/premake5.lua index 454007bcb..bdc6b7458 100644 --- a/src/xenia/ui/vulkan/premake5.lua +++ b/src/xenia/ui/vulkan/premake5.lua @@ -22,7 +22,7 @@ project("xenia-ui-vulkan") group("demos") project("xenia-ui-window-vulkan-demo") uuid("97598f13-3177-454c-8e58-c59e2b6ede27") - kind("WindowedApp") + single_library_windowed_app_kind() language("C++") links({ "fmt", diff --git a/tools/build/premake5.lua b/tools/build/premake5.lua index 2c7f8df6a..6f99284e4 100644 --- a/tools/build/premake5.lua +++ b/tools/build/premake5.lua @@ -5,4 +5,5 @@ include("scripts/force_compile_as_c.lua") include("scripts/force_compile_as_cc.lua") include("scripts/pkg_config.lua") include("scripts/platform_files.lua") +include("scripts/single_library.lua") include("scripts/test_suite.lua") diff --git a/tools/build/scripts/single_library.lua b/tools/build/scripts/single_library.lua new file mode 100644 index 000000000..50f60f2cf --- /dev/null +++ b/tools/build/scripts/single_library.lua @@ -0,0 +1,20 @@ +SINGLE_LIBRARY_PLATFORM_PATTERNS = { + "Android-*", +}; + +SINGLE_LIBRARY_FILTER = + "platforms:" .. table.concat(SINGLE_LIBRARY_PLATFORM_PATTERNS, " or "); +NOT_SINGLE_LIBRARY_FILTER = table.translate( + SINGLE_LIBRARY_PLATFORM_PATTERNS, + function(pattern) + return "platforms:not " .. pattern; + end); + +function single_library_windowed_app_kind() + filter(SINGLE_LIBRARY_FILTER); + kind("StaticLib"); + wholelib("On"); + filter(NOT_SINGLE_LIBRARY_FILTER); + kind("WindowedApp"); + filter({}); +end diff --git a/tools/build/scripts/test_suite.lua b/tools/build/scripts/test_suite.lua index a83e891de..2ef600995 100644 --- a/tools/build/scripts/test_suite.lua +++ b/tools/build/scripts/test_suite.lua @@ -24,8 +24,14 @@ local function combined_test_suite(test_suite_name, project_root, base_path, con libdirs(merge_arrays(config["libdirs"], { project_root.."/"..build_bin, })) - links(merge_arrays(config["links"], { - })) + links(config["links"]) + if config.filtered_links ~= nil then + for _, filtered_links in ipairs(config.filtered_links) do + filter(filtered_links.filter) + links(filtered_links.links) + end + filter({}) + end defines({ "XE_TEST_SUITE_NAME=\""..test_suite_name.."\"", }) @@ -57,8 +63,14 @@ local function split_test_suite(test_suite_name, project_root, base_path, config libdirs(merge_arrays(config["libdirs"], { project_root.."/"..build_bin, })) - links(merge_arrays(config["links"], { - })) + links(config["links"]) + if config.filtered_links ~= nil then + for _, filtered_links in ipairs(config.filtered_links) do + filter(filtered_links.filter) + links(filtered_links.links) + end + filter({}) + end files({ project_root.."/"..build_tools_src.."/test_suite_main.cc", file_path, From 3a065c35f0b81e0d0dcb5ce5efd275c515fa24bc Mon Sep 17 00:00:00 2001 From: Triang3l Date: Mon, 11 Jul 2022 21:46:53 +0300 Subject: [PATCH 02/20] [Android] -j, not ndk.jobs, in Gradle --- android/android_studio_project/app/build.gradle | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/android/android_studio_project/app/build.gradle b/android/android_studio_project/app/build.gradle index ae753e1b2..a05d41823 100644 --- a/android/android_studio_project/app/build.gradle +++ b/android/android_studio_project/app/build.gradle @@ -18,13 +18,14 @@ android { arguments 'NDK_APPLICATION_MK:=../../../build/xenia.Application.mk', 'PREMAKE_ANDROIDNDK_PLATFORMS:=Android-ARM64', 'PREMAKE_ANDROIDNDK_PLATFORMS+=Android-x86_64', + // ndk.jobs doesn't work as of Gradle 7.1.0. + "-j${Runtime.runtime.availableProcessors()}", // Work around "Bad file descriptor" on Windows on NDK r22+. '--output-sync=none' } } ndk { abiFilters 'arm64-v8a', 'x86_64' - jobs Runtime.runtime.availableProcessors() stl 'c++_static' } } From 7b8281aee094ac25a44790d4d1cc9bdaab799b97 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 14 Jul 2022 21:13:40 +0300 Subject: [PATCH 03/20] [UI] Android ImGui touch and mouse input --- .../xenia/emulator/WindowedAppActivity.java | 48 +++-- src/xenia/ui/imgui_drawer.cc | 80 +++++++- src/xenia/ui/imgui_drawer.h | 14 +- src/xenia/ui/ui_event.h | 41 ++++ src/xenia/ui/window.cc | 13 ++ src/xenia/ui/window.h | 3 + src/xenia/ui/window_android.cc | 184 ++++++++++++++++++ src/xenia/ui/window_android.h | 6 + src/xenia/ui/window_listener.h | 2 + src/xenia/ui/windowed_app_context_android.cc | 77 ++++++++ src/xenia/ui/windowed_app_context_android.h | 22 +++ 11 files changed, 467 insertions(+), 23 deletions(-) diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java index 323395057..69e0fbe59 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java @@ -1,8 +1,10 @@ package jp.xenia.emulator; +import android.annotation.SuppressLint; import android.app.Activity; import android.content.res.AssetManager; import android.os.Bundle; +import android.view.MotionEvent; import android.view.Surface; import android.view.SurfaceHolder; import android.view.View; @@ -16,10 +18,7 @@ public abstract class WindowedAppActivity extends Activity { System.loadLibrary("xenia-app"); } - private final WindowSurfaceOnLayoutChangeListener mWindowSurfaceOnLayoutChangeListener = - new WindowSurfaceOnLayoutChangeListener(); - private final WindowSurfaceHolderCallback mWindowSurfaceHolderCallback = - new WindowSurfaceHolderCallback(); + private final WindowSurfaceListener mWindowSurfaceListener = new WindowSurfaceListener(); // May be 0 while destroying (mainly while the superclass is). private long mAppContext = 0; @@ -35,6 +34,8 @@ public abstract class WindowedAppActivity extends Activity { private native void onWindowSurfaceLayoutChange( long appContext, int left, int top, int right, int bottom); + private native boolean onWindowSurfaceMotionEvent(long appContext, MotionEvent event); + private native void onWindowSurfaceChanged(long appContext, Surface windowSurface); private native void paintWindow(long appContext, boolean forcePaint); @@ -48,8 +49,10 @@ public abstract class WindowedAppActivity extends Activity { // Detach from the old surface. if (mWindowSurfaceView != null) { - mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceHolderCallback); - mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener); + mWindowSurfaceView.getHolder().removeCallback(mWindowSurfaceListener); + mWindowSurfaceView.setOnTouchListener(null); + mWindowSurfaceView.setOnGenericMotionListener(null); + mWindowSurfaceView.removeOnLayoutChangeListener(mWindowSurfaceListener); mWindowSurfaceView = null; if (mAppContext != 0) { onWindowSurfaceChanged(mAppContext, null); @@ -61,12 +64,12 @@ public abstract class WindowedAppActivity extends Activity { } mWindowSurfaceView = windowSurfaceView; - // The native window code assumes that, when the surface exists, it covers the entire - // window. // FIXME(Triang3l): This doesn't work if the layout has already been performed. - mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceOnLayoutChangeListener); + mWindowSurfaceView.addOnLayoutChangeListener(mWindowSurfaceListener); + mWindowSurfaceView.setOnGenericMotionListener(mWindowSurfaceListener); + mWindowSurfaceView.setOnTouchListener(mWindowSurfaceListener); final SurfaceHolder windowSurfaceHolder = mWindowSurfaceView.getHolder(); - windowSurfaceHolder.addCallback(mWindowSurfaceHolderCallback); + windowSurfaceHolder.addCallback(mWindowSurfaceListener); // If setting after the creation of the surface. if (mAppContext != 0) { final Surface windowSurface = windowSurfaceHolder.getSurface(); @@ -115,7 +118,11 @@ public abstract class WindowedAppActivity extends Activity { super.onDestroy(); } - private class WindowSurfaceOnLayoutChangeListener implements View.OnLayoutChangeListener { + private class WindowSurfaceListener implements + View.OnGenericMotionListener, + View.OnLayoutChangeListener, + View.OnTouchListener, + SurfaceHolder.Callback2 { @Override public void onLayoutChange( final View v, final int left, final int top, final int right, final int bottom, @@ -124,9 +131,24 @@ public abstract class WindowedAppActivity extends Activity { onWindowSurfaceLayoutChange(mAppContext, left, top, right, bottom); } } - } - private class WindowSurfaceHolderCallback implements SurfaceHolder.Callback2 { + @Override + public boolean onGenericMotion(View v, MotionEvent event) { + if (mAppContext == 0) { + return false; + } + return onWindowSurfaceMotionEvent(mAppContext, event); + } + + @SuppressLint("ClickableViewAccessibility") + @Override + public boolean onTouch(View v, MotionEvent event) { + if (mAppContext == 0) { + return false; + } + return onWindowSurfaceMotionEvent(mAppContext, event); + } + @Override public void surfaceCreated(final SurfaceHolder holder) { if (mAppContext == 0) { diff --git a/src/xenia/ui/imgui_drawer.cc b/src/xenia/ui/imgui_drawer.cc index c3ab41906..2454947fd 100644 --- a/src/xenia/ui/imgui_drawer.cc +++ b/src/xenia/ui/imgui_drawer.cc @@ -43,7 +43,8 @@ ImGuiDrawer::~ImGuiDrawer() { window_->RemoveInputListener(this); if (internal_state_) { ImGui::SetCurrentContext(internal_state_); - if (ImGui::IsAnyMouseDown()) { + if (touch_pointer_id_ == TouchEvent::kPointerIDNone && + ImGui::IsAnyMouseDown()) { window_->ReleaseMouse(); } } @@ -213,6 +214,9 @@ void ImGuiDrawer::Initialize() { frame_time_tick_frequency_ = double(Clock::QueryHostTickFrequency()); last_frame_time_ticks_ = Clock::QueryHostTickCount(); + + touch_pointer_id_ = TouchEvent::kPointerIDNone; + reset_mouse_position_after_next_frame_ = false; } void ImGuiDrawer::SetupFontTexture() { @@ -310,6 +314,11 @@ void ImGuiDrawer::Draw(UIDrawContext& ui_draw_context) { RenderDrawLists(draw_data, ui_draw_context); } + if (reset_mouse_position_after_next_frame_) { + reset_mouse_position_after_next_frame_ = false; + io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); + } + if (dialogs_.empty()) { // All dialogs have removed themselves during the draw, detach. presenter_->RemoveUIDrawerFromUIThread(this); @@ -382,7 +391,7 @@ void ImGuiDrawer::OnKeyChar(KeyEvent& e) { } void ImGuiDrawer::OnMouseDown(MouseEvent& e) { - UpdateMousePosition(e); + SwitchToPhysicalMouseAndUpdateMousePosition(e); auto& io = GetIO(); int button = -1; switch (e.button()) { @@ -409,10 +418,12 @@ void ImGuiDrawer::OnMouseDown(MouseEvent& e) { } } -void ImGuiDrawer::OnMouseMove(MouseEvent& e) { UpdateMousePosition(e); } +void ImGuiDrawer::OnMouseMove(MouseEvent& e) { + SwitchToPhysicalMouseAndUpdateMousePosition(e); +} void ImGuiDrawer::OnMouseUp(MouseEvent& e) { - UpdateMousePosition(e); + SwitchToPhysicalMouseAndUpdateMousePosition(e); auto& io = GetIO(); int button = -1; switch (e.button()) { @@ -440,14 +451,48 @@ void ImGuiDrawer::OnMouseUp(MouseEvent& e) { } void ImGuiDrawer::OnMouseWheel(MouseEvent& e) { - UpdateMousePosition(e); + SwitchToPhysicalMouseAndUpdateMousePosition(e); auto& io = GetIO(); io.MouseWheel += float(e.scroll_y()) / float(MouseEvent::kScrollPerDetent); } +void ImGuiDrawer::OnTouchEvent(TouchEvent& e) { + auto& io = GetIO(); + TouchEvent::Action action = e.action(); + uint32_t pointer_id = e.pointer_id(); + if (action == TouchEvent::Action::kDown) { + // The latest pointer needs to be controlling the ImGui mouse. + if (touch_pointer_id_ == TouchEvent::kPointerIDNone) { + // Switching from the mouse to touch input. + if (ImGui::IsAnyMouseDown()) { + std::memset(io.MouseDown, 0, sizeof(io.MouseDown)); + window_->ReleaseMouse(); + } + } + touch_pointer_id_ = pointer_id; + } else { + if (pointer_id != touch_pointer_id_) { + return; + } + } + UpdateMousePosition(e.x(), e.y()); + if (action == TouchEvent::Action::kUp || + action == TouchEvent::Action::kCancel) { + io.MouseDown[0] = false; + touch_pointer_id_ = TouchEvent::kPointerIDNone; + // Make sure that after a touch, the ImGui mouse isn't hovering over + // anything. + reset_mouse_position_after_next_frame_ = true; + } else { + io.MouseDown[0] = true; + reset_mouse_position_after_next_frame_ = false; + } +} + void ImGuiDrawer::ClearInput() { auto& io = GetIO(); - if (ImGui::IsAnyMouseDown()) { + if (touch_pointer_id_ == TouchEvent::kPointerIDNone && + ImGui::IsAnyMouseDown()) { window_->ReleaseMouse(); } io.MousePos = ImVec2(-FLT_MAX, -FLT_MAX); @@ -460,6 +505,8 @@ void ImGuiDrawer::ClearInput() { io.KeySuper = false; std::memset(io.KeysDown, 0, sizeof(io.KeysDown)); io.ClearInputCharacters(); + touch_pointer_id_ = TouchEvent::kPointerIDNone; + reset_mouse_position_after_next_frame_ = false; } void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) { @@ -487,12 +534,27 @@ void ImGuiDrawer::OnKey(KeyEvent& e, bool is_down) { } } -void ImGuiDrawer::UpdateMousePosition(const MouseEvent& e) { +void ImGuiDrawer::UpdateMousePosition(float x, float y) { auto& io = GetIO(); float physical_to_logical = float(window_->GetMediumDpi()) / float(window_->GetDpi()); - io.MousePos.x = e.x() * physical_to_logical; - io.MousePos.y = e.y() * physical_to_logical; + io.MousePos.x = x * physical_to_logical; + io.MousePos.y = y * physical_to_logical; +} + +void ImGuiDrawer::SwitchToPhysicalMouseAndUpdateMousePosition( + const MouseEvent& e) { + if (touch_pointer_id_ != TouchEvent::kPointerIDNone) { + touch_pointer_id_ = TouchEvent::kPointerIDNone; + auto& io = GetIO(); + std::memset(io.MouseDown, 0, sizeof(io.MouseDown)); + // Nothing needs to be done regarding CaptureMouse and ReleaseMouse - all + // buttons as well as mouse capture have been released when switching to + // touch input, the mouse is never captured during touch input, and now + // resetting to no buttons down (therefore not capturing). + } + reset_mouse_position_after_next_frame_ = false; + UpdateMousePosition(float(e.x()), float(e.y())); } } // namespace ui diff --git a/src/xenia/ui/imgui_drawer.h b/src/xenia/ui/imgui_drawer.h index fee5c4242..5563a1151 100644 --- a/src/xenia/ui/imgui_drawer.h +++ b/src/xenia/ui/imgui_drawer.h @@ -59,6 +59,7 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { void OnMouseMove(MouseEvent& e) override; void OnMouseUp(MouseEvent& e) override; void OnMouseWheel(MouseEvent& e) override; + void OnTouchEvent(TouchEvent& e) override; // For now, no need for OnDpiChanged because redrawing is done continuously. private: @@ -70,7 +71,8 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { void ClearInput(); void OnKey(KeyEvent& e, bool is_down); - void UpdateMousePosition(const MouseEvent& e); + void UpdateMousePosition(float x, float y); + void SwitchToPhysicalMouseAndUpdateMousePosition(const MouseEvent& e); Window* window_; size_t z_order_; @@ -92,6 +94,16 @@ class ImGuiDrawer : public WindowInputListener, public UIDrawer { // detaching the presenter. std::unique_ptr font_texture_; + // If there's an active pointer, the ImGui mouse is controlled by this touch. + // If it's TouchEvent::kPointerIDNone, the ImGui mouse is controlled by the + // mouse. + uint32_t touch_pointer_id_ = TouchEvent::kPointerIDNone; + // Whether after the next frame (since the mouse up event needs to be handled + // with the correct mouse position still), the ImGui mouse position should be + // reset (for instance, after releasing a touch), so it's not hovering over + // anything. + bool reset_mouse_position_after_next_frame_ = false; + double frame_time_tick_frequency_; uint64_t last_frame_time_ticks_; }; diff --git a/src/xenia/ui/ui_event.h b/src/xenia/ui/ui_event.h index 4d5520782..5ad26e540 100644 --- a/src/xenia/ui/ui_event.h +++ b/src/xenia/ui/ui_event.h @@ -10,6 +10,7 @@ #ifndef XENIA_UI_UI_EVENT_H_ #define XENIA_UI_UI_EVENT_H_ +#include #include #include "xenia/ui/virtual_key.h" @@ -156,6 +157,46 @@ class MouseEvent : public UIEvent { int32_t scroll_y_ = 0; }; +class TouchEvent : public UIEvent { + public: + enum class Action { + kDown, + kUp, + // Should be treated as an up event, but without performing the usual action + // for releasing. + kCancel, + kMove, + }; + + // Can be used by event listeners as the value for when there's no current + // pointer, for example. + static constexpr uint32_t kPointerIDNone = UINT32_MAX; + + explicit TouchEvent(Window* target, uint32_t pointer_id, Action action, + float x, float y) + : UIEvent(target), + pointer_id_(pointer_id), + action_(action), + x_(x), + y_(y) {} + + bool is_handled() const { return handled_; } + void set_handled(bool value) { handled_ = value; } + + uint32_t pointer_id() { return pointer_id_; } + Action action() const { return action_; } + // Can be outside the boundaries of the surface. + float x() const { return x_; } + float y() const { return y_; } + + private: + bool handled_ = false; + uint32_t pointer_id_; + Action action_; + float x_; + float y_; +}; + } // namespace ui } // namespace xe diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index ad6129307..ed00afc6b 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -645,6 +645,19 @@ void Window::OnMouseWheel(MouseEvent& e, } } +void Window::OnTouchEvent(TouchEvent& e, + WindowDestructionReceiver& destruction_receiver) { + PropagateEventThroughInputListeners( + [&e](auto listener) { + listener->OnTouchEvent(e); + return e.is_handled(); + }, + destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return; + } +} + void Window::SendEventToListeners( std::function fn, WindowDestructionReceiver& destruction_receiver) { diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index f3f3bb348..aaf7983a8 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -597,6 +597,9 @@ class Window { void OnMouseWheel(MouseEvent& e, WindowDestructionReceiver& destruction_receiver); + void OnTouchEvent(TouchEvent& e, + WindowDestructionReceiver& destruction_receiver); + private: struct ListenerIterationContext { explicit ListenerIterationContext(ListenerIterationContext* outer_context, diff --git a/src/xenia/ui/window_android.cc b/src/xenia/ui/window_android.cc index 817f439c7..d67d478d1 100644 --- a/src/xenia/ui/window_android.cc +++ b/src/xenia/ui/window_android.cc @@ -9,10 +9,16 @@ #include "xenia/ui/window_android.h" +#include +#include +#include +#include #include #include #include "xenia/base/assert.h" +#include "xenia/base/logging.h" +#include "xenia/base/math.h" #include "xenia/ui/surface_android.h" #include "xenia/ui/windowed_app_context_android.h" @@ -55,6 +61,181 @@ void AndroidWindow::OnActivitySurfaceLayoutChange() { } } +bool AndroidWindow::OnActivitySurfaceMotionEvent(jobject event) { + auto& android_app_context = + static_cast(app_context()); + JNIEnv* jni_env = android_app_context.ui_thread_jni_env(); + const AndroidWindowedAppContext::JniIDs& jni_ids = + android_app_context.jni_ids(); + + int32_t source = + jni_env->CallIntMethod(event, jni_ids.motion_event_get_source); + + switch (source) { + case AINPUT_SOURCE_TOUCHSCREEN: { + // Returning true for all touch events regardless of whether they have + // been handled to keep receiving touch events for the pointers in the + // event (if returning false for a down event, no more events will be sent + // for the pointers in it), and also because there are multiple pointers + // in a single event, and different handler invocations may result in + // different is_handled. + WindowDestructionReceiver destruction_receiver(this); + int32_t action_and_pointer_index = + jni_env->CallIntMethod(event, jni_ids.motion_event_get_action); + int32_t action = action_and_pointer_index & AMOTION_EVENT_ACTION_MASK; + // For pointer ACTION_POINTER_DOWN and ACTION_POINTER_UP. + int32_t pointer_index = (action_and_pointer_index & + AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + if (action == AMOTION_EVENT_ACTION_POINTER_DOWN || + action == AMOTION_EVENT_ACTION_POINTER_UP) { + int32_t touch_pointer_index = + (action_and_pointer_index & + AMOTION_EVENT_ACTION_POINTER_INDEX_MASK) >> + AMOTION_EVENT_ACTION_POINTER_INDEX_SHIFT; + TouchEvent e( + this, + jni_env->CallIntMethod(event, jni_ids.motion_event_get_pointer_id, + touch_pointer_index), + (action == AMOTION_EVENT_ACTION_POINTER_DOWN) + ? TouchEvent::Action::kDown + : TouchEvent::Action::kUp, + jni_env->CallFloatMethod(event, jni_ids.motion_event_get_x, + pointer_index), + jni_env->CallFloatMethod(event, jni_ids.motion_event_get_y, + pointer_index)); + OnTouchEvent(e, destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return true; + } + } else { + TouchEvent::Action touch_event_action; + switch (action) { + case AMOTION_EVENT_ACTION_DOWN: + touch_event_action = TouchEvent::Action::kDown; + break; + case AMOTION_EVENT_ACTION_UP: + touch_event_action = TouchEvent::Action::kUp; + break; + case AMOTION_EVENT_ACTION_MOVE: + touch_event_action = TouchEvent::Action::kMove; + break; + case AMOTION_EVENT_ACTION_CANCEL: + touch_event_action = TouchEvent::Action::kCancel; + break; + default: + return true; + } + int32_t touch_pointer_count = jni_env->CallIntMethod( + event, jni_ids.motion_event_get_pointer_count); + for (int32_t i = 0; i < touch_pointer_count; ++i) { + TouchEvent e( + this, + jni_env->CallIntMethod(event, jni_ids.motion_event_get_pointer_id, + i), + touch_event_action, + jni_env->CallFloatMethod(event, jni_ids.motion_event_get_x, i), + jni_env->CallFloatMethod(event, jni_ids.motion_event_get_y, i)); + OnTouchEvent(e, destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return true; + } + } + } + return true; + } break; + + case AINPUT_SOURCE_MOUSE: { + WindowDestructionReceiver destruction_receiver(this); + // X and Y can be outside the View (have negative coordinates, or beyond + // the size of the element), and not only for ACTION_HOVER_EXIT (it's + // predeced by ACTION_HOVER_MOVE, at least on Android API level 30, also + // with out-of-bounds coordinates), when moving the mouse outside the + // View, or when starting moving the mouse when the pointer was previously + // outside the View in some cases. + int32_t mouse_x = int32_t( + std::min(float(GetActualPhysicalWidth()), + std::max(0.0f, jni_env->CallFloatMethod( + event, jni_ids.motion_event_get_x, 0))) + + 0.5f); + int32_t mouse_y = int32_t( + std::min(float(GetActualPhysicalHeight()), + std::max(0.0f, jni_env->CallFloatMethod( + event, jni_ids.motion_event_get_y, 0))) + + 0.5f); + static const MouseEvent::Button kMouseEventButtons[] = { + MouseEvent::Button::kLeft, MouseEvent::Button::kRight, + MouseEvent::Button::kMiddle, MouseEvent::Button::kX1, + MouseEvent::Button::kX2, + }; + static constexpr uint32_t kUsedMouseButtonMask = + (UINT32_C(1) << xe::countof(kMouseEventButtons)) - 1; + uint32_t new_mouse_button_state = uint32_t( + jni_env->CallIntMethod(event, jni_ids.motion_event_get_button_state)); + // OnMouseUp. + uint32_t mouse_buttons_remaining = + mouse_button_state_ & ~new_mouse_button_state & kUsedMouseButtonMask; + uint32_t mouse_button_index; + while ( + xe::bit_scan_forward(mouse_buttons_remaining, &mouse_button_index)) { + mouse_buttons_remaining &= ~(UINT32_C(1) << mouse_button_index); + MouseEvent e(this, kMouseEventButtons[mouse_button_index], mouse_x, + mouse_y); + OnMouseUp(e, destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return true; + } + } + // Generic OnMouseMove regardless of the action since any event can + // provide new coordinates. + { + MouseEvent e(this, MouseEvent::Button::kNone, mouse_x, mouse_y); + OnMouseMove(e, destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return true; + } + } + // OnMouseWheel. + // The axis value may be outside -1...1 if multiple scrolls have occurred + // quickly. + int32_t scroll_x = int32_t( + jni_env->CallFloatMethod(event, jni_ids.motion_event_get_axis_value, + AMOTION_EVENT_AXIS_HSCROLL, 0) * + float(MouseEvent::kScrollPerDetent)); + int32_t scroll_y = int32_t( + jni_env->CallFloatMethod(event, jni_ids.motion_event_get_axis_value, + AMOTION_EVENT_AXIS_VSCROLL, 0) * + float(MouseEvent::kScrollPerDetent)); + if (scroll_x || scroll_y) { + MouseEvent e(this, MouseEvent::Button::kNone, mouse_x, mouse_y, + scroll_x, scroll_y); + OnMouseWheel(e, destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return true; + } + } + // OnMouseDown. + mouse_buttons_remaining = + new_mouse_button_state & ~mouse_button_state_ & kUsedMouseButtonMask; + while ( + xe::bit_scan_forward(mouse_buttons_remaining, &mouse_button_index)) { + mouse_buttons_remaining &= ~(UINT32_C(1) << mouse_button_index); + MouseEvent e(this, kMouseEventButtons[mouse_button_index], mouse_x, + mouse_y); + OnMouseDown(e, destruction_receiver); + if (destruction_receiver.IsWindowDestroyed()) { + return true; + } + } + // Update the button state for state differences. + mouse_button_state_ = new_mouse_button_state; + return true; + } break; + } + + return false; +} + uint32_t AndroidWindow::GetLatestDpiImpl() const { auto& android_app_context = static_cast(app_context()); @@ -62,6 +243,9 @@ uint32_t AndroidWindow::GetLatestDpiImpl() const { } bool AndroidWindow::OpenImpl() { + // Reset the input. + mouse_button_state_ = 0; + // The window is a proxy between the main activity and Xenia, so there can be // only one open window for an activity. auto& android_app_context = diff --git a/src/xenia/ui/window_android.h b/src/xenia/ui/window_android.h index 52fac19ec..79608da87 100644 --- a/src/xenia/ui/window_android.h +++ b/src/xenia/ui/window_android.h @@ -10,6 +10,8 @@ #ifndef XENIA_UI_WINDOW_ANDROID_H_ #define XENIA_UI_WINDOW_ANDROID_H_ +#include + #include "xenia/ui/menu_item.h" #include "xenia/ui/window.h" @@ -30,6 +32,7 @@ class AndroidWindow : public Window { uint32_t GetMediumDpi() const override { return 160; } void OnActivitySurfaceLayoutChange(); + bool OnActivitySurfaceMotionEvent(jobject event); void OnActivitySurfaceChanged() { OnSurfaceChanged(true); } void PaintActivitySurface(bool force_paint) { OnPaint(force_paint); } @@ -42,6 +45,9 @@ class AndroidWindow : public Window { std::unique_ptr CreateSurfaceImpl( Surface::TypeFlags allowed_types) override; void RequestPaintImpl() override; + + private: + uint32_t mouse_button_state_ = 0; }; // Dummy for the menu item - menus are controlled by the layout. diff --git a/src/xenia/ui/window_listener.h b/src/xenia/ui/window_listener.h index 6dbf36c77..f2b44a01e 100644 --- a/src/xenia/ui/window_listener.h +++ b/src/xenia/ui/window_listener.h @@ -48,6 +48,8 @@ class WindowInputListener { virtual void OnMouseMove(MouseEvent& e) {} virtual void OnMouseUp(MouseEvent& e) {} virtual void OnMouseWheel(MouseEvent& e) {} + + virtual void OnTouchEvent(TouchEvent& e) {} }; } // namespace ui diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc index c7cdaa6db..d25ed97ee 100644 --- a/src/xenia/ui/windowed_app_context_android.cc +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -20,6 +20,7 @@ #include #include #include +#include #include "xenia/base/assert.h" #include "xenia/base/logging.h" @@ -130,6 +131,12 @@ void AndroidWindowedAppContext::JniActivityOnWindowSurfaceLayoutChange( } } +bool AndroidWindowedAppContext::JniActivityOnWindowSurfaceMotionEvent( + jobject event) { + return activity_window_ && + activity_window_->OnActivitySurfaceMotionEvent(event); +} + void AndroidWindowedAppContext::JniActivityOnWindowSurfaceChanged( jobject window_surface_object) { // Detach from the old surface. @@ -250,6 +257,62 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, android_base_initialized_ = true; ui_thread_jni_env_->DeleteLocalRef(application_context_init_ref); + // Initialize common windowed app JNI IDs. + { + jclass motion_event_class_local_ref = + ui_thread_jni_env_->FindClass("android/view/MotionEvent"); + if (!motion_event_class_local_ref) { + XELOGE( + "AndroidWindowedAppContext: Failed to find the motion event class"); + Shutdown(); + return false; + } + motion_event_class_ = + reinterpret_cast(ui_thread_jni_env_->NewGlobalRef( + reinterpret_cast(motion_event_class_local_ref))); + ui_thread_jni_env_->DeleteLocalRef( + reinterpret_cast(motion_event_class_local_ref)); + } + if (!motion_event_class_) { + XELOGE( + "AndroidWindowedAppContext: Failed to create a global reference to the " + "motion event class"); + Shutdown(); + return false; + } + bool motion_event_ids_obtained = true; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_action = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getAction", "()I")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_axis_value = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getAxisValue", "(II)F")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_button_state = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getButtonState", "()I")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_pointer_id = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getPointerId", "(I)I")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_pointer_count = + ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getPointerCount", "()I")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_source = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getSource", "()I")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_x = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getX", "(I)F")) != nullptr; + motion_event_ids_obtained &= + (jni_ids_.motion_event_get_y = ui_thread_jni_env_->GetMethodID( + motion_event_class_, "getY", "(I)F")) != nullptr; + if (!motion_event_ids_obtained) { + XELOGE( + "AndroidWindowedAppContext: Failed to get the motion event class IDs"); + Shutdown(); + return false; + } + // Initialize interfacing with the WindowedAppActivity. activity_ = ui_thread_jni_env_->NewGlobalRef(activity); if (!activity_) { @@ -343,6 +406,12 @@ void AndroidWindowedAppContext::Shutdown() { activity_ = nullptr; } + std::memset(&jni_ids_, 0, sizeof(jni_ids_)); + if (motion_event_class_) { + ui_thread_jni_env_->DeleteGlobalRef(motion_event_class_); + motion_event_class_ = nullptr; + } + if (android_base_initialized_) { xe::ShutdownAndroidAppFromMainThread(); android_base_initialized_ = false; @@ -495,6 +564,14 @@ Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceLayoutChange( ->JniActivityOnWindowSurfaceLayoutChange(left, top, right, bottom); } +JNIEXPORT jboolean JNICALL +Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceMotionEvent( + JNIEnv* jni_env, jobject activity, jlong app_context_ptr, jobject event) { + return jboolean( + reinterpret_cast(app_context_ptr) + ->JniActivityOnWindowSurfaceMotionEvent(event)); +} + JNIEXPORT void JNICALL Java_jp_xenia_emulator_WindowedAppActivity_onWindowSurfaceChanged( JNIEnv* jni_env, jobject activity, jlong app_context_ptr, diff --git a/src/xenia/ui/windowed_app_context_android.h b/src/xenia/ui/windowed_app_context_android.h index 022826fc1..4da2d2501 100644 --- a/src/xenia/ui/windowed_app_context_android.h +++ b/src/xenia/ui/windowed_app_context_android.h @@ -28,17 +28,35 @@ class WindowedApp; class AndroidWindowedAppContext final : public WindowedAppContext { public: + // Precached JNI references and IDs that may be used for windowed app purposes + // by external code. + struct JniIDs { + // android.view.MotionEvent. + jmethodID motion_event_get_action; + jmethodID motion_event_get_axis_value; + jmethodID motion_event_get_button_state; + jmethodID motion_event_get_pointer_count; + jmethodID motion_event_get_pointer_id; + jmethodID motion_event_get_source; + jmethodID motion_event_get_x; + jmethodID motion_event_get_y; + }; + WindowedApp* app() const { return app_.get(); } void NotifyUILoopOfPendingFunctions() override; void PlatformQuitFromUIThread() override; + JNIEnv* ui_thread_jni_env() const { return ui_thread_jni_env_; } + uint32_t GetPixelDensity() const { return configuration_ ? uint32_t(AConfiguration_getDensity(configuration_)) : 160; } + const JniIDs& jni_ids() const { return jni_ids_; } + int32_t window_surface_layout_left() const { return window_surface_layout_left_; } @@ -70,6 +88,7 @@ class AndroidWindowedAppContext final : public WindowedAppContext { void JniActivityOnDestroy(); void JniActivityOnWindowSurfaceLayoutChange(jint left, jint top, jint right, jint bottom); + bool JniActivityOnWindowSurfaceMotionEvent(jobject event); void JniActivityOnWindowSurfaceChanged(jobject window_surface_object); void JniActivityPaintWindow(bool force_paint); @@ -117,6 +136,9 @@ class AndroidWindowedAppContext final : public WindowedAppContext { bool android_base_initialized_ = false; + jclass motion_event_class_ = nullptr; + JniIDs jni_ids_ = {}; + jobject activity_ = nullptr; jmethodID activity_method_finish_ = nullptr; jmethodID activity_method_post_invalidate_window_surface_ = nullptr; From 2a69d1db4d13e9009c6ae562e491be4a0e0668af Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 14 Jul 2022 21:16:23 +0300 Subject: [PATCH 04/20] [Vulkan] Fix a typo in a comment about BC textures [ci skip] --- src/xenia/gpu/vulkan/vulkan_texture_cache.cc | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/xenia/gpu/vulkan/vulkan_texture_cache.cc b/src/xenia/gpu/vulkan/vulkan_texture_cache.cc index 580696a30..c9844222b 100644 --- a/src/xenia/gpu/vulkan/vulkan_texture_cache.cc +++ b/src/xenia/gpu/vulkan/vulkan_texture_cache.cc @@ -1796,8 +1796,8 @@ bool VulkanTextureCache::Initialize() { // S3TC. // Not checking the textureCompressionBC feature because its availability // means that all BC formats are supported, however, the device may expose - // some BC formats without this feature. Xenia doesn't BC6H and BC7 at all, - // and has fallbacks for each used format. + // some BC formats without this feature. Xenia doesn't use BC6H and BC7 at + // all, and has fallbacks for each used format. // TODO(Triang3l): Raise the host texture memory usage limit if S3TC has to be // decompressed. // TODO(Triang3l): S3TC -> 5551 or 4444 as an option. From 9fd63519bf05762f89aeb0f2942d5e441d54192e Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 14 Jul 2022 22:04:06 +0300 Subject: [PATCH 05/20] [Base] Make MappedMemory non-copyable --- src/xenia/base/mapped_memory.h | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/xenia/base/mapped_memory.h b/src/xenia/base/mapped_memory.h index 203c43918..6a58ab4c4 100644 --- a/src/xenia/base/mapped_memory.h +++ b/src/xenia/base/mapped_memory.h @@ -32,6 +32,10 @@ class MappedMemory { MappedMemory(const std::filesystem::path& path, Mode mode, void* data, size_t size) : path_(path), mode_(mode), data_(data), size_(size) {} + MappedMemory(const MappedMemory& mapped_memory) = delete; + MappedMemory& operator=(const MappedMemory& mapped_memory) = delete; + MappedMemory(MappedMemory&& mapped_memory) = delete; + MappedMemory& operator=(MappedMemory&& mapped_memory) = delete; virtual ~MappedMemory() = default; std::unique_ptr Slice(Mode mode, size_t offset, size_t length) { From 65137e58bde86a150f7ba4038796aceea4009203 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 14 Jul 2022 22:11:46 +0300 Subject: [PATCH 06/20] [Base] PosixMappedMemory: fd instead of stdio Android ContentResolver, which is needed for content:// URIs, provides file descriptors rather than stdio files --- src/xenia/base/mapped_memory_posix.cc | 54 ++++++++++++++------------- 1 file changed, 29 insertions(+), 25 deletions(-) diff --git a/src/xenia/base/mapped_memory_posix.cc b/src/xenia/base/mapped_memory_posix.cc index 34c530e20..d6e877b4d 100644 --- a/src/xenia/base/mapped_memory_posix.cc +++ b/src/xenia/base/mapped_memory_posix.cc @@ -9,8 +9,10 @@ #include "xenia/base/mapped_memory.h" +#include #include -#include +#include +#include #include #include "xenia/base/string.h" @@ -19,61 +21,63 @@ namespace xe { class PosixMappedMemory : public MappedMemory { public: - PosixMappedMemory(const std::filesystem::path& path, Mode mode) - : MappedMemory(path, mode), file_handle(nullptr) {} + PosixMappedMemory(const std::filesystem::path& path, Mode mode, void* data, + size_t size, int file_descriptor) + : MappedMemory(path, mode, data, size), + file_descriptor_(file_descriptor) {} ~PosixMappedMemory() override { if (data_) { - munmap(data_, size_); + munmap(data_, size()); } - if (file_handle) { - fclose(file_handle); + if (file_descriptor_ >= 0) { + close(file_descriptor_); } } - FILE* file_handle; + private: + int file_descriptor_; }; std::unique_ptr MappedMemory::Open( const std::filesystem::path& path, Mode mode, size_t offset, size_t length) { - const char* mode_str; + int open_flags = 0; int prot; switch (mode) { case Mode::kRead: - mode_str = "rb"; + open_flags |= O_RDONLY; prot = PROT_READ; break; case Mode::kReadWrite: - mode_str = "r+b"; + open_flags |= O_RDWR; prot = PROT_READ | PROT_WRITE; break; } - auto mm = - std::unique_ptr(new PosixMappedMemory(path, mode)); - - mm->file_handle = fopen(path.c_str(), mode_str); - if (!mm->file_handle) { + int file_descriptor = open(path.c_str(), open_flags); + if (file_descriptor < 0) { return nullptr; } - size_t map_length; - map_length = length; + size_t map_length = length; if (!length) { - fseeko(mm->file_handle, 0, SEEK_END); - map_length = ftello(mm->file_handle); - fseeko(mm->file_handle, 0, SEEK_SET); + struct stat64 file_stat; + if (fstat64(file_descriptor, &file_stat)) { + close(file_descriptor); + return nullptr; + } + map_length = size_t(file_stat.st_size); } - mm->size_ = map_length; - mm->data_ = - mmap(0, map_length, prot, MAP_SHARED, fileno(mm->file_handle), offset); - if (!mm->data_) { + void* data = mmap(0, map_length, prot, MAP_SHARED, file_descriptor, offset); + if (!data) { + close(file_descriptor); return nullptr; } - return std::move(mm); + return std::unique_ptr( + new PosixMappedMemory(path, mode, data, map_length, file_descriptor)); } std::unique_ptr ChunkedMappedMemoryWriter::Open( From 415750252b66b9d32f52ecc9ba68fbb090953e42 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Thu, 14 Jul 2022 22:51:07 +0300 Subject: [PATCH 07/20] [Base] PosixMappedMemory: Close, Flush --- src/xenia/base/mapped_memory_posix.cc | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/xenia/base/mapped_memory_posix.cc b/src/xenia/base/mapped_memory_posix.cc index d6e877b4d..6f89ffe43 100644 --- a/src/xenia/base/mapped_memory_posix.cc +++ b/src/xenia/base/mapped_memory_posix.cc @@ -35,6 +35,22 @@ class PosixMappedMemory : public MappedMemory { } } + void Close(uint64_t truncate_size) override { + if (data_) { + munmap(data_, size()); + data_ = nullptr; + } + if (file_descriptor_ >= 0) { + if (truncate_size) { + ftruncate64(file_descriptor_, off64_t(truncate_size)); + } + close(file_descriptor_); + file_descriptor_ = -1; + } + } + + void Flush() override { msync(data(), size(), MS_ASYNC); } + private: int file_descriptor_; }; From 373b1430497c5f853c3f0d9141631141e20d1b85 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 16 Jul 2022 13:13:08 +0300 Subject: [PATCH 08/20] [Base] Cvars from Android Bundle/Intent --- .../xenia/emulator/WindowedAppActivity.java | 7 + src/xenia/base/console_app_main_android.cc | 3 +- src/xenia/base/cvar.cc | 5 +- src/xenia/base/cvar.h | 10 +- src/xenia/base/cvar_android.cc | 211 ++++++++++++++++++ src/xenia/base/main_android.cc | 9 +- src/xenia/base/main_android.h | 3 +- src/xenia/ui/windowed_app_context_android.cc | 62 ++++- 8 files changed, 301 insertions(+), 9 deletions(-) create mode 100644 src/xenia/base/cvar_android.cc diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java index 69e0fbe59..9ea50a0ca 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java @@ -14,6 +14,13 @@ import org.jetbrains.annotations.Nullable; import jp.xenia.XeniaRuntimeException; public abstract class WindowedAppActivity extends Activity { + // The EXTRA_CVARS value literal is also used in the native code. + + /** + * Name of the Bundle intent extra containing Xenia config variable launch arguments. + */ + public static final String EXTRA_CVARS = "jp.xenia.emulator.WindowedAppActivity.EXTRA_CVARS"; + static { System.loadLibrary("xenia-app"); } diff --git a/src/xenia/base/console_app_main_android.cc b/src/xenia/base/console_app_main_android.cc index 3e2f79473..8486d4e05 100644 --- a/src/xenia/base/console_app_main_android.cc +++ b/src/xenia/base/console_app_main_android.cc @@ -24,7 +24,8 @@ extern "C" int main(int argc, char** argv) { // Initialize Android globals, including logging. Needs parsed cvars. // TODO(Triang3l): Obtain the actual API level. - xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr); + xe::InitializeAndroidAppFromMainThread(__ANDROID_API__, nullptr, nullptr, + nullptr); std::vector args; for (int n = 0; n < argc; n++) { diff --git a/src/xenia/base/cvar.cc b/src/xenia/base/cvar.cc index 95c7a58f3..219f033ae 100644 --- a/src/xenia/base/cvar.cc +++ b/src/xenia/base/cvar.cc @@ -7,9 +7,7 @@ ****************************************************************************** */ -#include "cvar.h" - -#include "utf8.h" +#include "xenia/base/cvar.h" #define UTF_CPP_CPLUSPLUS 201703L #include "third_party/utfcpp/source/utf8.h" @@ -17,6 +15,7 @@ #include "xenia/base/console.h" #include "xenia/base/logging.h" #include "xenia/base/system.h" +#include "xenia/base/utf8.h" namespace utfcpp = utf8; diff --git a/src/xenia/base/cvar.h b/src/xenia/base/cvar.h index 585039fd6..61b8faf11 100644 --- a/src/xenia/base/cvar.h +++ b/src/xenia/base/cvar.h @@ -20,8 +20,13 @@ #include "third_party/fmt/include/fmt/format.h" #include "xenia/base/assert.h" #include "xenia/base/filesystem.h" +#include "xenia/base/platform.h" #include "xenia/base/string_util.h" +#if XE_PLATFORM_ANDROID +#include +#endif // XE_PLATFORM_ANDROID + namespace cvar { namespace toml { @@ -56,6 +61,7 @@ class CommandVar : virtual public ICommandVar { const std::string& description() const override; void AddToLaunchOptions(cxxopts::Options* options) override; void LoadFromLaunchOptions(cxxopts::ParseResult* result) override; + void SetCommandLineValue(T val); T* current_value() { return current_value_; } protected: @@ -67,7 +73,6 @@ class CommandVar : virtual public ICommandVar { T Convert(std::string val); static std::string ToString(T val); void SetValue(T val); - void SetCommandLineValue(T val); void UpdateValue() override; }; #pragma warning(push) @@ -297,6 +302,9 @@ inline void AddCommandVar(ICommandVar* cv) { void ParseLaunchArguments(int& argc, char**& argv, const std::string_view positional_help, const std::vector& positional_options); +#if XE_PLATFORM_ANDROID +void ParseLaunchArgumentsFromAndroidBundle(jobject bundle); +#endif // XE_PLATFORM_ANDROID template IConfigVar* define_configvar(const char* name, T* default_value, diff --git a/src/xenia/base/cvar_android.cc b/src/xenia/base/cvar_android.cc new file mode 100644 index 000000000..c8916d6be --- /dev/null +++ b/src/xenia/base/cvar_android.cc @@ -0,0 +1,211 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include + +#include "xenia/base/assert.h" +#include "xenia/base/cvar.h" +#include "xenia/base/main_android.h" + +namespace cvar { + +void ParseLaunchArgumentsFromAndroidBundle(jobject bundle) { + if (!ConfigVars) { + return; + } + + JNIEnv* jni_env = xe::GetAndroidThreadJniEnv(); + if (!jni_env) { + return; + } + + jclass bundle_class = jni_env->GetObjectClass(bundle); + if (!bundle_class) { + return; + } + bool bundle_methods_obtained = true; + jmethodID bundle_get_boolean = + jni_env->GetMethodID(bundle_class, "getBoolean", "(Ljava/lang/String;)Z"); + bundle_methods_obtained &= (bundle_get_boolean != nullptr); + jmethodID bundle_get_double = + jni_env->GetMethodID(bundle_class, "getDouble", "(Ljava/lang/String;)D"); + bundle_methods_obtained &= (bundle_get_double != nullptr); + jmethodID bundle_get_int = + jni_env->GetMethodID(bundle_class, "getInt", "(Ljava/lang/String;)I"); + bundle_methods_obtained &= (bundle_get_int != nullptr); + jmethodID bundle_get_long = + jni_env->GetMethodID(bundle_class, "getLong", "(Ljava/lang/String;)J"); + bundle_methods_obtained &= (bundle_get_long != nullptr); + jmethodID bundle_get_string = jni_env->GetMethodID( + bundle_class, "getString", "(Ljava/lang/String;)Ljava/lang/String;"); + bundle_methods_obtained &= (bundle_get_string != nullptr); + jmethodID bundle_key_set_method_id = + jni_env->GetMethodID(bundle_class, "keySet", "()Ljava/util/Set;"); + bundle_methods_obtained &= (bundle_key_set_method_id != nullptr); + if (!bundle_methods_obtained) { + jni_env->DeleteLocalRef(bundle_class); + return; + } + + jobject key_set = jni_env->CallObjectMethod(bundle, bundle_key_set_method_id); + if (!key_set) { + jni_env->DeleteLocalRef(bundle_class); + return; + } + + jclass set_class = jni_env->GetObjectClass(key_set); + if (!set_class) { + jni_env->DeleteLocalRef(key_set); + jni_env->DeleteLocalRef(bundle_class); + return; + } + bool set_methods_obtained = true; + jmethodID set_iterator_method_id = + jni_env->GetMethodID(set_class, "iterator", "()Ljava/util/Iterator;"); + set_methods_obtained &= (set_iterator_method_id != nullptr); + if (!set_methods_obtained) { + jni_env->DeleteLocalRef(set_class); + jni_env->DeleteLocalRef(key_set); + jni_env->DeleteLocalRef(bundle_class); + return; + } + + jobject key_set_iterator = + jni_env->CallObjectMethod(key_set, set_iterator_method_id); + if (!key_set_iterator) { + jni_env->DeleteLocalRef(set_class); + jni_env->DeleteLocalRef(key_set); + jni_env->DeleteLocalRef(bundle_class); + return; + } + + jclass iterator_class = jni_env->GetObjectClass(key_set_iterator); + if (!iterator_class) { + jni_env->DeleteLocalRef(key_set_iterator); + jni_env->DeleteLocalRef(set_class); + jni_env->DeleteLocalRef(key_set); + jni_env->DeleteLocalRef(bundle_class); + return; + } + bool iterator_methods_obtained = true; + jmethodID iterator_has_next = + jni_env->GetMethodID(iterator_class, "hasNext", "()Z"); + iterator_methods_obtained &= (iterator_has_next != nullptr); + jmethodID iterator_next = + jni_env->GetMethodID(iterator_class, "next", "()Ljava/lang/Object;"); + iterator_methods_obtained &= (iterator_next != nullptr); + if (!iterator_methods_obtained) { + jni_env->DeleteLocalRef(iterator_class); + jni_env->DeleteLocalRef(key_set_iterator); + jni_env->DeleteLocalRef(set_class); + jni_env->DeleteLocalRef(key_set); + jni_env->DeleteLocalRef(bundle_class); + return; + } + + while (jni_env->CallBooleanMethod(key_set_iterator, iterator_has_next)) { + jstring key = reinterpret_cast( + jni_env->CallObjectMethod(key_set_iterator, iterator_next)); + if (!key) { + continue; + } + const char* key_utf = jni_env->GetStringUTFChars(key, nullptr); + if (!key_utf) { + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_it = ConfigVars->find(key_utf); + jni_env->ReleaseStringUTFChars(key, key_utf); + // key_utf can't be used from now on. + if (cvar_it == ConfigVars->end()) { + jni_env->DeleteLocalRef(key); + continue; + } + IConfigVar* cvar = cvar_it->second; + auto cvar_bool = dynamic_cast*>(cvar); + if (cvar_bool) { + cvar_bool->SetCommandLineValue( + bool(jni_env->CallBooleanMethod(bundle, bundle_get_boolean, key))); + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_int32 = dynamic_cast*>(cvar); + if (cvar_int32) { + cvar_int32->SetCommandLineValue( + jni_env->CallIntMethod(bundle, bundle_get_int, key)); + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_uint32 = dynamic_cast*>(cvar); + if (cvar_uint32) { + cvar_uint32->SetCommandLineValue( + uint32_t(jni_env->CallIntMethod(bundle, bundle_get_int, key))); + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_uint64 = dynamic_cast*>(cvar); + if (cvar_uint64) { + cvar_uint64->SetCommandLineValue( + uint64_t(jni_env->CallLongMethod(bundle, bundle_get_long, key))); + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_double = dynamic_cast*>(cvar); + if (cvar_double) { + cvar_double->SetCommandLineValue( + jni_env->CallDoubleMethod(bundle, bundle_get_double, key)); + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_string = dynamic_cast*>(cvar); + if (cvar_string) { + jstring cvar_string_value = reinterpret_cast( + jni_env->CallObjectMethod(bundle, bundle_get_string, key)); + if (cvar_string_value) { + const char* cvar_string_value_utf = + jni_env->GetStringUTFChars(cvar_string_value, nullptr); + if (cvar_string_value_utf) { + cvar_string->SetCommandLineValue(cvar_string_value_utf); + jni_env->ReleaseStringUTFChars(cvar_string_value, + cvar_string_value_utf); + } + jni_env->DeleteLocalRef(cvar_string_value); + } + jni_env->DeleteLocalRef(key); + continue; + } + auto cvar_path = dynamic_cast*>(cvar); + if (cvar_path) { + jstring cvar_string_value = reinterpret_cast( + jni_env->CallObjectMethod(bundle, bundle_get_string, key)); + if (cvar_string_value) { + const char* cvar_string_value_utf = + jni_env->GetStringUTFChars(cvar_string_value, nullptr); + if (cvar_string_value_utf) { + cvar_path->SetCommandLineValue(cvar_string_value_utf); + jni_env->ReleaseStringUTFChars(cvar_string_value, + cvar_string_value_utf); + } + jni_env->DeleteLocalRef(cvar_string_value); + } + jni_env->DeleteLocalRef(key); + continue; + } + assert_always("Unsupported type of cvar {}", cvar->name().c_str()); + jni_env->DeleteLocalRef(key); + } + + jni_env->DeleteLocalRef(iterator_class); + jni_env->DeleteLocalRef(key_set_iterator); + jni_env->DeleteLocalRef(set_class); + jni_env->DeleteLocalRef(key_set); + jni_env->DeleteLocalRef(bundle_class); +} + +} // namespace cvar diff --git a/src/xenia/base/main_android.cc b/src/xenia/base/main_android.cc index a44ca7d3b..eaa4642c3 100644 --- a/src/xenia/base/main_android.cc +++ b/src/xenia/base/main_android.cc @@ -15,6 +15,7 @@ #include #include "xenia/base/assert.h" +#include "xenia/base/cvar.h" #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/system.h" @@ -44,7 +45,8 @@ static void AndroidThreadJNIEnvDestructor(void* jni_env_pointer) { void InitializeAndroidAppFromMainThread(int32_t api_level, JNIEnv* main_thread_jni_env, - jobject application_context) { + jobject application_context, + jobject launch_arguments_bundle) { if (android_initializations_++) { // Already initialized for another component in the process. return; @@ -96,6 +98,11 @@ void InitializeAndroidAppFromMainThread(int32_t api_level, // Logging uses threading. xe::threading::AndroidInitialize(); + // Initialize the cvars before logging. + if (launch_arguments_bundle) { + cvar::ParseLaunchArgumentsFromAndroidBundle(launch_arguments_bundle); + } + // Multiple apps can be launched within one process - don't pass the actual // app name. xe::InitializeLogging("xenia"); diff --git a/src/xenia/base/main_android.h b/src/xenia/base/main_android.h index e74a82ffe..a715e092b 100644 --- a/src/xenia/base/main_android.h +++ b/src/xenia/base/main_android.h @@ -31,7 +31,8 @@ namespace xe { // must be called in `main`, with a null main thread JNI environment. void InitializeAndroidAppFromMainThread(int32_t api_level, JNIEnv* main_thread_jni_env, - jobject application_context); + jobject application_context, + jobject launch_arguments_bundle); void ShutdownAndroidAppFromMainThread(); // May be the minimum supported level if the initialization was done without a diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc index d25ed97ee..6ff094935 100644 --- a/src/xenia/ui/windowed_app_context_android.cc +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -230,7 +230,7 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, return false; } - // Get the application context. + // Get activity methods needed for initialization. jmethodID activity_get_application_context = ui_thread_jni_env_->GetMethodID( activity_class_, "getApplicationContext", "()Landroid/content/Context;"); if (!activity_get_application_context) { @@ -240,6 +240,16 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, Shutdown(); return false; } + jmethodID activity_get_intent = ui_thread_jni_env_->GetMethodID( + activity_class_, "getIntent", "()Landroid/content/Intent;"); + if (!activity_get_intent) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to get the getIntent method of the activity"); + Shutdown(); + return false; + } + + // Get the application context. jobject application_context_init_ref = ui_thread_jni_env_->CallObjectMethod( activity, activity_get_application_context); if (!application_context_init_ref) { @@ -250,11 +260,59 @@ bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, return false; } + // Get the launch arguments. + jobject launch_arguments_bundle_init_ref = nullptr; + { + jobject intent_init_ref = + ui_thread_jni_env_->CallObjectMethod(activity, activity_get_intent); + if (!intent_init_ref) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to get the intent that has started the activity"); + } else { + jclass intent_class = ui_thread_jni_env_->GetObjectClass(intent_init_ref); + if (!intent_class) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to get the intent class"); + } else { + jmethodID intent_get_bundle_extra = ui_thread_jni_env_->GetMethodID( + intent_class, "getBundleExtra", + "(Ljava/lang/String;)Landroid/os/Bundle;"); + if (!intent_get_bundle_extra) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to get the getBundleExtra method of the intent"); + } else { + jstring launch_arguments_extra_name = + ui_thread_jni_env_->NewStringUTF( + "jp.xenia.emulator.WindowedAppActivity.EXTRA_CVARS"); + if (!launch_arguments_extra_name) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to create the launch arguments intent extra data name " + "string"); + } else { + launch_arguments_bundle_init_ref = + ui_thread_jni_env_->CallObjectMethod( + intent_init_ref, intent_get_bundle_extra, + launch_arguments_extra_name); + ui_thread_jni_env_->DeleteLocalRef(launch_arguments_extra_name); + } + } + ui_thread_jni_env_->DeleteLocalRef(intent_class); + } + ui_thread_jni_env_->DeleteLocalRef(intent_init_ref); + } + } + // Initialize Xenia globals that may depend on the base globals and logging. xe::InitializeAndroidAppFromMainThread( AConfiguration_getSdkVersion(configuration_), ui_thread_jni_env_, - application_context_init_ref); + application_context_init_ref, launch_arguments_bundle_init_ref); android_base_initialized_ = true; + if (launch_arguments_bundle_init_ref) { + ui_thread_jni_env_->DeleteLocalRef(launch_arguments_bundle_init_ref); + } ui_thread_jni_env_->DeleteLocalRef(application_context_init_ref); // Initialize common windowed app JNI IDs. From 500bbe9e0d69e4c62bef0e81b3aa4097c09503f5 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 16 Jul 2022 13:42:04 +0300 Subject: [PATCH 09/20] [Base] Use to_path for Android path argument loading --- src/xenia/base/cvar_android.cc | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/xenia/base/cvar_android.cc b/src/xenia/base/cvar_android.cc index c8916d6be..7af6ff5a9 100644 --- a/src/xenia/base/cvar_android.cc +++ b/src/xenia/base/cvar_android.cc @@ -11,6 +11,7 @@ #include "xenia/base/assert.h" #include "xenia/base/cvar.h" +#include "xenia/base/filesystem.h" #include "xenia/base/main_android.h" namespace cvar { @@ -188,7 +189,7 @@ void ParseLaunchArgumentsFromAndroidBundle(jobject bundle) { const char* cvar_string_value_utf = jni_env->GetStringUTFChars(cvar_string_value, nullptr); if (cvar_string_value_utf) { - cvar_path->SetCommandLineValue(cvar_string_value_utf); + cvar_path->SetCommandLineValue(xe::to_path(cvar_string_value_utf)); jni_env->ReleaseStringUTFChars(cvar_string_value, cvar_string_value_utf); } From 34a952d78974b1ee245a08b25f6763b556cc89e4 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 17 Jul 2022 16:14:29 +0300 Subject: [PATCH 10/20] [Base] Wrap strdup and strcasecmp in xe:: functions --- src/xenia/base/platform.h | 3 --- src/xenia/base/string.cc | 31 ++++++++++++++++++++++ src/xenia/base/string.h | 6 ++++- src/xenia/cpu/backend/x64/x64_assembler.cc | 3 ++- src/xenia/cpu/backend/x64/x64_sequences.cc | 4 +-- src/xenia/cpu/ppc/ppc_hir_builder.cc | 21 ++++++++------- src/xenia/cpu/ppc/ppc_translator.cc | 7 ++--- src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc | 4 +-- src/xenia/vfs/devices/null_device.cc | 3 ++- 9 files changed, 59 insertions(+), 23 deletions(-) diff --git a/src/xenia/base/platform.h b/src/xenia/base/platform.h index 04e678bd4..439d0c467 100644 --- a/src/xenia/base/platform.h +++ b/src/xenia/base/platform.h @@ -67,9 +67,6 @@ #endif #if XE_PLATFORM_WIN32 -#define strdup _strdup -#define strcasecmp _stricmp -#define strncasecmp _strnicmp #define WIN32_LEAN_AND_MEAN #define NOMINMAX // Don't want windows.h including min/max macros. #endif // XE_PLATFORM_WIN32 diff --git a/src/xenia/base/string.cc b/src/xenia/base/string.cc index 4fdeb3d0c..38b0ccf5e 100644 --- a/src/xenia/base/string.cc +++ b/src/xenia/base/string.cc @@ -9,9 +9,16 @@ #include "xenia/base/string.h" +#include #include #include +#include "xenia/base/platform.h" + +#if !XE_PLATFORM_WIN32 +#include +#endif // !XE_PLATFORM_WIN32 + #define UTF_CPP_CPLUSPLUS 201703L #include "third_party/utfcpp/source/utf8.h" @@ -19,6 +26,30 @@ namespace utfcpp = utf8; namespace xe { +int xe_strcasecmp(const char* string1, const char* string2) { +#if XE_PLATFORM_WIN32 + return _stricmp(string1, string2); +#else + return strcasecmp(string1, string2); +#endif // XE_PLATFORM_WIN32 +} + +int xe_strncasecmp(const char* string1, const char* string2, size_t count) { +#if XE_PLATFORM_WIN32 + return _strnicmp(string1, string2, count); +#else + return strncasecmp(string1, string2, count); +#endif // XE_PLATFORM_WIN32 +} + +char* xe_strdup(const char* source) { +#if XE_PLATFORM_WIN32 + return _strdup(source); +#else + return strdup(source); +#endif // XE_PLATFORM_WIN32 +} + std::string to_utf8(const std::u16string_view source) { return utfcpp::utf16to8(source); } diff --git a/src/xenia/base/string.h b/src/xenia/base/string.h index bed95dce0..b768d22e6 100644 --- a/src/xenia/base/string.h +++ b/src/xenia/base/string.h @@ -12,10 +12,14 @@ #include -#include "utf8.h" +#include "xenia/base/utf8.h" namespace xe { +int xe_strcasecmp(const char* string1, const char* string2); +int xe_strncasecmp(const char* string1, const char* string2, size_t count); +char* xe_strdup(const char* source); + std::string to_utf8(const std::u16string_view source); std::u16string to_utf16(const std::string_view source); diff --git a/src/xenia/cpu/backend/x64/x64_assembler.cc b/src/xenia/cpu/backend/x64/x64_assembler.cc index 4c50a5177..8643c4fb0 100644 --- a/src/xenia/cpu/backend/x64/x64_assembler.cc +++ b/src/xenia/cpu/backend/x64/x64_assembler.cc @@ -15,6 +15,7 @@ #include "third_party/capstone/include/capstone/x86.h" #include "xenia/base/profiling.h" #include "xenia/base/reset_scope.h" +#include "xenia/base/string.h" #include "xenia/cpu/backend/x64/x64_backend.h" #include "xenia/cpu/backend/x64/x64_code_cache.h" #include "xenia/cpu/backend/x64/x64_emitter.h" @@ -86,7 +87,7 @@ bool X64Assembler::Assemble(GuestFunction* function, HIRBuilder* builder, if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmMachineCode) { DumpMachineCode(machine_code, code_size, function->source_map(), &string_buffer_); - debug_info->set_machine_code_disasm(strdup(string_buffer_.buffer())); + debug_info->set_machine_code_disasm(xe_strdup(string_buffer_.buffer())); string_buffer_.Reset(); } diff --git a/src/xenia/cpu/backend/x64/x64_sequences.cc b/src/xenia/cpu/backend/x64/x64_sequences.cc index 34cef4f7d..c7ef4361b 100644 --- a/src/xenia/cpu/backend/x64/x64_sequences.cc +++ b/src/xenia/cpu/backend/x64/x64_sequences.cc @@ -25,12 +25,12 @@ #include "xenia/cpu/backend/x64/x64_sequences.h" #include -#include #include #include "xenia/base/assert.h" #include "xenia/base/clock.h" #include "xenia/base/logging.h" +#include "xenia/base/string.h" #include "xenia/base/threading.h" #include "xenia/cpu/backend/x64/x64_emitter.h" #include "xenia/cpu/backend/x64/x64_op.h" @@ -63,7 +63,7 @@ struct COMMENT : Sequence> { auto str = reinterpret_cast(i.src1.value); // TODO(benvanik): pass through. // TODO(benvanik): don't just leak this memory. - auto str_copy = strdup(str); + auto str_copy = xe_strdup(str); e.mov(e.rdx, reinterpret_cast(str_copy)); e.CallNative(reinterpret_cast(TraceString)); } diff --git a/src/xenia/cpu/ppc/ppc_hir_builder.cc b/src/xenia/cpu/ppc/ppc_hir_builder.cc index 460da0894..4aedfdd26 100644 --- a/src/xenia/cpu/ppc/ppc_hir_builder.cc +++ b/src/xenia/cpu/ppc/ppc_hir_builder.cc @@ -19,6 +19,7 @@ #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/profiling.h" +#include "xenia/base/string.h" #include "xenia/cpu/cpu_flags.h" #include "xenia/cpu/hir/label.h" #include "xenia/cpu/ppc/ppc_context.h" @@ -215,25 +216,25 @@ void PPCHIRBuilder::MaybeBreakOnInstruction(uint32_t address) { auto op = cvars::break_condition_op.c_str(); // TODO(rick): table? - if (strcasecmp(op, "eq") == 0) { + if (xe_strcasecmp(op, "eq") == 0) { TrapTrue(CompareEQ(left, right)); - } else if (strcasecmp(op, "ne") == 0) { + } else if (xe_strcasecmp(op, "ne") == 0) { TrapTrue(CompareNE(left, right)); - } else if (strcasecmp(op, "slt") == 0) { + } else if (xe_strcasecmp(op, "slt") == 0) { TrapTrue(CompareSLT(left, right)); - } else if (strcasecmp(op, "sle") == 0) { + } else if (xe_strcasecmp(op, "sle") == 0) { TrapTrue(CompareSLE(left, right)); - } else if (strcasecmp(op, "sgt") == 0) { + } else if (xe_strcasecmp(op, "sgt") == 0) { TrapTrue(CompareSGT(left, right)); - } else if (strcasecmp(op, "sge") == 0) { + } else if (xe_strcasecmp(op, "sge") == 0) { TrapTrue(CompareSGE(left, right)); - } else if (strcasecmp(op, "ult") == 0) { + } else if (xe_strcasecmp(op, "ult") == 0) { TrapTrue(CompareULT(left, right)); - } else if (strcasecmp(op, "ule") == 0) { + } else if (xe_strcasecmp(op, "ule") == 0) { TrapTrue(CompareULE(left, right)); - } else if (strcasecmp(op, "ugt") == 0) { + } else if (xe_strcasecmp(op, "ugt") == 0) { TrapTrue(CompareUGT(left, right)); - } else if (strcasecmp(op, "uge") == 0) { + } else if (xe_strcasecmp(op, "uge") == 0) { TrapTrue(CompareUGE(left, right)); } else { assert_always(); diff --git a/src/xenia/cpu/ppc/ppc_translator.cc b/src/xenia/cpu/ppc/ppc_translator.cc index 708a4b608..99c258a32 100644 --- a/src/xenia/cpu/ppc/ppc_translator.cc +++ b/src/xenia/cpu/ppc/ppc_translator.cc @@ -14,6 +14,7 @@ #include "xenia/base/memory.h" #include "xenia/base/profiling.h" #include "xenia/base/reset_scope.h" +#include "xenia/base/string.h" #include "xenia/cpu/compiler/compiler_passes.h" #include "xenia/cpu/cpu_flags.h" #include "xenia/cpu/ppc/ppc_frontend.h" @@ -155,7 +156,7 @@ bool PPCTranslator::Translate(GuestFunction* function, // Stash source. if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmSource) { DumpSource(function, &string_buffer_); - debug_info->set_source_disasm(strdup(string_buffer_.buffer())); + debug_info->set_source_disasm(xe_strdup(string_buffer_.buffer())); string_buffer_.Reset(); } @@ -171,7 +172,7 @@ bool PPCTranslator::Translate(GuestFunction* function, // Stash raw HIR. if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmRawHir) { builder_->Dump(&string_buffer_); - debug_info->set_raw_hir_disasm(strdup(string_buffer_.buffer())); + debug_info->set_raw_hir_disasm(xe_strdup(string_buffer_.buffer())); string_buffer_.Reset(); } @@ -183,7 +184,7 @@ bool PPCTranslator::Translate(GuestFunction* function, // Stash optimized HIR. if (debug_info_flags & DebugInfoFlags::kDebugInfoDisasmHir) { builder_->Dump(&string_buffer_); - debug_info->set_hir_disasm(strdup(string_buffer_.buffer())); + debug_info->set_hir_disasm(xe_strdup(string_buffer_.buffer())); string_buffer_.Reset(); } diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc index db8421a64..db7f72ea4 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_rtl.cc @@ -107,7 +107,7 @@ DECLARE_XBOXKRNL_EXPORT1(RtlLowerChar, kNone, kImplemented); dword_result_t RtlCompareString_entry(lpstring_t string_1, lpstring_t string_2, dword_t case_insensitive) { - int ret = case_insensitive ? strcasecmp(string_1, string_2) + int ret = case_insensitive ? xe_strcasecmp(string_1, string_2) : std::strcmp(string_1, string_2); return ret; @@ -130,7 +130,7 @@ dword_result_t RtlCompareStringN_entry(lpstring_t string_1, } auto len = std::min(string_1_len, string_2_len); - int ret = case_insensitive ? strncasecmp(string_1, string_2, len) + int ret = case_insensitive ? xe_strncasecmp(string_1, string_2, len) : std::strncmp(string_1, string_2, len); return ret; diff --git a/src/xenia/vfs/devices/null_device.cc b/src/xenia/vfs/devices/null_device.cc index e7b014d7b..ef34fd833 100644 --- a/src/xenia/vfs/devices/null_device.cc +++ b/src/xenia/vfs/devices/null_device.cc @@ -12,6 +12,7 @@ #include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" +#include "xenia/base/string.h" #include "xenia/kernel/xfile.h" #include "xenia/vfs/devices/null_entry.h" @@ -50,7 +51,7 @@ Entry* NullDevice::ResolvePath(const std::string_view path) { } for (auto& child : root->children()) { - if (!strcasecmp(child->path().c_str(), path.data())) { + if (!xe_strcasecmp(child->path().c_str(), path.data())) { return child.get(); } } From 93a7918025b61d7268db154deeddc3def8b1c0d5 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 17 Jul 2022 16:25:58 +0300 Subject: [PATCH 11/20] [Base] Android content URI file descriptor opening --- src/xenia/base/filesystem.h | 10 + src/xenia/base/filesystem_android.cc | 278 +++++++++++++++++++++++++++ src/xenia/base/main_android.cc | 5 + 3 files changed, 293 insertions(+) create mode 100644 src/xenia/base/filesystem_android.cc diff --git a/src/xenia/base/filesystem.h b/src/xenia/base/filesystem.h index c8cff150f..cca30dd3f 100644 --- a/src/xenia/base/filesystem.h +++ b/src/xenia/base/filesystem.h @@ -14,8 +14,10 @@ #include #include #include +#include #include +#include "xenia/base/platform.h" #include "xenia/base/string.h" namespace xe { @@ -122,6 +124,14 @@ struct FileInfo { bool GetInfo(const std::filesystem::path& path, FileInfo* out_info); std::vector ListFiles(const std::filesystem::path& path); +#if XE_PLATFORM_ANDROID +void AndroidInitialize(); +void AndroidShutdown(); +bool IsAndroidContentUri(const std::string_view source); +int OpenAndroidContentFileDescriptor(const std::string_view uri, + const char* mode); +#endif // XE_PLATFORM_ANDROID + } // namespace filesystem } // namespace xe diff --git a/src/xenia/base/filesystem_android.cc b/src/xenia/base/filesystem_android.cc new file mode 100644 index 000000000..12f5c2d7d --- /dev/null +++ b/src/xenia/base/filesystem_android.cc @@ -0,0 +1,278 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2022 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include +#include +#include + +#include "xenia/base/filesystem.h" +#include "xenia/base/main_android.h" +#include "xenia/base/math.h" +#include "xenia/base/string.h" + +namespace xe { +namespace filesystem { + +// Using Android logging because the file system may need to be initialized +// before logging. + +static jobject android_content_resolver_; +static jclass android_content_resolver_class_; +static jmethodID android_content_resolver_open_file_descriptor_; + +static jclass android_parcel_file_descriptor_class_; +static jmethodID android_parcel_file_descriptor_detach_fd_; + +static jclass android_uri_class_; +static jmethodID android_uri_parse_; + +static bool android_content_resolver_initialized_; + +static void AndroidShutdownContentResolver() { + android_content_resolver_initialized_ = false; + android_uri_parse_ = nullptr; + android_parcel_file_descriptor_detach_fd_ = nullptr; + android_content_resolver_open_file_descriptor_ = nullptr; + JNIEnv* jni_env = GetAndroidThreadJniEnv(); + if (jni_env) { + if (android_uri_class_) { + jni_env->DeleteGlobalRef(android_uri_class_); + } + if (android_parcel_file_descriptor_class_) { + jni_env->DeleteGlobalRef( + reinterpret_cast(android_parcel_file_descriptor_class_)); + } + if (android_content_resolver_class_) { + jni_env->DeleteGlobalRef( + reinterpret_cast(android_content_resolver_class_)); + } + if (android_content_resolver_) { + jni_env->DeleteGlobalRef( + reinterpret_cast(android_content_resolver_)); + } + } + android_uri_class_ = nullptr; + android_parcel_file_descriptor_class_ = nullptr; + android_content_resolver_class_ = nullptr; + android_content_resolver_ = nullptr; +} + +static void AndroidInitializeContentResolver() { + JNIEnv* jni_env = GetAndroidThreadJniEnv(); + if (!jni_env) { + return; + } + jobject application_context = GetAndroidApplicationContext(); + if (!application_context) { + return; + } + { + jclass context_class = jni_env->GetObjectClass(application_context); + if (!context_class) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the context class"); + AndroidShutdownContentResolver(); + return; + } + jmethodID context_get_content_resolver = + jni_env->GetMethodID(context_class, "getContentResolver", + "()Landroid/content/ContentResolver;"); + if (!context_get_content_resolver) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the getContentResolver method of the context"); + jni_env->DeleteLocalRef(reinterpret_cast(context_class)); + AndroidShutdownContentResolver(); + return; + } + jobject content_resolver = jni_env->CallObjectMethod( + application_context, context_get_content_resolver); + jni_env->DeleteLocalRef(reinterpret_cast(context_class)); + if (!content_resolver) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the content resolver"); + AndroidShutdownContentResolver(); + return; + } + android_content_resolver_ = jni_env->NewGlobalRef(content_resolver); + jni_env->DeleteLocalRef(content_resolver); + } + if (!android_content_resolver_) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to create a global reference to the content resolver"); + AndroidShutdownContentResolver(); + return; + } + { + jobject content_resolver_class = + jni_env->GetObjectClass(android_content_resolver_); + if (!content_resolver_class) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the content resolver class"); + AndroidShutdownContentResolver(); + return; + } + android_content_resolver_class_ = + reinterpret_cast(jni_env->NewGlobalRef( + reinterpret_cast(content_resolver_class))); + jni_env->DeleteLocalRef(reinterpret_cast(content_resolver_class)); + } + if (!android_content_resolver_class_) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to create a global reference to the content resolver class"); + AndroidShutdownContentResolver(); + return; + } + android_content_resolver_open_file_descriptor_ = jni_env->GetMethodID( + android_content_resolver_class_, "openFileDescriptor", + "(Landroid/net/Uri;Ljava/lang/String;)Landroid/os/ParcelFileDescriptor;"); + if (!android_content_resolver_open_file_descriptor_) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the openFileDescriptor method of the content resolver"); + AndroidShutdownContentResolver(); + return; + } + + { + jclass parcel_file_descriptor_class = + jni_env->FindClass("android/os/ParcelFileDescriptor"); + if (!parcel_file_descriptor_class) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the parcel file descriptor class"); + AndroidShutdownContentResolver(); + return; + } + android_parcel_file_descriptor_class_ = + reinterpret_cast(jni_env->NewGlobalRef( + reinterpret_cast(parcel_file_descriptor_class))); + jni_env->DeleteLocalRef( + reinterpret_cast(parcel_file_descriptor_class)); + } + if (!android_parcel_file_descriptor_class_) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to create a global reference to the parcel file descriptor " + "class"); + AndroidShutdownContentResolver(); + return; + } + android_parcel_file_descriptor_detach_fd_ = jni_env->GetMethodID( + android_parcel_file_descriptor_class_, "detachFd", "()I"); + if (!android_parcel_file_descriptor_detach_fd_) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the detachFd method of the parcel file descriptor"); + AndroidShutdownContentResolver(); + return; + } + + { + jclass uri_class = jni_env->FindClass("android/net/Uri"); + if (!uri_class) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the URI class"); + AndroidShutdownContentResolver(); + return; + } + android_uri_class_ = reinterpret_cast( + jni_env->NewGlobalRef(reinterpret_cast(uri_class))); + jni_env->DeleteLocalRef(reinterpret_cast(uri_class)); + } + if (!android_uri_class_) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to create a global reference to the URI class"); + AndroidShutdownContentResolver(); + return; + } + android_uri_parse_ = jni_env->GetStaticMethodID( + android_uri_class_, "parse", "(Ljava/lang/String;)Landroid/net/Uri;"); + if (!android_uri_parse_) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidInitializeContentResolver", + "Failed to get the parse method of the URI class"); + AndroidShutdownContentResolver(); + return; + } + + android_content_resolver_initialized_ = true; +} + +void AndroidInitialize() { AndroidInitializeContentResolver(); } + +void AndroidShutdown() { AndroidShutdownContentResolver(); } + +bool IsAndroidContentUri(const std::string_view source) { + // A URI schema is case-insensitive. Though just content: defines the schema, + // still including // in the comparison to distinguish from a file with a name + // starting from content: (as this is the main purpose of this code - + // separating URIs from file paths) more clearly. + static const char kContentSchema[] = "content://"; + constexpr size_t kContentSchemaLength = xe::countof(kContentSchema) - 1; + return source.size() >= kContentSchemaLength && + !xe_strncasecmp(source.data(), kContentSchema, kContentSchemaLength); +} + +int OpenAndroidContentFileDescriptor(const std::string_view uri, + const char* mode) { + if (!android_content_resolver_initialized_) { + return -1; + } + JNIEnv* jni_env = GetAndroidThreadJniEnv(); + if (!jni_env) { + return -1; + } + + jobject uri_object; + { + jstring uri_string; + { + std::u16string uri_u16 = xe::to_utf16(uri); + uri_string = jni_env->NewString( + reinterpret_cast(uri_u16.data()), uri_u16.size()); + } + if (!uri_string) { + return -1; + } + uri_object = jni_env->CallStaticObjectMethod( + android_uri_class_, android_uri_parse_, uri_string); + jni_env->DeleteLocalRef(uri_string); + } + if (!uri_object) { + return -1; + } + + jstring mode_string = jni_env->NewStringUTF(mode); + if (!mode_string) { + jni_env->DeleteLocalRef(uri_object); + return -1; + } + + jobject parcel_file_descriptor = jni_env->CallObjectMethod( + android_content_resolver_, android_content_resolver_open_file_descriptor_, + uri_object, mode_string); + jni_env->DeleteLocalRef(mode_string); + jni_env->DeleteLocalRef(uri_object); + if (jni_env->ExceptionCheck()) { + jni_env->ExceptionClear(); + return -1; + } + if (!parcel_file_descriptor) { + return -1; + } + int file_descriptor = jni_env->CallIntMethod( + parcel_file_descriptor, android_parcel_file_descriptor_detach_fd_); + jni_env->DeleteLocalRef(parcel_file_descriptor); + + return file_descriptor; +} + +} // namespace filesystem +} // namespace xe diff --git a/src/xenia/base/main_android.cc b/src/xenia/base/main_android.cc index eaa4642c3..5e43dbcad 100644 --- a/src/xenia/base/main_android.cc +++ b/src/xenia/base/main_android.cc @@ -16,6 +16,7 @@ #include "xenia/base/assert.h" #include "xenia/base/cvar.h" +#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/memory.h" #include "xenia/base/system.h" @@ -98,6 +99,8 @@ void InitializeAndroidAppFromMainThread(int32_t api_level, // Logging uses threading. xe::threading::AndroidInitialize(); + xe::filesystem::AndroidInitialize(); + // Initialize the cvars before logging. if (launch_arguments_bundle) { cvar::ParseLaunchArgumentsFromAndroidBundle(launch_arguments_bundle); @@ -135,6 +138,8 @@ void ShutdownAndroidAppFromMainThread() { xe::ShutdownLogging(); + xe::filesystem::AndroidShutdown(); + xe::threading::AndroidShutdown(); if (android_application_context_) { From 624f2b2d9e6ac7511015d89bbef62e5a82f26346 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 17 Jul 2022 16:34:17 +0300 Subject: [PATCH 12/20] [Base] Android content URI file memory mapping --- src/xenia/base/mapped_memory.h | 24 ++++--- src/xenia/base/mapped_memory_posix.cc | 81 ++++++++++++++++------- src/xenia/base/mapped_memory_win.cc | 5 +- src/xenia/vfs/devices/disc_image_entry.cc | 2 +- 4 files changed, 73 insertions(+), 39 deletions(-) diff --git a/src/xenia/base/mapped_memory.h b/src/xenia/base/mapped_memory.h index 6a58ab4c4..ce89b651a 100644 --- a/src/xenia/base/mapped_memory.h +++ b/src/xenia/base/mapped_memory.h @@ -12,7 +12,9 @@ #include #include -#include +#include + +#include "xenia/base/platform.h" namespace xe { @@ -26,21 +28,25 @@ class MappedMemory { static std::unique_ptr Open(const std::filesystem::path& path, Mode mode, size_t offset = 0, size_t length = 0); +#if XE_PLATFORM_ANDROID + static std::unique_ptr OpenForAndroidContentUri( + const std::string_view uri, Mode mode, size_t offset = 0, + size_t length = 0); +#endif // XE_PLATFORM_ANDROID - MappedMemory(const std::filesystem::path& path, Mode mode) - : path_(path), mode_(mode), data_(nullptr), size_(0) {} - MappedMemory(const std::filesystem::path& path, Mode mode, void* data, - size_t size) - : path_(path), mode_(mode), data_(data), size_(size) {} + MappedMemory() : data_(nullptr), size_(0) {} + MappedMemory(void* data, size_t size) : data_(data), size_(size) {} MappedMemory(const MappedMemory& mapped_memory) = delete; MappedMemory& operator=(const MappedMemory& mapped_memory) = delete; MappedMemory(MappedMemory&& mapped_memory) = delete; MappedMemory& operator=(MappedMemory&& mapped_memory) = delete; virtual ~MappedMemory() = default; - std::unique_ptr Slice(Mode mode, size_t offset, size_t length) { + // The mapping is still backed by the object the slice was created from, a + // slice is not owning. + std::unique_ptr Slice(size_t offset, size_t length) { return std::unique_ptr( - new MappedMemory(path_, mode, data() + offset, length)); + new MappedMemory(data() + offset, length)); } uint8_t* data() const { return reinterpret_cast(data_); } @@ -54,8 +60,6 @@ class MappedMemory { virtual bool Remap(size_t offset, size_t length) { return false; } protected: - std::filesystem::path path_; - Mode mode_; void* data_; size_t size_; }; diff --git a/src/xenia/base/mapped_memory_posix.cc b/src/xenia/base/mapped_memory_posix.cc index 6f89ffe43..330ced53f 100644 --- a/src/xenia/base/mapped_memory_posix.cc +++ b/src/xenia/base/mapped_memory_posix.cc @@ -15,16 +15,15 @@ #include #include -#include "xenia/base/string.h" +#include "xenia/base/filesystem.h" +#include "xenia/base/platform.h" namespace xe { class PosixMappedMemory : public MappedMemory { public: - PosixMappedMemory(const std::filesystem::path& path, Mode mode, void* data, - size_t size, int file_descriptor) - : MappedMemory(path, mode, data, size), - file_descriptor_(file_descriptor) {} + PosixMappedMemory(void* data, size_t size, int file_descriptor) + : MappedMemory(data, size), file_descriptor_(file_descriptor) {} ~PosixMappedMemory() override { if (data_) { @@ -35,6 +34,39 @@ class PosixMappedMemory : public MappedMemory { } } + static std::unique_ptr WrapFileDescriptor( + int file_descriptor, Mode mode, size_t offset = 0, size_t length = 0) { + int protection = 0; + switch (mode) { + case Mode::kRead: + protection |= PROT_READ; + break; + case Mode::kReadWrite: + protection |= PROT_READ | PROT_WRITE; + break; + } + + size_t map_length = length; + if (!length) { + struct stat64 file_stat; + if (fstat64(file_descriptor, &file_stat)) { + close(file_descriptor); + return nullptr; + } + map_length = size_t(file_stat.st_size); + } + + void* data = + mmap(0, map_length, protection, MAP_SHARED, file_descriptor, offset); + if (!data) { + close(file_descriptor); + return nullptr; + } + + return std::make_unique(data, map_length, + file_descriptor); + } + void Close(uint64_t truncate_size) override { if (data_) { munmap(data_, size()); @@ -59,42 +91,43 @@ std::unique_ptr MappedMemory::Open( const std::filesystem::path& path, Mode mode, size_t offset, size_t length) { int open_flags = 0; - int prot; switch (mode) { case Mode::kRead: open_flags |= O_RDONLY; - prot = PROT_READ; break; case Mode::kReadWrite: open_flags |= O_RDWR; - prot = PROT_READ | PROT_WRITE; break; } - int file_descriptor = open(path.c_str(), open_flags); if (file_descriptor < 0) { return nullptr; } + return PosixMappedMemory::WrapFileDescriptor(file_descriptor, mode, offset, + length); +} - size_t map_length = length; - if (!length) { - struct stat64 file_stat; - if (fstat64(file_descriptor, &file_stat)) { - close(file_descriptor); - return nullptr; - } - map_length = size_t(file_stat.st_size); +#if XE_PLATFORM_ANDROID +std::unique_ptr MappedMemory::OpenForAndroidContentUri( + const std::string_view uri, Mode mode, size_t offset, size_t length) { + const char* open_mode = nullptr; + switch (mode) { + case Mode::kRead: + open_mode = "r"; + break; + case Mode::kReadWrite: + open_mode = "rw"; + break; } - - void* data = mmap(0, map_length, prot, MAP_SHARED, file_descriptor, offset); - if (!data) { - close(file_descriptor); + int file_descriptor = + xe::filesystem::OpenAndroidContentFileDescriptor(uri, open_mode); + if (file_descriptor < 0) { return nullptr; } - - return std::unique_ptr( - new PosixMappedMemory(path, mode, data, map_length, file_descriptor)); + return PosixMappedMemory::WrapFileDescriptor(file_descriptor, mode, offset, + length); } +#endif // XE_PLATFORM_ANDROID std::unique_ptr ChunkedMappedMemoryWriter::Open( const std::filesystem::path& path, size_t chunk_size, diff --git a/src/xenia/base/mapped_memory_win.cc b/src/xenia/base/mapped_memory_win.cc index 79e94eeaf..f0af5ee7b 100644 --- a/src/xenia/base/mapped_memory_win.cc +++ b/src/xenia/base/mapped_memory_win.cc @@ -32,9 +32,6 @@ class Win32MappedMemory : public MappedMemory { // CreateFileMapping returns nullptr in case of failure. static constexpr HANDLE kMappingHandleInvalid = nullptr; - Win32MappedMemory(const std::filesystem::path& path, Mode mode) - : MappedMemory(path, mode) {} - ~Win32MappedMemory() override { if (data_) { UnmapViewOfFile(data_); @@ -135,7 +132,7 @@ std::unique_ptr MappedMemory::Open( offset & ~static_cast(system_info.dwAllocationGranularity - 1); const size_t aligned_length = length + (offset - aligned_offset); - auto mm = std::make_unique(path, mode); + auto mm = std::make_unique(); mm->view_access_ = view_access; mm->file_handle = CreateFile(path.c_str(), file_access, file_share, nullptr, diff --git a/src/xenia/vfs/devices/disc_image_entry.cc b/src/xenia/vfs/devices/disc_image_entry.cc index e8450bd73..0d251dff5 100644 --- a/src/xenia/vfs/devices/disc_image_entry.cc +++ b/src/xenia/vfs/devices/disc_image_entry.cc @@ -48,7 +48,7 @@ std::unique_ptr DiscImageEntry::OpenMapped( size_t real_offset = data_offset_ + offset; size_t real_length = length ? std::min(length, data_size_) : data_size_; - return mmap_->Slice(mode, real_offset, real_length); + return mmap_->Slice(real_offset, real_length); } } // namespace vfs From 25663827ba57005d7a388b1a69d73200a83601eb Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 17 Jul 2022 16:37:49 +0300 Subject: [PATCH 13/20] [GPU] Trace viewer Android content URI loading --- src/xenia/gpu/trace_dump.cc | 3 ++- src/xenia/gpu/trace_reader.cc | 14 ++++++++++++-- src/xenia/gpu/trace_reader.h | 4 ++-- src/xenia/gpu/trace_viewer.cc | 25 +++++++++++++++---------- src/xenia/gpu/trace_viewer.h | 4 ++-- 5 files changed, 33 insertions(+), 17 deletions(-) diff --git a/src/xenia/gpu/trace_dump.cc b/src/xenia/gpu/trace_dump.cc index ec4e85cea..4cf774c42 100644 --- a/src/xenia/gpu/trace_dump.cc +++ b/src/xenia/gpu/trace_dump.cc @@ -10,6 +10,7 @@ #include "xenia/gpu/trace_dump.h" #include "third_party/stb/stb_image_write.h" +#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/profiling.h" #include "xenia/base/string.h" @@ -109,7 +110,7 @@ bool TraceDump::Setup() { bool TraceDump::Load(const std::filesystem::path& trace_file_path) { trace_file_path_ = trace_file_path; - if (!player_->Open(trace_file_path_)) { + if (!player_->Open(xe::path_to_utf8(trace_file_path_))) { XELOGE("Could not load trace file"); return false; } diff --git a/src/xenia/gpu/trace_reader.cc b/src/xenia/gpu/trace_reader.cc index 6c20c79ec..5d535ad8a 100644 --- a/src/xenia/gpu/trace_reader.cc +++ b/src/xenia/gpu/trace_reader.cc @@ -16,6 +16,7 @@ #include "xenia/base/logging.h" #include "xenia/base/mapped_memory.h" #include "xenia/base/math.h" +#include "xenia/base/platform.h" #include "xenia/gpu/packet_disassembler.h" #include "xenia/gpu/trace_protocol.h" #include "xenia/memory.h" @@ -23,10 +24,19 @@ namespace xe { namespace gpu { -bool TraceReader::Open(const std::filesystem::path& path) { +bool TraceReader::Open(const std::string_view path) { Close(); - mmap_ = MappedMemory::Open(path, MappedMemory::Mode::kRead); + mmap_.reset(); +#if XE_PLATFORM_ANDROID + if (xe::filesystem::IsAndroidContentUri(path)) { + mmap_ = + MappedMemory::OpenForAndroidContentUri(path, MappedMemory::Mode::kRead); + } +#endif // XE_PLATFORM_ANDROID + if (!mmap_) { + mmap_ = MappedMemory::Open(xe::to_path(path), MappedMemory::Mode::kRead); + } if (!mmap_) { return false; } diff --git a/src/xenia/gpu/trace_reader.h b/src/xenia/gpu/trace_reader.h index 7ee65a283..d021915d9 100644 --- a/src/xenia/gpu/trace_reader.h +++ b/src/xenia/gpu/trace_reader.h @@ -10,7 +10,7 @@ #ifndef XENIA_GPU_TRACE_READER_H_ #define XENIA_GPU_TRACE_READER_H_ -#include +#include #include #include "xenia/base/mapped_memory.h" @@ -99,7 +99,7 @@ class TraceReader { const Frame* frame(int n) const { return &frames_[n]; } int frame_count() const { return int(frames_.size()); } - bool Open(const std::filesystem::path& path); + bool Open(const std::string_view path); void Close(); diff --git a/src/xenia/gpu/trace_viewer.cc b/src/xenia/gpu/trace_viewer.cc index 7485dd6b0..5d5978fd1 100644 --- a/src/xenia/gpu/trace_viewer.cc +++ b/src/xenia/gpu/trace_viewer.cc @@ -10,13 +10,16 @@ #include "xenia/gpu/trace_viewer.h" #include +#include #include "third_party/half/include/half.hpp" #include "third_party/imgui/imgui.h" #include "xenia/base/assert.h" #include "xenia/base/clock.h" +#include "xenia/base/filesystem.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" +#include "xenia/base/platform.h" #include "xenia/base/string.h" #include "xenia/base/system.h" #include "xenia/base/threading.h" @@ -38,7 +41,8 @@ #include "xenia/ui/windowed_app_context.h" #include "xenia/xbox.h" -DEFINE_path(target_trace_file, "", "Specifies the trace file to load.", "GPU"); +DEFINE_string(target_trace_file, "", "Specifies the trace file to load.", + "GPU"); namespace xe { namespace gpu { @@ -62,9 +66,13 @@ TraceViewer::TraceViewer(xe::ui::WindowedAppContext& app_context, TraceViewer::~TraceViewer() = default; bool TraceViewer::OnInitialize() { - std::filesystem::path path = cvars::target_trace_file; + std::string path = cvars::target_trace_file; // If no path passed, ask the user. + // On Android, however, there's no synchronous file picker, and the trace file + // must be picked externally and provided to the trace viewer activity via the + // intent. +#if !XE_PLATFORM_ANDROID if (path.empty()) { auto file_picker = xe::ui::FilePicker::Create(); file_picker->set_mode(ui::FilePicker::Mode::kOpen); @@ -78,10 +86,11 @@ bool TraceViewer::OnInitialize() { if (file_picker->Show()) { auto selected_files = file_picker->selected_files(); if (!selected_files.empty()) { - path = selected_files[0]; + path = xe::path_to_utf8(selected_files[0]); } } } +#endif // !XE_PLATFORM_ANDROID if (path.empty()) { xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Warning, @@ -89,15 +98,12 @@ bool TraceViewer::OnInitialize() { return false; } - // Normalize the path and make absolute. - auto abs_path = std::filesystem::absolute(path); - if (!Setup()) { xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error, "Unable to setup trace viewer"); return false; } - if (!Load(std::move(abs_path))) { + if (!Load(path)) { xe::ShowSimpleMessageBox(xe::SimpleMessageBoxType::Error, "Unable to load trace file; not found?"); return false; @@ -179,9 +185,8 @@ void TraceViewer::TraceViewerDialog::OnDraw(ImGuiIO& io) { trace_viewer_.DrawUI(); } -bool TraceViewer::Load(const std::filesystem::path& trace_file_path) { - auto file_name = trace_file_path.filename(); - window_->SetTitle("Xenia GPU Trace Viewer: " + xe::path_to_utf8(file_name)); +bool TraceViewer::Load(const std::string_view trace_file_path) { + window_->SetTitle("Xenia GPU Trace Viewer: " + std::string(trace_file_path)); if (!player_->Open(trace_file_path)) { XELOGE("Could not load trace file"); diff --git a/src/xenia/gpu/trace_viewer.h b/src/xenia/gpu/trace_viewer.h index 8ef0e9b86..b41b18aca 100644 --- a/src/xenia/gpu/trace_viewer.h +++ b/src/xenia/gpu/trace_viewer.h @@ -10,7 +10,7 @@ #ifndef XENIA_GPU_TRACE_VIEWER_H_ #define XENIA_GPU_TRACE_VIEWER_H_ -#include +#include #include "xenia/emulator.h" #include "xenia/gpu/shader.h" @@ -95,7 +95,7 @@ class TraceViewer : public xe::ui::WindowedApp { kHostDisasm, }; - bool Load(const std::filesystem::path& trace_file_path); + bool Load(const std::string_view trace_file_path); void DrawUI(); void DrawControllerUI(); From 421c9a80c5ed5e3bfe4af87e3b4e802f1db911ad Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 17 Jul 2022 16:42:33 +0300 Subject: [PATCH 14/20] [Android] Add missing `final` to surface callbacks --- .../src/main/java/jp/xenia/emulator/WindowedAppActivity.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java index 9ea50a0ca..84de4ab13 100644 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java @@ -140,7 +140,7 @@ public abstract class WindowedAppActivity extends Activity { } @Override - public boolean onGenericMotion(View v, MotionEvent event) { + public boolean onGenericMotion(final View view, final MotionEvent event) { if (mAppContext == 0) { return false; } @@ -149,7 +149,7 @@ public abstract class WindowedAppActivity extends Activity { @SuppressLint("ClickableViewAccessibility") @Override - public boolean onTouch(View v, MotionEvent event) { + public boolean onTouch(final View view, final MotionEvent event) { if (mAppContext == 0) { return false; } From 8948b2b557179893d84bc88b055ebeafabe058b9 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 17 Jul 2022 16:58:19 +0300 Subject: [PATCH 15/20] [Android] GPU trace viewer and launcher activities --- .../app/src/main/AndroidManifest.xml | 17 ++++++- .../emulator/GpuTraceViewerActivity.java | 18 ++++++++ .../jp/xenia/emulator/LauncherActivity.java | 45 +++++++++++++++++++ .../res/layout/activity_gpu_trace_viewer.xml | 7 +++ .../src/main/res/layout/activity_launcher.xml | 27 +++++++++++ .../app/src/main/res/values/strings.xml | 3 +- 6 files changed, 114 insertions(+), 3 deletions(-) create mode 100644 android/android_studio_project/app/src/main/java/jp/xenia/emulator/GpuTraceViewerActivity.java create mode 100644 android/android_studio_project/app/src/main/java/jp/xenia/emulator/LauncherActivity.java create mode 100644 android/android_studio_project/app/src/main/res/layout/activity_gpu_trace_viewer.xml create mode 100644 android/android_studio_project/app/src/main/res/layout/activity_launcher.xml diff --git a/android/android_studio_project/app/src/main/AndroidManifest.xml b/android/android_studio_project/app/src/main/AndroidManifest.xml index 25bf5a95f..ae06f2f11 100644 --- a/android/android_studio_project/app/src/main/AndroidManifest.xml +++ b/android/android_studio_project/app/src/main/AndroidManifest.xml @@ -30,14 +30,27 @@ android:theme="@android:style/Theme.Material.Light"> + android:name="jp.xenia.emulator.LauncherActivity" + android:exported="true"> + + + + \ No newline at end of file diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/GpuTraceViewerActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/GpuTraceViewerActivity.java new file mode 100644 index 000000000..f1fe8a306 --- /dev/null +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/GpuTraceViewerActivity.java @@ -0,0 +1,18 @@ +package jp.xenia.emulator; + +import android.os.Bundle; + +public class GpuTraceViewerActivity extends WindowedAppActivity { + @Override + protected String getWindowedAppIdentifier() { + return "xenia_gpu_vulkan_trace_viewer"; + } + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_gpu_trace_viewer); + setWindowSurfaceView(findViewById(R.id.gpu_trace_viewer_surface_view)); + } +} diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/LauncherActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/LauncherActivity.java new file mode 100644 index 000000000..0c977f0e8 --- /dev/null +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/LauncherActivity.java @@ -0,0 +1,45 @@ +package jp.xenia.emulator; + +import android.app.Activity; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; + +public class LauncherActivity extends Activity { + private static final int REQUEST_OPEN_GPU_TRACE_VIEWER = 0; + + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.activity_launcher); + } + + @Override + protected void onActivityResult( + final int requestCode, final int resultCode, final Intent data) { + if (requestCode == REQUEST_OPEN_GPU_TRACE_VIEWER && resultCode == RESULT_OK) { + final Uri uri = data.getData(); + if (uri != null) { + final Intent gpuTraceViewerIntent = new Intent(this, GpuTraceViewerActivity.class); + final Bundle gpuTraceViewerLaunchArguments = new Bundle(); + gpuTraceViewerLaunchArguments.putString("target_trace_file", uri.toString()); + gpuTraceViewerIntent.putExtra( + WindowedAppActivity.EXTRA_CVARS, gpuTraceViewerLaunchArguments); + startActivity(gpuTraceViewerIntent); + } + } + } + + public void onLaunchGpuTraceViewerClick(final View view) { + final Intent intent = new Intent(Intent.ACTION_OPEN_DOCUMENT); + intent.addCategory(Intent.CATEGORY_OPENABLE); + intent.setType("application/octet-stream"); + startActivityForResult(intent, REQUEST_OPEN_GPU_TRACE_VIEWER); + } + + public void onLaunchWindowDemoClick(final View view) { + startActivity(new Intent(this, WindowDemoActivity.class)); + } +} diff --git a/android/android_studio_project/app/src/main/res/layout/activity_gpu_trace_viewer.xml b/android/android_studio_project/app/src/main/res/layout/activity_gpu_trace_viewer.xml new file mode 100644 index 000000000..8e407ecdf --- /dev/null +++ b/android/android_studio_project/app/src/main/res/layout/activity_gpu_trace_viewer.xml @@ -0,0 +1,7 @@ + + diff --git a/android/android_studio_project/app/src/main/res/layout/activity_launcher.xml b/android/android_studio_project/app/src/main/res/layout/activity_launcher.xml new file mode 100644 index 000000000..15c5c3cc6 --- /dev/null +++ b/android/android_studio_project/app/src/main/res/layout/activity_launcher.xml @@ -0,0 +1,27 @@ + + + + + +