diff --git a/src/xenia/base/console_app_main_android.cc b/src/xenia/base/console_app_main_android.cc index 70e69fc5e..320128bc1 100644 --- a/src/xenia/base/console_app_main_android.cc +++ b/src/xenia/base/console_app_main_android.cc @@ -7,4 +7,33 @@ ****************************************************************************** */ -#include "xenia/base/console_app_posix.cc" +#include +#include + +#include "xenia/base/console_app_main.h" +#include "xenia/base/cvar.h" +#include "xenia/base/main_android.h" + +extern "C" int main(int argc, char** argv) { + xe::ConsoleAppEntryInfo entry_info = xe::GetConsoleAppEntryInfo(); + + if (!entry_info.transparent_options) { + cvar::ParseLaunchArguments(argc, argv, entry_info.positional_usage, + entry_info.positional_options); + } + + // Initialize Android globals, including logging. Needs parsed cvars. + // TODO(Triang3l): Obtain the actual API level. + xe::InitializeAndroidAppFromMainThread(__ANDROID_API__); + + std::vector args; + for (int n = 0; n < argc; n++) { + args.emplace_back(argv[n]); + } + + int result = entry_info.entry_point(args); + + xe::ShutdownAndroidAppFromMainThread(); + + return result; +} diff --git a/src/xenia/base/main_android.cc b/src/xenia/base/main_android.cc new file mode 100644 index 000000000..56211fee2 --- /dev/null +++ b/src/xenia/base/main_android.cc @@ -0,0 +1,66 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/base/main_android.h" + +#include + +#include "xenia/base/assert.h" +#include "xenia/base/logging.h" +#include "xenia/base/memory.h" +#include "xenia/base/threading.h" + +namespace xe { + +static size_t android_initializations_ = 0; + +static int32_t android_api_level_ = __ANDROID_API__; + +void InitializeAndroidAppFromMainThread(int32_t api_level) { + if (android_initializations_++) { + // Already initialized for another component in the process. + return; + } + + // Set the API level before everything else if available - may be needed by + // subsystem initialization itself. + android_api_level_ = api_level; + + // Logging uses threading. + xe::threading::AndroidInitialize(); + + // Multiple apps can be launched within one process - don't pass the actual + // app name. + xe::InitializeLogging("xenia"); + + xe::memory::AndroidInitialize(); +} + +void ShutdownAndroidAppFromMainThread() { + assert_not_zero(android_initializations_); + if (!android_initializations_) { + return; + } + if (--android_initializations_) { + // Other components are still running. + return; + } + + xe::memory::AndroidShutdown(); + + xe::ShutdownLogging(); + + xe::threading::AndroidShutdown(); + + android_api_level_ = __ANDROID_API__; +} + +int32_t GetAndroidApiLevel() { return android_api_level_; } + +} // namespace xe diff --git a/src/xenia/base/main_android.h b/src/xenia/base/main_android.h new file mode 100644 index 000000000..1f2d23dd2 --- /dev/null +++ b/src/xenia/base/main_android.h @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_BASE_MAIN_ANDROID_H_ +#define XENIA_BASE_MAIN_ANDROID_H_ + +#include + +#include "xenia/base/platform.h" + +namespace xe { + +// In activities, these functions must be called in onCreate and onDestroy. +// +// Multiple components may run in the same process, and they will be +// instantiated in the main thread, which is, for regular applications (the +// system application exception doesn't apply to Xenia), the UI thread. +// +// For this reason, it's okay to call these functions multiple times if +// different Xenia windowed applications run in one process - there is call +// counting internally. +// +// In standalone console apps built with $(BUILD_EXECUTABLE), these functions +// must be called in `main`. +void InitializeAndroidAppFromMainThread(int32_t api_level); +void ShutdownAndroidAppFromMainThread(); + +// May be the minimum supported level if the initialization was done without a +// configuration. +int32_t GetAndroidApiLevel(); + +} // namespace xe + +#endif // XENIA_BASE_MAIN_ANDROID_H_ diff --git a/src/xenia/base/memory.h b/src/xenia/base/memory.h index 459f09db3..14fb65968 100644 --- a/src/xenia/base/memory.h +++ b/src/xenia/base/memory.h @@ -24,6 +24,11 @@ namespace xe { namespace memory { +#if XE_PLATFORM_ANDROID +void AndroidInitialize(); +void AndroidShutdown(); +#endif + // Returns the native page size of the system, in bytes. // This should be ~4KiB. size_t page_size(); diff --git a/src/xenia/base/memory_posix.cc b/src/xenia/base/memory_posix.cc index 271249a87..2ff36a603 100644 --- a/src/xenia/base/memory_posix.cc +++ b/src/xenia/base/memory_posix.cc @@ -12,22 +12,52 @@ #include #include #include +#include #include "xenia/base/math.h" #include "xenia/base/platform.h" #include "xenia/base/string.h" #if XE_PLATFORM_ANDROID +#include #include #include #include -#include "xenia/base/platform_android.h" +#include "xenia/base/main_android.h" #endif namespace xe { namespace memory { +#if XE_PLATFORM_ANDROID +// May be null if no dynamically loaded functions are required. +static void* libandroid_; +// API 26+. +static int (*android_ASharedMemory_create_)(const char* name, size_t size); + +void AndroidInitialize() { + if (xe::GetAndroidApiLevel() >= 26) { + libandroid_ = dlopen("libandroid.so", RTLD_NOW); + assert_not_null(libandroid_); + if (libandroid_) { + android_ASharedMemory_create_ = + reinterpret_cast( + dlsym(libandroid_, "ASharedMemory_create")); + assert_not_null(android_ASharedMemory_create_); + } + } +} + +void AndroidShutdown() { + android_ASharedMemory_create_ = nullptr; + if (libandroid_) { + dlclose(libandroid_); + libandroid_ = nullptr; + } +} +#endif + size_t page_size() { return getpagesize(); } size_t allocation_granularity() { return page_size(); } @@ -86,11 +116,9 @@ FileMappingHandle CreateFileMappingHandle(const std::filesystem::path& path, size_t length, PageAccess access, bool commit) { #if XE_PLATFORM_ANDROID - if (xe::platform::android::api_level() >= 26) { - // TODO(Triang3l): Check if memfd can be used instead on API 30+. - int sharedmem_fd = - xe::platform::android::api_functions().api_26.ASharedMemory_create( - path.c_str(), length); + // TODO(Triang3l): Check if memfd can be used instead on API 30+. + if (android_ASharedMemory_create_) { + int sharedmem_fd = android_ASharedMemory_create_(path.c_str(), length); return sharedmem_fd >= 0 ? sharedmem_fd : kFileMappingHandleInvalid; } diff --git a/src/xenia/base/platform_android.cc b/src/xenia/base/platform_android.cc deleted file mode 100644 index 3db9a7f9f..000000000 --- a/src/xenia/base/platform_android.cc +++ /dev/null @@ -1,65 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/base/platform_android.h" - -#include -#include - -#include "xenia/base/assert.h" - -namespace xe { -namespace platform { -namespace android { - -static bool initialized = false; - -static int32_t api_level_ = __ANDROID_API__; - -static ApiFunctions api_functions_; - -void Initialize(const ANativeActivity* activity) { - if (initialized) { - return; - } - - AConfiguration* configuration = AConfiguration_new(); - AConfiguration_fromAssetManager(configuration, activity->assetManager); - api_level_ = AConfiguration_getSdkVersion(configuration); - AConfiguration_delete(configuration); - - if (api_level_ >= 26) { - // Leaked intentionally as these will be usable anywhere, already loaded - // into the address space as the application is linked against them. - // https://chromium.googlesource.com/chromium/src/+/master/third_party/ashmem/ashmem-dev.c#201 - void* libandroid = dlopen("libandroid.so", RTLD_NOW); - assert_not_null(libandroid); - void* libc = dlopen("libc.so", RTLD_NOW); - assert_not_null(libc); -#define XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(lib, name, api) \ - api_functions_.api_##api.name = \ - reinterpret_cast( \ - dlsym(lib, #name)); \ - assert_not_null(api_functions_.api_##api.name); - XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(libandroid, ASharedMemory_create, 26); - // pthreads are a part of Bionic libc on Android. - XE_PLATFORM_ANDROID_LOAD_API_FUNCTION(libc, pthread_getname_np, 26); -#undef XE_PLATFORM_ANDROID_LOAD_API_FUNCTION - } - - initialized = true; -} - -int32_t api_level() { return api_level_; } - -const ApiFunctions& api_functions() { return api_functions_; } - -} // namespace android -} // namespace platform -} // namespace xe diff --git a/src/xenia/base/platform_android.h b/src/xenia/base/platform_android.h deleted file mode 100644 index 711084578..000000000 --- a/src/xenia/base/platform_android.h +++ /dev/null @@ -1,48 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2020 Ben Vanik. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_BASE_PLATFORM_ANDROID_H_ -#define XENIA_BASE_PLATFORM_ANDROID_H_ - -// NOTE: if you're including this file it means you are explicitly depending -// on Android-specific headers. This is bad for portability and should be -// avoided! - -#include -#include -#include -#include - -namespace xe { -namespace platform { -namespace android { - -// Must be called in onCreate of the first activity. -void Initialize(const ANativeActivity* activity); - -// Returns the device API level - if not initialized, will return the minimum -// level supported by Xenia. -int32_t api_level(); - -// Android API functions added after the minimum supported API version. -struct ApiFunctions { - struct { - // libandroid - int (*ASharedMemory_create)(const char* name, size_t size); - // libc - int (*pthread_getname_np)(pthread_t pthread, char* buf, size_t n); - } api_26; -}; -const ApiFunctions& api_functions(); - -} // namespace android -} // namespace platform -} // namespace xe - -#endif // XENIA_BASE_PLATFORM_ANDROID_H_ diff --git a/src/xenia/base/threading.h b/src/xenia/base/threading.h index 36665d7a4..520c2d531 100644 --- a/src/xenia/base/threading.h +++ b/src/xenia/base/threading.h @@ -25,10 +25,16 @@ #include #include "xenia/base/assert.h" +#include "xenia/base/platform.h" namespace xe { namespace threading { +#if XE_PLATFORM_ANDROID +void AndroidInitialize(); +void AndroidShutdown(); +#endif + // This is more like an Event with self-reset when returning from Wait() class Fence { public: diff --git a/src/xenia/base/threading_posix.cc b/src/xenia/base/threading_posix.cc index 7766df05e..05fc616b7 100644 --- a/src/xenia/base/threading_posix.cc +++ b/src/xenia/base/threading_posix.cc @@ -21,20 +21,51 @@ #include #include #include +#include #include #include #include #if XE_PLATFORM_ANDROID +#include #include -#include "xenia/base/platform_android.h" +#include "xenia/base/main_android.h" #include "xenia/base/string_util.h" #endif namespace xe { namespace threading { +#if XE_PLATFORM_ANDROID +// May be null if no dynamically loaded functions are required. +static void* android_libc_; +// API 26+. +static int (*android_pthread_getname_np_)(pthread_t pthread, char* buf, + size_t n); + +void AndroidInitialize() { + if (xe::GetAndroidApiLevel() >= 26) { + android_libc_ = dlopen("libc.so", RTLD_NOW); + assert_not_null(android_libc_); + if (android_libc_) { + android_pthread_getname_np_ = + reinterpret_cast( + dlsym(android_libc_, "pthread_getname_np")); + assert_not_null(android_pthread_getname_np_); + } + } +} + +void AndroidShutdown() { + android_pthread_getname_np_ = nullptr; + if (android_libc_) { + dlclose(android_libc_); + android_libc_ = nullptr; + } +} +#endif + template inline timespec DurationToTimeSpec( std::chrono::duration<_Rep, _Period> duration) { @@ -577,9 +608,9 @@ class PosixCondition : public PosixConditionBase { // pthread_getname_np was added in API 26 - below that, store the name in // this object, which may be only modified through Xenia threading, but // should be enough in most cases. - if (xe::platform::android::api_level() >= 26) { - if (xe::platform::android::api_functions().api_26.pthread_getname_np( - thread_, result.data(), result.size() - 1) != 0) { + if (android_pthread_getname_np_) { + if (android_pthread_getname_np_(thread_, result.data(), + result.size() - 1) != 0) { assert_always(); } } else { @@ -608,7 +639,7 @@ class PosixCondition : public PosixConditionBase { #if XE_PLATFORM_ANDROID void SetAndroidPreApi26Name(const std::string_view name) { - if (xe::platform::android::api_level() >= 26) { + if (android_pthread_getname_np_) { return; } std::lock_guard lock(android_pre_api_26_name_mutex_); @@ -1145,7 +1176,7 @@ void Thread::Exit(int exit_code) { void set_name(const std::string_view name) { pthread_setname_np(pthread_self(), std::string(name).c_str()); #if XE_PLATFORM_ANDROID - if (xe::platform::android::api_level() < 26 && current_thread_) { + if (!android_pthread_getname_np_ && current_thread_) { current_thread_->condition().SetAndroidPreApi26Name(name); } #endif diff --git a/src/xenia/ui/window_android.cc b/src/xenia/ui/window_android.cc new file mode 100644 index 000000000..cd5aaba80 --- /dev/null +++ b/src/xenia/ui/window_android.cc @@ -0,0 +1,54 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/ui/window_android.h" + +#include +#include + +#include "xenia/base/assert.h" +#include "xenia/ui/windowed_app_context_android.h" + +namespace xe { +namespace ui { + +std::unique_ptr Window::Create(WindowedAppContext& app_context, + const std::string& title) { + // The window is a proxy between the main activity and Xenia, so there can be + // only one for an activity. + AndroidWindowedAppContext& android_app_context = + static_cast(app_context); + AndroidWindow* current_activity_window = + android_app_context.GetActivityWindow(); + assert_null(current_activity_window); + if (current_activity_window) { + return nullptr; + } + auto window = std::make_unique(app_context, title); + android_app_context.SetActivityWindow(window.get()); + return std::move(window); +} + +AndroidWindow::~AndroidWindow() { + AndroidWindowedAppContext& android_app_context = + static_cast(app_context()); + if (android_app_context.GetActivityWindow() == this) { + android_app_context.SetActivityWindow(nullptr); + } +} + +std::unique_ptr MenuItem::Create(Type type, + const std::string& text, + const std::string& hotkey, + std::function callback) { + return std::make_unique(type, text, hotkey, callback); +} + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/window_android.h b/src/xenia/ui/window_android.h new file mode 100644 index 000000000..780c1d77d --- /dev/null +++ b/src/xenia/ui/window_android.h @@ -0,0 +1,64 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_UI_WINDOW_ANDROID_H_ +#define XENIA_UI_WINDOW_ANDROID_H_ + +#include "xenia/ui/menu_item.h" +#include "xenia/ui/window.h" + +namespace xe { +namespace ui { + +class AndroidWindow : public Window { + public: + // Many functions are left unimplemented because the layout is configured from + // XML and Java. + + AndroidWindow(WindowedAppContext& app_context, const std::string& title) + : Window(app_context, title) {} + ~AndroidWindow(); + + NativePlatformHandle native_platform_handle() const override { + return nullptr; + } + // TODO(Triang3l): ANativeWindow for Vulkan surface creation. + NativeWindowHandle native_handle() const override { return nullptr; } + + void EnableMainMenu() override {} + void DisableMainMenu() override {} + + bool SetIcon(const void* buffer, size_t size) override { return false; } + + bool CaptureMouse() override { return false; } + bool ReleaseMouse() override { return false; } + + int get_medium_dpi() const override { return 160; } + + // TODO(Triang3l): Call the close event, which may finish the activity. + void Close() override {} +}; + +// Dummy for the menu item - menus are controlled by the layout. +// TODO(Triang3l): Make something like MenuItem work as the basic common action +// interface for Java buttons. +class AndroidMenuItem final : public MenuItem { + public: + AndroidMenuItem(Type type, const std::string& text, const std::string& hotkey, + std::function callback) + : MenuItem(type, text, hotkey, callback) {} + + void EnableMenuItem(Window& window) override {} + void DisableMenuItem(Window& window) override {} +}; + +} // namespace ui +} // namespace xe + +#endif // XENIA_UI_WINDOW_ANDROID_H_ diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc index e2dbe1aa5..c1909d02e 100644 --- a/src/xenia/ui/windowed_app_context_android.cc +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -9,9 +9,12 @@ #include "xenia/ui/windowed_app_context_android.h" +#include #include +#include #include "xenia/base/assert.h" +#include "xenia/base/main_android.h" #include "xenia/ui/windowed_app.h" namespace xe { @@ -22,6 +25,8 @@ void AndroidWindowedAppContext::StartAppOnNativeActivityCreate( [[maybe_unused]] size_t saved_state_size, std::unique_ptr (*app_creator)( WindowedAppContext& app_context)) { + // TODO(Triang3l): Pass the launch options from the Intent or the saved + // instance state. AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext(activity); // The pointer is now held by the Activity as its ANativeActivity::instance, @@ -35,6 +40,8 @@ void AndroidWindowedAppContext::StartAppOnNativeActivityCreate( AndroidWindowedAppContext::~AndroidWindowedAppContext() { // TODO(Triang3l): Unregister activity callbacks. activity_->instance = nullptr; + + xe::ShutdownAndroidAppFromMainThread(); } void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() { @@ -42,11 +49,21 @@ void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() { } void AndroidWindowedAppContext::PlatformQuitFromUIThread() { - ANativeActivity_finish(activity); + ANativeActivity_finish(activity_); } AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity) : activity_(activity) { + int32_t api_level; + { + AConfiguration* configuration = AConfiguration_new(); + AConfiguration_fromAssetManager(configuration, activity->assetManager); + api_level = AConfiguration_getSdkVersion(configuration); + AConfiguration_delete(configuration); + } + + xe::InitializeAndroidAppFromMainThread(api_level); + activity_->instance = this; // TODO(Triang3l): Register activity callbacks. } @@ -54,7 +71,7 @@ AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity) bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr ( *app_creator)(WindowedAppContext& app_context)) { assert_null(app_); - app_ = app_creator(this); + app_ = app_creator(*this); if (!app_->OnInitialize()) { app_->InvokeOnDestroy(); app_.reset(); diff --git a/src/xenia/ui/windowed_app_context_android.h b/src/xenia/ui/windowed_app_context_android.h index 08ddc31bc..cfdc16ed1 100644 --- a/src/xenia/ui/windowed_app_context_android.h +++ b/src/xenia/ui/windowed_app_context_android.h @@ -18,6 +18,7 @@ namespace xe { namespace ui { +class AndroidWindow; class WindowedApp; class AndroidWindowedAppContext final : public WindowedAppContext { @@ -39,6 +40,13 @@ class AndroidWindowedAppContext final : public WindowedAppContext { void PlatformQuitFromUIThread() override; + // The single Window instance that will be receiving window callbacks. + // Multiple windows cannot be created as one activity or fragment can have + // only one layout. This window acts purely as a proxy between the activity + // and the Xenia logic. + AndroidWindow* GetActivityWindow() const { return activity_window_; } + void SetActivityWindow(AndroidWindow* window) { activity_window_ = window; } + private: explicit AndroidWindowedAppContext(ANativeActivity* activity); bool InitializeApp(std::unique_ptr (*app_creator)( @@ -50,6 +58,8 @@ class AndroidWindowedAppContext final : public WindowedAppContext { ANativeActivity* activity_; std::unique_ptr app_; + AndroidWindow* activity_window_ = nullptr; + // TODO(Triang3l): The rest of the context, including quit handler (and the // destructor) calling `finish` on the activity, UI looper notification // posting, etc. diff --git a/src/xenia/ui/windowed_app_main_android.cc b/src/xenia/ui/windowed_app_main_android.cc new file mode 100644 index 000000000..1e2fd6c92 --- /dev/null +++ b/src/xenia/ui/windowed_app_main_android.cc @@ -0,0 +1,10 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2021 Ben Vanik. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +// Not needed - no single entry point, the event loop is polled by the OS.