[Base/UI] Android globals initialization + WindowedAppContext parts

This commit is contained in:
Triang3l 2021-09-13 23:09:28 +03:00
parent acbd22840d
commit 7aeac37eb6
14 changed files with 375 additions and 128 deletions

View File

@ -7,4 +7,33 @@
******************************************************************************
*/
#include "xenia/base/console_app_posix.cc"
#include <string>
#include <vector>
#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<std::string> args;
for (int n = 0; n < argc; n++) {
args.emplace_back(argv[n]);
}
int result = entry_info.entry_point(args);
xe::ShutdownAndroidAppFromMainThread();
return result;
}

View File

@ -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 <cstddef>
#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

View File

@ -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 <cstdint>
#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_

View File

@ -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();

View File

@ -12,22 +12,52 @@
#include <fcntl.h>
#include <sys/mman.h>
#include <unistd.h>
#include <cstddef>
#include "xenia/base/math.h"
#include "xenia/base/platform.h"
#include "xenia/base/string.h"
#if XE_PLATFORM_ANDROID
#include <dlfcn.h>
#include <linux/ashmem.h>
#include <string.h>
#include <sys/ioctl.h>
#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<decltype(android_ASharedMemory_create_)>(
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;
}

View File

@ -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 <android/configuration.h>
#include <dlfcn.h>
#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<decltype(api_functions_.api_##api.name)>( \
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

View File

@ -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 <android/native_activity.h>
#include <pthread.h>
#include <cstddef>
#include <cstdint>
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_

View File

@ -25,10 +25,16 @@
#include <vector>
#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:

View File

@ -21,20 +21,51 @@
#include <sys/types.h>
#include <unistd.h>
#include <array>
#include <cstddef>
#include <cstring>
#include <ctime>
#include <memory>
#if XE_PLATFORM_ANDROID
#include <dlfcn.h>
#include <sched.h>
#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<decltype(android_pthread_getname_np_)>(
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 <typename _Rep, typename _Period>
inline timespec DurationToTimeSpec(
std::chrono::duration<_Rep, _Period> duration) {
@ -577,9 +608,9 @@ class PosixCondition<Thread> : 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<Thread> : 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<std::mutex> 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

View File

@ -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 <memory>
#include <utility>
#include "xenia/base/assert.h"
#include "xenia/ui/windowed_app_context_android.h"
namespace xe {
namespace ui {
std::unique_ptr<Window> 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<AndroidWindowedAppContext&>(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<AndroidWindow>(app_context, title);
android_app_context.SetActivityWindow(window.get());
return std::move(window);
}
AndroidWindow::~AndroidWindow() {
AndroidWindowedAppContext& android_app_context =
static_cast<AndroidWindowedAppContext&>(app_context());
if (android_app_context.GetActivityWindow() == this) {
android_app_context.SetActivityWindow(nullptr);
}
}
std::unique_ptr<ui::MenuItem> MenuItem::Create(Type type,
const std::string& text,
const std::string& hotkey,
std::function<void()> callback) {
return std::make_unique<AndroidMenuItem>(type, text, hotkey, callback);
}
} // namespace ui
} // namespace xe

View File

@ -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<void()> 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_

View File

@ -9,9 +9,12 @@
#include "xenia/ui/windowed_app_context_android.h"
#include <android/configuration.h>
#include <android/native_activity.h>
#include <cstdint>
#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<WindowedApp> (*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<WindowedApp> (
*app_creator)(WindowedAppContext& app_context)) {
assert_null(app_);
app_ = app_creator(this);
app_ = app_creator(*this);
if (!app_->OnInitialize()) {
app_->InvokeOnDestroy();
app_.reset();

View File

@ -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<WindowedApp> (*app_creator)(
@ -50,6 +58,8 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
ANativeActivity* activity_;
std::unique_ptr<WindowedApp> 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.

View File

@ -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.