From d4f2bef6c857b90d677625af2824ad92931227c7 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Mon, 13 Sep 2021 21:09:53 +0200 Subject: [PATCH 01/17] [FFmpeg] Update submodule Support new targets like android and aarch64. --- third_party/FFmpeg | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/third_party/FFmpeg b/third_party/FFmpeg index 09eac851e..fb6c32ddf 160000 --- a/third_party/FFmpeg +++ b/third_party/FFmpeg @@ -1 +1 @@ -Subproject commit 09eac851efa5886d82067c2cb3cc9fb789a85c7e +Subproject commit fb6c32ddf75d818dce39e2f7d00401e9864a8d4d From 03355713540bfbd19f12ff2a2605ac700e7efea5 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Wed, 15 Sep 2021 22:58:11 +0300 Subject: [PATCH 02/17] [UI] Android CallInUIThread and activity onDestroy --- src/xenia/ui/windowed_app.h | 2 +- src/xenia/ui/windowed_app_context_android.cc | 222 +++++++++++++++++-- src/xenia/ui/windowed_app_context_android.h | 53 ++++- 3 files changed, 249 insertions(+), 28 deletions(-) diff --git a/src/xenia/ui/windowed_app.h b/src/xenia/ui/windowed_app.h index 14eba9cb7..b521a759e 100644 --- a/src/xenia/ui/windowed_app.h +++ b/src/xenia/ui/windowed_app.h @@ -109,7 +109,7 @@ class WindowedApp { #define XE_DEFINE_WINDOWED_APP(export_name, creator) \ __attribute__((visibility("default"))) extern "C" void export_name( \ ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \ - xe::ui::AndroidWindowedAppContext::StartAppOnNativeActivityCreate( \ + xe::ui::AndroidWindowedAppContext::StartAppOnActivityCreate( \ activity, saved_state, saved_state_size, creator); \ } #else diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc index c1909d02e..fcab4f5fd 100644 --- a/src/xenia/ui/windowed_app_context_android.cc +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -10,50 +10,71 @@ #include "xenia/ui/windowed_app_context_android.h" #include +#include #include +#include +#include +#include #include #include "xenia/base/assert.h" +#include "xenia/base/logging.h" #include "xenia/base/main_android.h" #include "xenia/ui/windowed_app.h" namespace xe { namespace ui { -void AndroidWindowedAppContext::StartAppOnNativeActivityCreate( +void AndroidWindowedAppContext::StartAppOnActivityCreate( ANativeActivity* activity, [[maybe_unused]] void* saved_state, [[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); + AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext; + if (!app_context->Initialize(activity)) { + delete app_context; + ANativeActivity_finish(activity); + return; + } // The pointer is now held by the Activity as its ANativeActivity::instance, // until the destruction. if (!app_context->InitializeApp(app_creator)) { - delete app_context; + // InitializeApp might have sent commands to the UI thread looper callback + // pipe, perform deferred destruction. + app_context->RequestDestruction(); ANativeActivity_finish(activity); + return; } } -AndroidWindowedAppContext::~AndroidWindowedAppContext() { - // TODO(Triang3l): Unregister activity callbacks. - activity_->instance = nullptr; - - xe::ShutdownAndroidAppFromMainThread(); -} - void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() { - // TODO(Triang3l): Request message processing in the UI thread. + // Don't check ui_thread_looper_callback_registered_, as it's owned + // exclusively by the UI thread, while this may be called by any, and in case + // of a pipe error, the callback will be invoked by the looper, which will + // trigger all the necessary shutdown, and the pending functions will be + // called anyway by the shutdown. + UIThreadLooperCallbackCommand command = + UIThreadLooperCallbackCommand::kExecutePendingFunctions; + if (write(ui_thread_looper_callback_pipe_[1], &command, sizeof(command)) != + sizeof(command)) { + XELOGE( + "AndroidWindowedAppContext: Failed to write a pending function " + "execution command to the UI thread looper callback pipe"); + return; + } + ALooper_wake(ui_thread_looper_); } void AndroidWindowedAppContext::PlatformQuitFromUIThread() { + // All the shutdown will be done in onDestroy of the activity. ANativeActivity_finish(activity_); } -AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity) - : activity_(activity) { +AndroidWindowedAppContext::~AndroidWindowedAppContext() { Shutdown(); } + +bool AndroidWindowedAppContext::Initialize(ANativeActivity* activity) { int32_t api_level; { AConfiguration* configuration = AConfiguration_new(); @@ -61,11 +82,170 @@ AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity) api_level = AConfiguration_getSdkVersion(configuration); AConfiguration_delete(configuration); } - xe::InitializeAndroidAppFromMainThread(api_level); + android_base_initialized_ = true; + // Initialize sending commands to the UI thread looper callback, for + // requesting function calls in the UI thread. + ui_thread_looper_ = ALooper_forThread(); + // The context may be created only in the UI thread, which must have an + // internal looper. + assert_not_null(ui_thread_looper_); + if (!ui_thread_looper_) { + XELOGE("AndroidWindowedAppContext: Failed to get the UI thread looper"); + Shutdown(); + return false; + } + // The looper can be woken up by other threads, so acquiring it. Shutdown + // assumes that if ui_thread_looper_ is not null, it has been acquired. + ALooper_acquire(ui_thread_looper_); + if (pipe(ui_thread_looper_callback_pipe_.data())) { + XELOGE( + "AndroidWindowedAppContext: Failed to create the UI thread looper " + "callback pipe"); + Shutdown(); + return false; + } + if (ALooper_addFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0], + ALOOPER_POLL_CALLBACK, ALOOPER_EVENT_INPUT, + UIThreadLooperCallback, this) != 1) { + XELOGE( + "AndroidWindowedAppContext: Failed to add the callback to the UI " + "thread looper"); + Shutdown(); + return false; + } + ui_thread_looper_callback_registered_ = true; + + activity_ = activity; activity_->instance = this; - // TODO(Triang3l): Register activity callbacks. + activity_->callbacks->onDestroy = OnActivityDestroy; + + return true; +} + +void AndroidWindowedAppContext::Shutdown() { + if (app_) { + app_->InvokeOnDestroy(); + app_.reset(); + } + + // The app should destroy the window, but make sure everything is cleaned up + // anyway. + assert_null(activity_window_); + activity_window_ = nullptr; + + if (activity_) { + activity_->callbacks->onDestroy = nullptr; + activity_->instance = nullptr; + activity_ = nullptr; + } + + if (ui_thread_looper_callback_registered_) { + ALooper_removeFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0]); + ui_thread_looper_callback_registered_ = false; + } + for (int& pipe_fd : ui_thread_looper_callback_pipe_) { + if (pipe_fd == -1) { + continue; + } + close(pipe_fd); + pipe_fd = -1; + } + if (ui_thread_looper_) { + ALooper_release(ui_thread_looper_); + ui_thread_looper_ = nullptr; + } + + if (android_base_initialized_) { + xe::ShutdownAndroidAppFromMainThread(); + android_base_initialized_ = false; + } +} + +void AndroidWindowedAppContext::RequestDestruction() { + // According to ALooper_removeFd documentation: + // "...it is possible for the callback to already be running or for it to run + // one last time if the file descriptor was already signalled. Calling code + // is responsible for ensuring that this case is safely handled. For example, + // if the callback takes care of removing itself during its own execution + // either by returning 0 or by calling this method..." + // If the looper callback is registered, the pipe may have pending commands, + // and thus the callback may still be called with the pointer to the context + // as the user data. + if (!ui_thread_looper_callback_registered_) { + delete this; + return; + } + UIThreadLooperCallbackCommand command = + UIThreadLooperCallbackCommand::kDestroy; + if (write(ui_thread_looper_callback_pipe_[1], &command, sizeof(command)) != + sizeof(command)) { + XELOGE( + "AndroidWindowedAppContext: Failed to write a destruction command to " + "the UI thread looper callback pipe"); + delete this; + return; + } + ALooper_wake(ui_thread_looper_); +} + +int AndroidWindowedAppContext::UIThreadLooperCallback(int fd, int events, + void* data) { + // In case of errors, destruction of the pipe (most importantly the write end) + // must not be done here immediately as other threads, which may still be + // sending commands, would not be aware of that. + auto app_context = static_cast(data); + if (events & + (ALOOPER_EVENT_ERROR | ALOOPER_EVENT_HANGUP | ALOOPER_EVENT_INVALID)) { + // Will return 0 to unregister self, this file descriptor is not usable + // anymore, so let everything potentially referencing it in QuitFromUIThread + // know. + app_context->ui_thread_looper_callback_registered_ = false; + XELOGE( + "AndroidWindowedAppContext: The UI thread looper callback pipe file " + "descriptor has encountered an error condition during polling"); + app_context->QuitFromUIThread(); + return 0; + } + if (!(events & ALOOPER_EVENT_INPUT)) { + // Spurious callback call. Need a non-empty pipe. + return 1; + } + // Process one command with a blocking `read`. The callback will be invoked + // again and again if there is still data after this read. + UIThreadLooperCallbackCommand command; + switch (read(fd, &command, sizeof(command))) { + case sizeof(command): + break; + case -1: + // Will return 0 to unregister self, this file descriptor is not usable + // anymore, so let everything potentially referencing it in + // QuitFromUIThread know. + app_context->ui_thread_looper_callback_registered_ = false; + XELOGE( + "AndroidWindowedAppContext: The UI thread looper callback pipe file " + "descriptor has encountered an error condition during reading"); + app_context->QuitFromUIThread(); + return 0; + default: + // Something like incomplete data - shouldn't be happening, but not a + // reported error. + return 1; + } + switch (command) { + case UIThreadLooperCallbackCommand::kDestroy: + // Final destruction requested. Will unregister self by returning 0, so + // set ui_thread_looper_callback_registered_ to false so Shutdown won't + // try to unregister it too. + app_context->ui_thread_looper_callback_registered_ = false; + delete app_context; + return 0; + case UIThreadLooperCallbackCommand::kExecutePendingFunctions: + app_context->ExecutePendingFunctionsFromUIThread(); + break; + } + return 1; } bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr ( @@ -80,5 +260,15 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr ( return true; } +void AndroidWindowedAppContext::OnActivityDestroy(ANativeActivity* activity) { + auto& app_context = + *static_cast(activity->instance); + if (app_context.app_) { + app_context.app_->InvokeOnDestroy(); + app_context.app_.reset(); + } + app_context.RequestDestruction(); +} + } // namespace ui } // namespace xe diff --git a/src/xenia/ui/windowed_app_context_android.h b/src/xenia/ui/windowed_app_context_android.h index cfdc16ed1..64e45372d 100644 --- a/src/xenia/ui/windowed_app_context_android.h +++ b/src/xenia/ui/windowed_app_context_android.h @@ -10,7 +10,9 @@ #ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ #define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ +#include #include +#include #include #include "xenia/ui/windowed_app_context.h" @@ -24,15 +26,11 @@ class WindowedApp; class AndroidWindowedAppContext final : public WindowedAppContext { public: // For calling from android.app.func_name exports. - static void StartAppOnNativeActivityCreate( + static void StartAppOnActivityCreate( ANativeActivity* activity, void* saved_state, size_t saved_state_size, std::unique_ptr (*app_creator)( WindowedAppContext& app_context)); - // Defined in the translation unit where WindowedApp is complete because of - // std::unique_ptr. - ~AndroidWindowedAppContext(); - ANativeActivity* activity() const { return activity_; } WindowedApp* app() const { return app_.get(); } @@ -48,21 +46,54 @@ class AndroidWindowedAppContext final : public WindowedAppContext { void SetActivityWindow(AndroidWindow* window) { activity_window_ = window; } private: - explicit AndroidWindowedAppContext(ANativeActivity* activity); + enum class UIThreadLooperCallbackCommand : uint8_t { + kDestroy, + kExecutePendingFunctions, + }; + + AndroidWindowedAppContext() = default; + + // Don't delete this object directly externally if successfully initialized as + // the looper may still execute the callback for pending commands after an + // external ANativeActivity_removeFd, and the callback receives a pointer to + // the context - deletion must be deferred and done in the callback itself. + // Defined in the translation unit where WindowedApp is complete because of + // std::unique_ptr. + ~AndroidWindowedAppContext(); + + bool Initialize(ANativeActivity* activity); + void Shutdown(); + + // Call this function instead of deleting the object directly, so if needed, + // deletion will be deferred until the callback (receiving a pointer to the + // context) can no longer be executed by the looper (will be done inside the + // callback). + void RequestDestruction(); + + static int UIThreadLooperCallback(int fd, int events, void* data); + bool InitializeApp(std::unique_ptr (*app_creator)( WindowedAppContext& app_context)); + static void OnActivityDestroy(ANativeActivity* activity); + + bool android_base_initialized_ = false; + + // May be read by non-UI threads in NotifyUILoopOfPendingFunctions. + ALooper* ui_thread_looper_ = nullptr; + // [1] (the write file descriptor) may be referenced as read-only by non-UI + // threads in NotifyUILoopOfPendingFunctions. + std::array ui_thread_looper_callback_pipe_{-1, -1}; + bool ui_thread_looper_callback_registered_ = false; + // TODO(Triang3l): Switch from ANativeActivity to the context itself being the // object for communication with the Java code when NativeActivity isn't used // anymore as its functionality is heavily limited. - ANativeActivity* activity_; - std::unique_ptr app_; + ANativeActivity* activity_ = nullptr; 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. + std::unique_ptr app_; }; } // namespace ui From 360e2f541409a4de8ff7b128d82f5d6a4756dc18 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Wed, 15 Sep 2021 22:10:57 +0200 Subject: [PATCH 03/17] [Kernel] Fix glibc exception on empty content_root --- src/xenia/kernel/kernel_state.cc | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 9c5dcebc2..7d60aac64 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -52,7 +52,9 @@ KernelState::KernelState(Emulator* emulator) user_profile_ = std::make_unique(); auto content_root = emulator_->content_root(); - content_root = std::filesystem::absolute(content_root); + if (!content_root.empty()) { + content_root = std::filesystem::absolute(content_root); + } content_manager_ = std::make_unique(this, content_root); assert_null(shared_kernel_state_); From 15436b17bb840b84f5c06208632f97622f5ec044 Mon Sep 17 00:00:00 2001 From: Rick Gibbed Date: Thu, 16 Sep 2021 11:55:30 -0500 Subject: [PATCH 04/17] Update FUNDING.yml. Adding myself to the GitHub Sponsors list. --- .github/FUNDING.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 3fc7e06c7..4e01ac6b1 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1 +1,2 @@ +github: gibbed patreon: xenia_project From 4068819266f28a1c4c991318fad7eb410547ff94 Mon Sep 17 00:00:00 2001 From: Rick Gibbed Date: Thu, 16 Sep 2021 12:49:08 -0500 Subject: [PATCH 05/17] Update FUNDING.yml. [ci skip] Add JoelLinn to the GitHub Sponsors list. --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 4e01ac6b1..85352fe2b 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -github: gibbed patreon: xenia_project +github: [gibbed, JoelLinn] From 347c9f01fdc6d87159a98e754717c3423a801d2a Mon Sep 17 00:00:00 2001 From: Rick Gibbed Date: Thu, 16 Sep 2021 13:11:22 -0500 Subject: [PATCH 06/17] Update FUNDING.yml. [ci skip] Add Razzile to the GitHub Sponsors list. --- .github/FUNDING.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index 85352fe2b..62bcd516c 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ patreon: xenia_project -github: [gibbed, JoelLinn] +github: [gibbed, JoelLinn, Razzile] From 26a2d814da8da553b736ac630f2807cbf0c47942 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 18 Sep 2021 20:32:24 +0300 Subject: [PATCH 07/17] [UI] android.app.NativeActivity > WindowedAppActivity + code style --- .clang-format | 5 + .../app/src/main/AndroidManifest.xml | 25 +- .../java/jp/xenia/emulator/DemoActivity.java | 12 - .../jp/xenia/emulator/WindowDemoActivity.java | 8 + .../xenia/emulator/WindowedAppActivity.java | 45 ++++ ...vity_demo.xml => activity_window_demo.xml} | 2 +- src/xenia/ui/windowed_app.cc | 25 ++ src/xenia/ui/windowed_app.h | 77 ++++-- src/xenia/ui/windowed_app_context_android.cc | 234 ++++++++++++++---- src/xenia/ui/windowed_app_context_android.h | 50 ++-- 10 files changed, 377 insertions(+), 106 deletions(-) delete mode 100644 android/android_studio_project/app/src/main/java/jp/xenia/emulator/DemoActivity.java create mode 100644 android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java create mode 100644 android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java rename android/android_studio_project/app/src/main/res/layout/{activity_demo.xml => activity_window_demo.xml} (76%) create mode 100644 src/xenia/ui/windowed_app.cc diff --git a/.clang-format b/.clang-format index f9aa6536d..e4310ac3c 100644 --- a/.clang-format +++ b/.clang-format @@ -6,3 +6,8 @@ SortIncludes: true # Regroup causes unnecessary noise due to clang-format bug. IncludeBlocks: Preserve + +--- +Language: Java +DisableFormat: true +SortIncludes: false diff --git a/android/android_studio_project/app/src/main/AndroidManifest.xml b/android/android_studio_project/app/src/main/AndroidManifest.xml index c5c7c703f..8f6d53cb0 100644 --- a/android/android_studio_project/app/src/main/AndroidManifest.xml +++ b/android/android_studio_project/app/src/main/AndroidManifest.xml @@ -2,12 +2,23 @@ - - - + + + + + - - + + - + + + \ No newline at end of file diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/DemoActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/DemoActivity.java deleted file mode 100644 index 970bd9b03..000000000 --- a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/DemoActivity.java +++ /dev/null @@ -1,12 +0,0 @@ -package jp.xenia.emulator; - -import android.app.Activity; -import android.os.Bundle; - -public class DemoActivity extends Activity { - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - setContentView(R.layout.activity_demo); - } -} \ No newline at end of file diff --git a/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java new file mode 100644 index 000000000..a0dd36f0e --- /dev/null +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowDemoActivity.java @@ -0,0 +1,8 @@ +package jp.xenia.emulator; + +public class WindowDemoActivity extends WindowedAppActivity { + @Override + protected String getWindowedAppIdentifier() { + return "xenia_ui_window_vulkan_demo"; + } +} 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 new file mode 100644 index 000000000..dd89881c3 --- /dev/null +++ b/android/android_studio_project/app/src/main/java/jp/xenia/emulator/WindowedAppActivity.java @@ -0,0 +1,45 @@ +package jp.xenia.emulator; + +import android.app.Activity; +import android.content.res.AssetManager; +import android.os.Bundle; +import android.util.Log; + +public abstract class WindowedAppActivity extends Activity { + private static final String TAG = "WindowedAppActivity"; + + static { + // TODO(Triang3l): Move all demos to libxenia.so. + System.loadLibrary("xenia-ui-window-vulkan-demo"); + } + + private long mAppContext; + + private native long initializeWindowedAppOnCreateNative( + String windowedAppIdentifier, AssetManager assetManager); + + private native void onDestroyNative(long appContext); + + protected abstract String getWindowedAppIdentifier(); + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mAppContext = initializeWindowedAppOnCreateNative(getWindowedAppIdentifier(), getAssets()); + if (mAppContext == 0) { + Log.e(TAG, "Error initializing the windowed app"); + finish(); + return; + } + } + + @Override + protected void onDestroy() { + if (mAppContext != 0) { + onDestroyNative(mAppContext); + } + mAppContext = 0; + super.onDestroy(); + } +} diff --git a/android/android_studio_project/app/src/main/res/layout/activity_demo.xml b/android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml similarity index 76% rename from android/android_studio_project/app/src/main/res/layout/activity_demo.xml rename to android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml index ed5456938..79f49f81a 100644 --- a/android/android_studio_project/app/src/main/res/layout/activity_demo.xml +++ b/android/android_studio_project/app/src/main/res/layout/activity_window_demo.xml @@ -3,6 +3,6 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - tools:context="jp.xenia.emulator.DemoActivity"> + tools:context="jp.xenia.emulator.WindowDemoActivity"> \ No newline at end of file diff --git a/src/xenia/ui/windowed_app.cc b/src/xenia/ui/windowed_app.cc new file mode 100644 index 000000000..8e19674ec --- /dev/null +++ b/src/xenia/ui/windowed_app.cc @@ -0,0 +1,25 @@ +/** + ****************************************************************************** + * 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/windowed_app.h" + +#include +#include + +namespace xe { +namespace ui { + +#if XE_UI_WINDOWED_APPS_IN_LIBRARY +// A zero-initialized pointer to remove dependence on the initialization order +// of the map relatively to the app creator proxies. +std::unordered_map* WindowedApp::creators_; +#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY + +} // namespace ui +} // namespace xe diff --git a/src/xenia/ui/windowed_app.h b/src/xenia/ui/windowed_app.h index b521a759e..89d4eff1f 100644 --- a/src/xenia/ui/windowed_app.h +++ b/src/xenia/ui/windowed_app.h @@ -13,15 +13,17 @@ #include #include #include +#include +#include #include +#include "xenia/base/assert.h" #include "xenia/base/platform.h" #include "xenia/ui/windowed_app_context.h" #if XE_PLATFORM_ANDROID -#include - -#include "xenia/ui/windowed_app_context_android.h" +// Multiple apps in a single library instead of separate executables. +#define XE_UI_WINDOWED_APPS_IN_LIBRARY 1 #endif namespace xe { @@ -36,6 +38,9 @@ class WindowedApp { // initialization of platform-specific parts, should preferably be as simple // as possible). + using Creator = std::unique_ptr (*)( + xe::ui::WindowedAppContext& app_context); + WindowedApp(const WindowedApp& app) = delete; WindowedApp& operator=(const WindowedApp& app) = delete; virtual ~WindowedApp() = default; @@ -101,27 +106,67 @@ class WindowedApp { std::string name_; std::string positional_options_usage_; std::vector positional_options_; + +#if XE_UI_WINDOWED_APPS_IN_LIBRARY + public: + class CreatorRegistration { + public: + CreatorRegistration(const std::string_view identifier, Creator creator) { + if (!creators_) { + // Will be deleted by the last creator registration's destructor, no + // need for a library destructor. + creators_ = new std::unordered_map; + } + iterator_inserted_ = creators_->emplace(identifier, creator); + assert_true(iterator_inserted_.second); + } + + ~CreatorRegistration() { + if (iterator_inserted_.second) { + creators_->erase(iterator_inserted_.first); + if (creators_->empty()) { + delete creators_; + } + } + } + + private: + std::pair::iterator, bool> + iterator_inserted_; + }; + + static Creator GetCreator(const std::string& identifier) { + if (!creators_) { + return nullptr; + } + auto it = creators_->find(identifier); + return it != creators_->end() ? it->second : nullptr; + } + + private: + static std::unordered_map* creators_; +#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY }; -#if XE_PLATFORM_ANDROID -// Multiple apps in a single library. ANativeActivity_onCreate chosen via -// android.app.func_name of the NativeActivity of each app. -#define XE_DEFINE_WINDOWED_APP(export_name, creator) \ - __attribute__((visibility("default"))) extern "C" void export_name( \ - ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \ - xe::ui::AndroidWindowedAppContext::StartAppOnActivityCreate( \ - activity, saved_state, saved_state_size, creator); \ +#if XE_UI_WINDOWED_APPS_IN_LIBRARY +// Multiple apps in a single library. +#define XE_DEFINE_WINDOWED_APP(identifier, creator) \ + namespace xe { \ + namespace ui { \ + namespace windowed_app_creator_registrations { \ + xe::ui::WindowedApp::CreatorRegistration identifier(#identifier, creator); \ + } \ + } \ } #else // Separate executables for each app. std::unique_ptr (*GetWindowedAppCreator())( WindowedAppContext& app_context); -#define XE_DEFINE_WINDOWED_APP(export_name, creator) \ - std::unique_ptr (*xe::ui::GetWindowedAppCreator())( \ - xe::ui::WindowedAppContext & app_context) { \ - return creator; \ +#define XE_DEFINE_WINDOWED_APP(identifier, creator) \ + xe::ui::WindowedApp::Creator xe::ui::GetWindowedAppCreator() { \ + return creator; \ } -#endif +#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY } // namespace ui } // namespace xe diff --git a/src/xenia/ui/windowed_app_context_android.cc b/src/xenia/ui/windowed_app_context_android.cc index fcab4f5fd..5af4efad6 100644 --- a/src/xenia/ui/windowed_app_context_android.cc +++ b/src/xenia/ui/windowed_app_context_android.cc @@ -9,10 +9,12 @@ #include "xenia/ui/windowed_app_context_android.h" +#include #include +#include #include -#include #include +#include #include #include #include @@ -25,30 +27,6 @@ namespace xe { namespace ui { -void AndroidWindowedAppContext::StartAppOnActivityCreate( - ANativeActivity* activity, [[maybe_unused]] void* saved_state, - [[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; - if (!app_context->Initialize(activity)) { - delete app_context; - ANativeActivity_finish(activity); - return; - } - // The pointer is now held by the Activity as its ANativeActivity::instance, - // until the destruction. - if (!app_context->InitializeApp(app_creator)) { - // InitializeApp might have sent commands to the UI thread looper callback - // pipe, perform deferred destruction. - app_context->RequestDestruction(); - ANativeActivity_finish(activity); - return; - } -} - void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() { // Don't check ui_thread_looper_callback_registered_, as it's owned // exclusively by the UI thread, while this may be called by any, and in case @@ -69,22 +47,145 @@ void AndroidWindowedAppContext::NotifyUILoopOfPendingFunctions() { void AndroidWindowedAppContext::PlatformQuitFromUIThread() { // All the shutdown will be done in onDestroy of the activity. - ANativeActivity_finish(activity_); + if (activity_ && activity_method_finish_) { + ui_thread_jni_env_->CallVoidMethod(activity_, activity_method_finish_); + } +} + +AndroidWindowedAppContext* +AndroidWindowedAppContext::JniActivityInitializeWindowedAppOnCreate( + JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier, + jobject asset_manager) { + WindowedApp::Creator app_creator; + { + const char* windowed_app_identifier_c_str = + jni_env->GetStringUTFChars(windowed_app_identifier, nullptr); + if (!windowed_app_identifier_c_str) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to get the UTF-8 string for the windowed app identifier"); + return nullptr; + } + app_creator = WindowedApp::GetCreator(windowed_app_identifier_c_str); + if (!app_creator) { + __android_log_print(ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to get the creator for the windowed app %s", + windowed_app_identifier_c_str); + jni_env->ReleaseStringUTFChars(windowed_app_identifier, + windowed_app_identifier_c_str); + return nullptr; + } + jni_env->ReleaseStringUTFChars(windowed_app_identifier, + windowed_app_identifier_c_str); + } + + AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext; + if (!app_context->Initialize(jni_env, activity, asset_manager)) { + delete app_context; + return nullptr; + } + + if (!app_context->InitializeApp(app_creator)) { + // InitializeApp might have sent commands to the UI thread looper callback + // pipe, perform deferred destruction. + app_context->RequestDestruction(); + return nullptr; + } + + return app_context; +} + +void AndroidWindowedAppContext::JniActivityOnDestroy() { + if (app_) { + app_->InvokeOnDestroy(); + app_.reset(); + } + RequestDestruction(); } AndroidWindowedAppContext::~AndroidWindowedAppContext() { Shutdown(); } -bool AndroidWindowedAppContext::Initialize(ANativeActivity* activity) { - int32_t api_level; - { - AConfiguration* configuration = AConfiguration_new(); - AConfiguration_fromAssetManager(configuration, activity->assetManager); - api_level = AConfiguration_getSdkVersion(configuration); - AConfiguration_delete(configuration); +bool AndroidWindowedAppContext::Initialize(JNIEnv* ui_thread_jni_env, + jobject activity, + jobject asset_manager) { + // Xenia logging is not initialized yet - use __android_log_write or + // __android_log_print until InitializeAndroidAppFromMainThread is done. + + ui_thread_jni_env_ = ui_thread_jni_env; + + // Initialize the asset manager for retrieving the current configuration. + asset_manager_jobject_ = ui_thread_jni_env_->NewGlobalRef(asset_manager); + if (!asset_manager_jobject_) { + __android_log_write( + ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to create a global reference to the asset manager"); + Shutdown(); + return false; } - xe::InitializeAndroidAppFromMainThread(api_level); + asset_manager_ = + AAssetManager_fromJava(ui_thread_jni_env_, asset_manager_jobject_); + if (!asset_manager_) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to create get the AAssetManager"); + Shutdown(); + return false; + } + + // Get the initial configuration. + configuration_ = AConfiguration_new(); + if (!configuration_) { + __android_log_write(ANDROID_LOG_ERROR, "AndroidWindowedAppContext", + "Failed to create an AConfiguration"); + Shutdown(); + return false; + } + AConfiguration_fromAssetManager(configuration_, asset_manager_); + + // Initialize Xenia globals that may depend on the API level, as well as + // logging. + xe::InitializeAndroidAppFromMainThread( + AConfiguration_getSdkVersion(configuration_)); android_base_initialized_ = true; + // Initialize interfacing with the WindowedAppActivity. + activity_ = ui_thread_jni_env_->NewGlobalRef(activity); + if (!activity_) { + XELOGE( + "AndroidWindowedAppContext: Failed to create a global reference to the " + "activity"); + Shutdown(); + return false; + } + { + jclass activity_class_local_ref = + ui_thread_jni_env_->GetObjectClass(activity); + if (!activity_class_local_ref) { + XELOGE("AndroidWindowedAppContext: Failed to get the activity class"); + Shutdown(); + return false; + } + activity_class_ = reinterpret_cast(ui_thread_jni_env_->NewGlobalRef( + reinterpret_cast(activity_class_local_ref))); + ui_thread_jni_env_->DeleteLocalRef( + reinterpret_cast(activity_class_local_ref)); + } + if (!activity_class_) { + XELOGE( + "AndroidWindowedAppContext: Failed to create a global reference to the " + "activity class"); + Shutdown(); + return false; + } + bool activity_ids_obtained = true; + activity_ids_obtained &= + (activity_method_finish_ = ui_thread_jni_env_->GetMethodID( + activity_class_, "finish", "()V")) != nullptr; + if (!activity_ids_obtained) { + XELOGE("AndroidWindowedAppContext: Failed to get the activity class IDs"); + Shutdown(); + return false; + } + // Initialize sending commands to the UI thread looper callback, for // requesting function calls in the UI thread. ui_thread_looper_ = ALooper_forThread(); @@ -117,10 +218,6 @@ bool AndroidWindowedAppContext::Initialize(ANativeActivity* activity) { } ui_thread_looper_callback_registered_ = true; - activity_ = activity; - activity_->instance = this; - activity_->callbacks->onDestroy = OnActivityDestroy; - return true; } @@ -135,12 +232,6 @@ void AndroidWindowedAppContext::Shutdown() { assert_null(activity_window_); activity_window_ = nullptr; - if (activity_) { - activity_->callbacks->onDestroy = nullptr; - activity_->instance = nullptr; - activity_ = nullptr; - } - if (ui_thread_looper_callback_registered_) { ALooper_removeFd(ui_thread_looper_, ui_thread_looper_callback_pipe_[0]); ui_thread_looper_callback_registered_ = false; @@ -157,10 +248,34 @@ void AndroidWindowedAppContext::Shutdown() { ui_thread_looper_ = nullptr; } + activity_method_finish_ = nullptr; + if (activity_class_) { + ui_thread_jni_env_->DeleteGlobalRef( + reinterpret_cast(activity_class_)); + activity_class_ = nullptr; + } + if (activity_) { + ui_thread_jni_env_->DeleteGlobalRef(activity_); + activity_ = nullptr; + } + if (android_base_initialized_) { xe::ShutdownAndroidAppFromMainThread(); android_base_initialized_ = false; } + + if (configuration_) { + AConfiguration_delete(configuration_); + configuration_ = nullptr; + } + + asset_manager_ = nullptr; + if (asset_manager_jobject_) { + ui_thread_jni_env_->DeleteGlobalRef(asset_manager_jobject_); + asset_manager_jobject_ = nullptr; + } + + ui_thread_jni_env_ = nullptr; } void AndroidWindowedAppContext::RequestDestruction() { @@ -260,15 +375,26 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr ( return true; } -void AndroidWindowedAppContext::OnActivityDestroy(ANativeActivity* activity) { - auto& app_context = - *static_cast(activity->instance); - if (app_context.app_) { - app_context.app_->InvokeOnDestroy(); - app_context.app_.reset(); - } - app_context.RequestDestruction(); -} - } // namespace ui } // namespace xe + +extern "C" { + +JNIEXPORT jlong JNICALL +Java_jp_xenia_emulator_WindowedAppActivity_initializeWindowedAppOnCreateNative( + JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier, + jobject asset_manager) { + return reinterpret_cast( + xe::ui::AndroidWindowedAppContext :: + JniActivityInitializeWindowedAppOnCreate( + jni_env, activity, windowed_app_identifier, asset_manager)); +} + +JNIEXPORT void JNICALL +Java_jp_xenia_emulator_WindowedAppActivity_onDestroyNative( + JNIEnv* jni_env, jobject activity, jlong app_context_ptr) { + reinterpret_cast(app_context_ptr) + ->JniActivityOnDestroy(); +} + +} // extern "C" diff --git a/src/xenia/ui/windowed_app_context_android.h b/src/xenia/ui/windowed_app_context_android.h index 64e45372d..91cd10427 100644 --- a/src/xenia/ui/windowed_app_context_android.h +++ b/src/xenia/ui/windowed_app_context_android.h @@ -10,8 +10,10 @@ #ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ #define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_ +#include +#include #include -#include +#include #include #include @@ -25,13 +27,6 @@ class WindowedApp; class AndroidWindowedAppContext final : public WindowedAppContext { public: - // For calling from android.app.func_name exports. - static void StartAppOnActivityCreate( - ANativeActivity* activity, void* saved_state, size_t saved_state_size, - std::unique_ptr (*app_creator)( - WindowedAppContext& app_context)); - - ANativeActivity* activity() const { return activity_; } WindowedApp* app() const { return app_.get(); } void NotifyUILoopOfPendingFunctions() override; @@ -45,6 +40,12 @@ class AndroidWindowedAppContext final : public WindowedAppContext { AndroidWindow* GetActivityWindow() const { return activity_window_; } void SetActivityWindow(AndroidWindow* window) { activity_window_ = window; } + // For calling from WindowedAppActivity native methods. + static AndroidWindowedAppContext* JniActivityInitializeWindowedAppOnCreate( + JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier, + jobject asset_manager); + void JniActivityOnDestroy(); + private: enum class UIThreadLooperCallbackCommand : uint8_t { kDestroy, @@ -55,13 +56,14 @@ class AndroidWindowedAppContext final : public WindowedAppContext { // Don't delete this object directly externally if successfully initialized as // the looper may still execute the callback for pending commands after an - // external ANativeActivity_removeFd, and the callback receives a pointer to - // the context - deletion must be deferred and done in the callback itself. + // external ALooper_removeFd, and the callback receives a pointer to the + // context - deletion must be deferred and done in the callback itself. // Defined in the translation unit where WindowedApp is complete because of // std::unique_ptr. ~AndroidWindowedAppContext(); - bool Initialize(ANativeActivity* activity); + bool Initialize(JNIEnv* ui_thread_jni_env, jobject activity, + jobject asset_manager); void Shutdown(); // Call this function instead of deleting the object directly, so if needed, @@ -75,10 +77,29 @@ class AndroidWindowedAppContext final : public WindowedAppContext { bool InitializeApp(std::unique_ptr (*app_creator)( WindowedAppContext& app_context)); - static void OnActivityDestroy(ANativeActivity* activity); + // Useful notes about JNI usage on Android within Xenia: + // - All static libraries defining JNI native functions must be linked to + // shared libraries via LOCAL_WHOLE_STATIC_LIBRARIES. + // - If method or field IDs are cached, a global reference to the class needs + // to be held - it prevents the class from being unloaded by the class + // loaders (in a way that would make the IDs invalid when it's reloaded). + // - GetStringUTFChars (UTF-8) returns null-terminated strings, GetStringChars + // (UTF-16) does not. + JNIEnv* ui_thread_jni_env_ = nullptr; + + // The object reference must be held by the app according to + // AAssetManager_fromJava documentation. + jobject asset_manager_jobject_ = nullptr; + AAssetManager* asset_manager_ = nullptr; + + AConfiguration* configuration_ = nullptr; bool android_base_initialized_ = false; + jobject activity_ = nullptr; + jclass activity_class_ = nullptr; + jmethodID activity_method_finish_ = nullptr; + // May be read by non-UI threads in NotifyUILoopOfPendingFunctions. ALooper* ui_thread_looper_ = nullptr; // [1] (the write file descriptor) may be referenced as read-only by non-UI @@ -86,11 +107,6 @@ class AndroidWindowedAppContext final : public WindowedAppContext { std::array ui_thread_looper_callback_pipe_{-1, -1}; bool ui_thread_looper_callback_registered_ = false; - // TODO(Triang3l): Switch from ANativeActivity to the context itself being the - // object for communication with the Java code when NativeActivity isn't used - // anymore as its functionality is heavily limited. - ANativeActivity* activity_ = nullptr; - AndroidWindow* activity_window_ = nullptr; std::unique_ptr app_; From 36d8b20287a3f1f02fe25d1b5cbe6e9dd983e823 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 18 Sep 2021 20:33:00 +0300 Subject: [PATCH 08/17] [Docs] Android code style guide --- docs/style_guide.md | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/docs/style_guide.md b/docs/style_guide.md index f61590f9f..b211469c4 100644 --- a/docs/style_guide.md +++ b/docs/style_guide.md @@ -1,4 +1,4 @@ -# Style Guide +# C++ Style Guide The style guide can be summed up as 'clang-format with the Google style set'. In addition, the [Google Style Guide](https://google.github.io/styleguide/cppguide.html) @@ -40,7 +40,7 @@ The buildbot runs `xb lint --all` on the master branch, and will run `xb lint --origin` on pull requests. Run `xb format` before you commit each local change so that you are consistently clean, otherwise you may have to rebase. If you forget, run `xb format --origin` and rebase your changes (so you -don't end up with 5 changes and then a 6th 'whoops' one - that's nasty). +don't end up with 5 changes and then a 6th 'whoops' one — that's nasty). The buildbot is running LLVM 3.8.0. If you are noticing style differences between your local lint/format and the buildbot, ensure you are running that @@ -82,3 +82,44 @@ tabs or linefeeds or whatever again. TODO(benvanik): write a cool script to do this/editor plugins. In the future, the linter will run as a git commit hook and on travis. + +# Android Style Guide + +Android Java and Groovy code and XML files currently don't have automatic format +verification during builds, however, stick to the [AOSP Java Code Style Rules](https://source.android.com/setup/contribute/code-style), +which contain guidelines not only for code formatting, but for the usage of +language features as well. + +The formatting rules used in Xenia match the default Android Studio settings. +They diverge from the C++ code style rules of Xenia in many areas, such as +indentation width and the maximum line length, however, the goal for Android +formatting in Xenia is to ensure quick development environment setup. + +In Java code, limit the length of each line to 100 characters. If an assignment +doesn't fit in the limit, move the right-hand side of it to a separate line with +8-space indentation. Similarly, if the argument list of a method declaration or +a call is too long, start the entire argument list on a new line, also indented +with 8 spaces — this is one of the differences from the C++ code style in Xenia, +where arguments may be aligned with the opening bracket. In general, follow the +[rectangle rule](https://github.com/google/google-java-format/wiki/The-Rectangle-Rule) +so expressions in the code constitute a hierarchy of their bounding rectangles, +ensuring that with 4-space indentation for block contents and 8-space +indentation for subexpressions. + +In XML files, if the width of the line with an element exceeds 100 characters, +or in most cases when there are multiple attributes, each attribute should be +placed on a separate line with 4-space indentation, with the exception of the +first `xmlns`, which should stay on the same line as the element name. + +In Groovy, use 4-space indentation for blocks and 8-space indentation for +splitting arguments into multiple lines. String literals should be written in +single quotes unless string interpolation is used. + +You can use the Code -> Reformat Code and Code -> Reformat File options in +Android Studio to apply coarse formatting rules for different kinds of files +supported by Android Studio, such as Java, XML and Groovy. While clang-format is +very strict and generates code with the single allowed way of formatting, +Android Studio, however, preserves many style choices in the original code, so +it's recommended to approximate the final style manually instead of relying +entirely on automatic formatting. Also use Code -> Rearrange Code to maintain a +consistent structure of Java class declarations. From 247cb91ac5c3d8f7f087f45905315f1db8562879 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Wed, 22 Sep 2021 15:04:00 +0200 Subject: [PATCH 09/17] [Base] Replace GCC workaround (loop opt bug) Previous workaround was dangerous, this one is more sane. https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100801#c3 --- src/xenia/base/memory.cc | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/src/xenia/base/memory.cc b/src/xenia/base/memory.cc index b675e059f..8acbf43bd 100644 --- a/src/xenia/base/memory.cc +++ b/src/xenia/base/memory.cc @@ -48,10 +48,10 @@ void copy_128_aligned(void* dest, const void* src, size_t count) { // https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100801 // TODO(Joel Linn): Remove this when fixed GCC versions are common place. #if XE_COMPILER_GNUC -#define XE_WORKAROUND_LOOP_KILL_MOD(x) \ - if ((count % (x)) == 0) __builtin_unreachable(); +#define XE_WORKAROUND_CONSTANT_RETURN_IF(x) \ + if (__builtin_constant_p(x) && (x)) return; #else -#define XE_WORKAROUND_LOOP_KILL_MOD(x) +#define XE_WORKAROUND_CONSTANT_RETURN_IF(x) #endif void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr, size_t count) { @@ -70,8 +70,8 @@ void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr, __m128i output = _mm_shuffle_epi8(input, shufmask); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 8 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(8); dest[i] = byte_swap(src[i]); } } @@ -90,8 +90,8 @@ void copy_and_swap_16_unaligned(void* dest_ptr, const void* src_ptr, __m128i output = _mm_shuffle_epi8(input, shufmask); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 8 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(8); dest[i] = byte_swap(src[i]); } } @@ -113,8 +113,8 @@ void copy_and_swap_32_aligned(void* dest_ptr, const void* src_ptr, __m128i output = _mm_shuffle_epi8(input, shufmask); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(4); dest[i] = byte_swap(src[i]); } } @@ -133,8 +133,8 @@ void copy_and_swap_32_unaligned(void* dest_ptr, const void* src_ptr, __m128i output = _mm_shuffle_epi8(input, shufmask); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(4); dest[i] = byte_swap(src[i]); } } @@ -156,8 +156,8 @@ void copy_and_swap_64_aligned(void* dest_ptr, const void* src_ptr, __m128i output = _mm_shuffle_epi8(input, shufmask); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 2 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(2); dest[i] = byte_swap(src[i]); } } @@ -176,8 +176,8 @@ void copy_and_swap_64_unaligned(void* dest_ptr, const void* src_ptr, __m128i output = _mm_shuffle_epi8(input, shufmask); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 2 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(2); dest[i] = byte_swap(src[i]); } } @@ -193,8 +193,8 @@ void copy_and_swap_16_in_32_aligned(void* dest_ptr, const void* src_ptr, _mm_or_si128(_mm_slli_epi32(input, 16), _mm_srli_epi32(input, 16)); _mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(4); dest[i] = (src[i] >> 16) | (src[i] << 16); } } @@ -210,8 +210,8 @@ void copy_and_swap_16_in_32_unaligned(void* dest_ptr, const void* src_ptr, _mm_or_si128(_mm_slli_epi32(input, 16), _mm_srli_epi32(input, 16)); _mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output); } + XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0); for (; i < count; ++i) { // handle residual elements - XE_WORKAROUND_LOOP_KILL_MOD(4); dest[i] = (src[i] >> 16) | (src[i] << 16); } } From cfd18b89f8fc5ebddc7a29ea777c60e7fa0eb219 Mon Sep 17 00:00:00 2001 From: Joel Linn Date: Wed, 22 Sep 2021 15:06:10 +0200 Subject: [PATCH 10/17] [GPU] GCC build fix for render target cache --- src/xenia/gpu/render_target_cache.cc | 4 ++-- src/xenia/gpu/render_target_cache.h | 27 +++++++++++++++++++-------- 2 files changed, 21 insertions(+), 10 deletions(-) diff --git a/src/xenia/gpu/render_target_cache.cc b/src/xenia/gpu/render_target_cache.cc index 2dcf93789..5b0ba7c66 100644 --- a/src/xenia/gpu/render_target_cache.cc +++ b/src/xenia/gpu/render_target_cache.cc @@ -1342,7 +1342,7 @@ void RenderTargetCache::ChangeOwnership( nullptr, resolve_clear_cutout)) { RenderTargetKey transfer_host_depth_source = host_depth_encoding_different - ? it->second.host_depth_render_targets[dest.resource_format] + ? it->second.GetHostDepthRenderTarget(dest.GetDepthFormat()) : RenderTargetKey(); if (transfer_host_depth_source == transfer_source) { // Same render target, don't provide a separate host depth source. @@ -1387,7 +1387,7 @@ void RenderTargetCache::ChangeOwnership( // Claim the current range. it->second.render_target = dest; if (host_depth_encoding_different) { - it->second.host_depth_render_targets[dest.resource_format] = dest; + it->second.GetHostDepthRenderTarget(dest.GetDepthFormat()) = dest; } // Check if can merge with the next range after claiming. std::map::iterator it_next; diff --git a/src/xenia/gpu/render_target_cache.h b/src/xenia/gpu/render_target_cache.h index bf7c9a83e..9fe068f40 100644 --- a/src/xenia/gpu/render_target_cache.h +++ b/src/xenia/gpu/render_target_cache.h @@ -538,13 +538,8 @@ class RenderTargetCache { // float32 value to that of an unorm24 with a totally wrong value). If the // range hasn't been used yet (render_target.IsEmpty() == true), these are // empty too. - union { - struct { - RenderTargetKey host_depth_render_target_unorm24; - RenderTargetKey host_depth_render_target_float24; - }; - RenderTargetKey host_depth_render_targets[2]; - }; + RenderTargetKey host_depth_render_target_unorm24; + RenderTargetKey host_depth_render_target_float24; OwnershipRange(uint32_t end_tiles, RenderTargetKey render_target, RenderTargetKey host_depth_render_target_unorm24, RenderTargetKey host_depth_render_target_float24) @@ -552,6 +547,22 @@ class RenderTargetCache { render_target(render_target), host_depth_render_target_unorm24(host_depth_render_target_unorm24), host_depth_render_target_float24(host_depth_render_target_float24) {} + const RenderTargetKey& GetHostDepthRenderTarget( + xenos::DepthRenderTargetFormat resource_format) const { + assert_true( + resource_format == xenos::DepthRenderTargetFormat::kD24S8 || + resource_format == xenos::DepthRenderTargetFormat::kD24FS8, + "Illegal resource format"); + return resource_format == xenos::DepthRenderTargetFormat::kD24S8 + ? host_depth_render_target_unorm24 + : host_depth_render_target_float24; + } + RenderTargetKey& GetHostDepthRenderTarget( + xenos::DepthRenderTargetFormat resource_format) { + return const_cast( + const_cast(this)->GetHostDepthRenderTarget( + resource_format)); + } bool IsOwnedBy(RenderTargetKey key, bool host_depth_encoding_different) const { if (render_target != key) { @@ -561,7 +572,7 @@ class RenderTargetCache { return false; } if (host_depth_encoding_different && !key.is_depth && - host_depth_render_targets[key.resource_format] != key) { + GetHostDepthRenderTarget(key.GetDepthFormat()) != key) { // Depth encoding is the same, but different addressing is needed. return false; } From 5161bd7ab26b43ecd1cff67893e9618410c67b25 Mon Sep 17 00:00:00 2001 From: soopercool101 Date: Tue, 28 Sep 2021 14:14:19 -0700 Subject: [PATCH 11/17] Fix "404 not found" on "Build commit on Github..." --- src/xenia/app/emulator_window.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 156946750..62d288f19 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -429,7 +429,7 @@ void EmulatorWindow::ShowCommitID() { "https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER); #else LaunchWebBrowser( - "https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/"); + "https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT); #endif } From d6660ac391a1d3ec148fa768f059c8b5c06d1d36 Mon Sep 17 00:00:00 2001 From: Gliniak Date: Thu, 14 Oct 2021 11:33:56 +0200 Subject: [PATCH 12/17] [Kernel] Added %L to formatter --- src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc index 66a2141d7..2cfcb50ac 100644 --- a/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc +++ b/src/xenia/kernel/xboxkrnl/xboxkrnl_strings.cc @@ -290,6 +290,13 @@ int32_t format_core(PPCContext* ppc_context, FormatData& data, ArgList& args, } state = FS_Type; continue; + } else if (c == 'L') { + // 58410826 incorrectly uses 'L' instead of 'l'. + // TODO(gibbed): L appears to be treated as an invalid token by + // xboxkrnl, investigate how invalid tokens are processed in xboxkrnl + // formatting when state FF_Type is reached. + state = FS_Type; + continue; } else if (c == 'h') { flags |= FF_IsShort; state = FS_Type; From 28fec845d5a457974181146d76e88b5e3f58b7d7 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Fri, 22 Oct 2021 20:00:41 +0300 Subject: [PATCH 13/17] [GPU] Document memexport/resolve formats with more details --- src/xenia/gpu/draw_util.cc | 5 +- .../gpu/dxbc_shader_translator_memexport.cc | 8 + src/xenia/gpu/registers.h | 16 +- src/xenia/gpu/xenos.h | 138 ++++++++++++++++-- 4 files changed, 141 insertions(+), 26 deletions(-) diff --git a/src/xenia/gpu/draw_util.cc b/src/xenia/gpu/draw_util.cc index 5157c1a88..a91db496c 100644 --- a/src/xenia/gpu/draw_util.cc +++ b/src/xenia/gpu/draw_util.cc @@ -876,8 +876,9 @@ bool GetResolveInfo(const RegisterFile& regs, const Memory& memory, } info_out.address.copy_sample_select = sample_select; // Get the format to pass to the shader in a unified way - for depth (for - // which Direct3D 9 specifies the k_8_8_8_8 destination format), make sure the - // shader won't try to do conversion - pass proper k_24_8 or k_24_8_FLOAT. + // which Direct3D 9 specifies the k_8_8_8_8 uint destination format), make + // sure the shader won't try to do conversion - pass proper k_24_8 or + // k_24_8_FLOAT. auto rb_copy_dest_info = regs.Get(); xenos::TextureFormat dest_format; auto rb_depth_info = regs.Get(); diff --git a/src/xenia/gpu/dxbc_shader_translator_memexport.cc b/src/xenia/gpu/dxbc_shader_translator_memexport.cc index 87de6cce3..4e8a43b62 100644 --- a/src/xenia/gpu/dxbc_shader_translator_memexport.cc +++ b/src/xenia/gpu/dxbc_shader_translator_memexport.cc @@ -15,6 +15,14 @@ namespace xe { namespace gpu { using namespace ucode; +// TODO(Triang3l): Support sub-dword memexports (like k_8 in 58410B86). This +// would require four 128 MB R8_UINT UAVs due to the Nvidia addressing limit. +// Need to be careful with resource binding tiers, however. Resource binding +// tier 1 on feature level 11_0 allows only 8 UAVs _across all stages_. +// RWByteAddressBuffer + 4 typed buffers is 5 per stage already, would need 10 +// for both VS and PS, or even 11 with the eDRAM ROV. Need to drop draw commands +// doing memexport in both VS and PS on FL 11_0 resource binding tier 1. + void DxbcShaderTranslator::ExportToMemory_PackFixed32( const uint32_t* eM_temps, uint32_t eM_count, const uint32_t bits[4], const dxbc::Src& is_integer, const dxbc::Src& is_signed) { diff --git a/src/xenia/gpu/registers.h b/src/xenia/gpu/registers.h index 69d922d8b..029a2d6d8 100644 --- a/src/xenia/gpu/registers.h +++ b/src/xenia/gpu/registers.h @@ -712,14 +712,14 @@ static_assert_size(RB_COPY_CONTROL, sizeof(uint32_t)); union alignas(uint32_t) RB_COPY_DEST_INFO { struct { - xenos::Endian128 copy_dest_endian : 3; // +0 - uint32_t copy_dest_array : 1; // +3 - uint32_t copy_dest_slice : 3; // +4 - xenos::ColorFormat copy_dest_format : 6; // +7 - uint32_t copy_dest_number : 3; // +13 - int32_t copy_dest_exp_bias : 6; // +16 - uint32_t : 2; // +22 - uint32_t copy_dest_swap : 1; // +24 + xenos::Endian128 copy_dest_endian : 3; // +0 + uint32_t copy_dest_array : 1; // +3 + uint32_t copy_dest_slice : 3; // +4 + xenos::ColorFormat copy_dest_format : 6; // +7 + xenos::SurfaceNumberFormat copy_dest_number : 3; // +13 + int32_t copy_dest_exp_bias : 6; // +16 + uint32_t : 2; // +22 + uint32_t copy_dest_swap : 1; // +24 }; uint32_t value; static constexpr Register register_index = XE_GPU_REG_RB_COPY_DEST_INFO; diff --git a/src/xenia/gpu/xenos.h b/src/xenia/gpu/xenos.h index 2a0b6c938..2f0ee64e2 100644 --- a/src/xenia/gpu/xenos.h +++ b/src/xenia/gpu/xenos.h @@ -185,7 +185,7 @@ enum class IndexFormat : uint32_t { }; // SurfaceNumberX from yamato_enum.h. -enum class SurfaceNumFormat : uint32_t { +enum class SurfaceNumberFormat : uint32_t { kUnsignedRepeatingFraction = 0, // Microsoft-style, scale factor (2^(n-1))-1. kSignedRepeatingFraction = 1, @@ -1176,14 +1176,120 @@ union alignas(uint32_t) xe_gpu_fetch_group_t { }; static_assert_size(xe_gpu_fetch_group_t, sizeof(uint32_t) * 6); -// GPU_MEMEXPORT_STREAM_CONSTANT from a game .pdb - float constant for memexport -// stream configuration. -// This is used with the floating-point ALU in shaders (written to eA using -// mad), so the dwords have a normalized exponent when reinterpreted as floats -// (otherwise they would be flushed to zero), but actually these are packed -// integers. dword_1 specifically is 2^23 because -// powf(2.0f, 23.0f) + float(i) == 0x4B000000 | i -// so mad can pack indices as integers in the lower bits. +// Shader memory export (memexport) allows for writing of arbitrary formatted +// data with random access / scatter capabilities. It provides functionality +// largely similar to resolving - format packing, supporting arbitrary color +// formats, from sub-dword ones such as k_8 in 58410B86, to 128-bit ones, with +// endian swap similar to how it's performed in resolves (up to 128-bit); +// specifying the number format, swapping red and blue channels - though with no +// exponent biasing. Unlike resolving, however, instead of writing to tiled +// textures, it exports the data to up to 5 elements (the eM# shader registers, +// each corresponding to `base address + element size * (offset + 0...4)`) in a +// stream defined by a stream constant and an offset in elements written to eA - +// a shader, however, can write to multiple streams with different or the same +// stream constants, by performing `alloc export` multiple times. It's used +// mostly in vertex shaders (most commonly in improvised "compute shaders" done +// by executing a vertex shader for a number of point-type primitives covering +// nothing), though usage in pixel shaders is also possible - an example is +// provided in the "Advanced Screenspace Antialiasing" presentation by Arne +// Schober. +// https://ubm-twvideo01.s3.amazonaws.com/o1/vault/gdceurope2010/slides/A_Schober_Advanced_Screenspace_Antialiasing.pdf +// +// Unlike fetch constants, which are passed via special registers, a memory +// export stream is configured by writing the stream constant and the offset to +// a shader export register (eA) allocated by the shader - similar to more +// conventional exports like oPos, o#, oC#. Therefore, in general, it's not +// possible to know what its value will be without running the shader. For +// emulation, this means that the memory range referenced by an export - that +// needs to be validated - requires running the shader on the CPU in general. +// Thankfully, however, the usual way of setting up eA is by executing: +// `mad eA, r#, const0100, c#` +// where c# is the stream float4 constant from the float constant registers, and +// const0100 is a literal (0.0f, 1.0f, 0.0f, 0.0f) constant, also from the float +// constant registers, used for placing the element index (r#) in the correct +// component of eA. This allows for easy gathering of memexport stream +// constants, which contain both the base address and the size of the +// destination buffer for bounds checking, from the shader code and the float +// constant registers, as long as the guest uses this instruction pattern to +// write to eA. +// +// The Xenos doesn't have an integer ALU, and denormals are treated as zero and +// are flushed. However, eA contains integers and bit fields. A stream constant +// is thus structured in a way that allows for packing integers in normalized +// floating-point numbers. +// +// X contains the base address of the stream in dwords as integer bits in the +// lower 30 bits, and bits 0b01 in the top. The 0b01 bits make the exponent +// nonzero, so the number is considered normalized, and therefore isn't flushed +// to zero. With only 512 MB of the physical memory on the Xbox 360, the +// exponent can't become 0b11111111, so X also won't be NaN for any valid Xbox +// 360 physical address (though in general the GPU supports 32-bit addresses, +// but this is originally an Xbox 360-specific feature, that was later, however, +// likely reused for GL_QCOM_writeonly_rendering). +// +// TODO(Triang3l): Verify whether GL_QCOM_writeonly_rendering is actually +// memexport on the Adreno 2xx using GL_OES_get_program_binary - it's also +// interesting to see how alphatest interacts with it, whether it's still true +// fixed-function alphatest, as it's claimed to be supported as usual by the +// extension specification - it's likely, however, that memory exports are +// discarded alongside other exports such as oC# and oDepth this way. +// +// Y of eA contains the offset in elements - this is what shaders are supposed +// to calculate from something like the vertex index. Again, it's specified as +// an integer in the low bits, not as a truly floating-point number. For this +// purpose, stream constants contain the value 2^23 - when a whole +// floating-point number smaller than 2^23 is added as floating-point to 2^23, +// its integer representation becomes the mantissa bits of a number with an +// exponent of 23. Via multiply-add, `offset * 1.0f + exp2f(23)` is written here +// by the shader, allowing for element offsets of up to 2^23 - 1. +// +// Z is a bit field with the information about the formatting of the data. It's +// also packed as a normalized floating-point number, but in a cleaner way than +// X because not as many bits are required - just like Y, it has an exponent of +// 23 (possibly to let shaders build these values manually using floating-point +// multiply-add like integer shift-or, and finally to add 2^23, though that's +// not a case easy to handle in emulation, unlike prebuilt stream constants). +// +// W contains the number of elements in the stream. It's also packed with the +// full 23 exponent just like Y and Z, there's no way to index more than 2^23 +// elements using packing via addition to 2^23, so this field also doesn't need +// more bits than that. +// +// Examples of setup in titles (Z from MSB to LSB): +// +// 4D5307E6 particles (different VS invocation counts, like 1, 2, 4): +// There is a passthrough shader - useful for verification as it simply writes +// directly what it reads via vfetch of various formats. Another shader (with +// different c# numbers, but same formats) does complicated math to process the +// particles. +// c152: Z = 010010110000|0|111|00|100110|00000|010, count = 35840 +// 8in32, 32_32_32_32_FLOAT, float, RGBA - from 32_32_32_32_FLOAT vfetch +// c154, 162: Z = 010010110000|0|111|00|100000|00000|001, count = 71680 +// 8in16, 16_16_16_16_FLOAT, float, RGBA - from 16_16_16_16_FLOAT vfetch +// c156, 158, 160: Z = 010010110000|0|000|00|011010|00000|001, count = 71680 +// 8in16, 16_16_16_16, unorm, RGBA - from 16_16_16_16 unorm vfetch +// c164: Z = 010010110000|0|111|00|011111|00000|001, count = 143360 +// 8in16, 16_16_FLOAT, float, RGBA - from 16_16_FLOAT vfetch +// c166: Z = 010010110000|0|000|00|011001|00000|001, count = 143360 +// 8in16, 16_16, unorm, RGBA - from 16_16 unorm vfetch +// c168: Z = 010010110000|0|001|00|000111|00000|010, count = 143360 +// 8in32, 2_10_10_10, snorm, RGBA - from 2_10_10_10 snorm vfetch +// c170, c172: Z = 010010110000|1|000|00|000110|00000|010, count = 143360 +// 8in32, 8_8_8_8, unorm, BGRA - from 8_8_8_8 unorm vfetch with .zyxw swizzle +// +// 4D5307E6 water simulation (2048 VS invocations): +// c130: Z = 010010110000|0|111|00|100110|00000|010, count = 16384 +// 8in32, 32_32_32_32_FLOAT, float, RGBA +// The shader has 5 memexports of this kind and 6 32_32_32_32_FLOAT vfetches. +// +// 4D5307E6 water tessellation factors (1 VS invocation per triangle patch): +// c130: Z = 010010110000|0|111|11|100100|11111|010, count = patch count * 3 +// 8in32, 32_FLOAT, float, RGBA +// +// 41560817 texture memory copying (64 bytes per invocation, two eA, eight eM#): +// c0: Z = 010010110000|0|010|11|011010|00011|001 +// 8in16, 16_16_16_16, uint, RGBA - from 16_16_16_16 uint vfetch +// (16_16_16_16 is the largest color format without special values) union alignas(uint32_t) xe_gpu_memexport_stream_t { struct { uint32_t base_address : 30; // +0 dword_0 physical address >> 2 @@ -1191,13 +1297,13 @@ union alignas(uint32_t) xe_gpu_memexport_stream_t { uint32_t const_0x4b000000; // +0 dword_1 - Endian128 endianness : 3; // +0 dword_2 - uint32_t unused_0 : 5; // +3 - ColorFormat format : 6; // +8 - uint32_t unused_1 : 2; // +14 - SurfaceNumFormat num_format : 3; // +16 - uint32_t red_blue_swap : 1; // +19 - uint32_t const_0x4b0 : 12; // +20 + Endian128 endianness : 3; // +0 dword_2 + uint32_t unused_0 : 5; // +3 + ColorFormat format : 6; // +8 + uint32_t unused_1 : 2; // +14 + SurfaceNumberFormat num_format : 3; // +16 + uint32_t red_blue_swap : 1; // +19 + uint32_t const_0x4b0 : 12; // +20 uint32_t index_count : 23; // +0 dword_3 uint32_t const_0x96 : 9; // +23 From 548758857f1b89521407c0b486b5e300cfd5478f Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Wed, 6 Oct 2021 14:38:06 -0700 Subject: [PATCH 14/17] Fix Xcode support in xenia-build --- xenia-build | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/xenia-build b/xenia-build index 7586582ab..ebcba477e 100755 --- a/xenia-build +++ b/xenia-build @@ -822,9 +822,16 @@ class BaseBuildCommand(Command): ] + ([targets] if targets is not None else []) + pass_args, shell=False) elif sys.platform == 'darwin': - # TODO(benvanik): other platforms. - print('ERROR: don\'t know how to build on this platform.') - result = 1 + schemes = args['target'] if len(args['target']) else ['xenia-app'] + nested_args = [['-scheme', scheme] for scheme in schemes] + scheme_args = [arg for pair in nested_args for arg in pair] + result = subprocess.call([ + 'xcodebuild', + '-workspace', + 'build/xenia.xcworkspace', + '-configuration', + args['config'] + ] + scheme_args + pass_args, shell=False, env=dict(os.environ)) else: result = subprocess.call([ 'make', @@ -1690,6 +1697,9 @@ class DevenvCommand(Command): print('ERROR: Visual Studio is not installed.'); return 1 print('Launching Visual Studio...') + elif sys.platform == 'darwin': + print('Launching Xcode...') + devenv = 'xcode4' elif has_bin('clion') or has_bin('clion.sh'): print('Launching CLion...') show_reload_prompt = create_clion_workspace() @@ -1711,6 +1721,11 @@ class DevenvCommand(Command): 'devenv', 'build\\xenia.sln', ]) + elif sys.platform == 'darwin': + shell_call([ + 'xed', + 'build/xenia.xcworkspace', + ]) elif has_bin('clion'): shell_call([ 'clion', From 2962a266b5857ef2b0bfc97064100db1b40aa0bd Mon Sep 17 00:00:00 2001 From: Conrad Kramer Date: Wed, 6 Oct 2021 14:55:58 -0700 Subject: [PATCH 15/17] Fix xenia-core build on macOS --- premake5.lua | 5 +++++ src/xenia/base/arena.cc | 2 +- src/xenia/base/atomic.h | 45 +++---------------------------------- src/xenia/base/clock_x64.cc | 2 +- src/xenia/base/logging.cc | 3 +-- src/xenia/base/utf8.cc | 2 -- third_party/FFmpeg | 2 +- third_party/SDL2.lua | 2 +- 8 files changed, 13 insertions(+), 50 deletions(-) diff --git a/premake5.lua b/premake5.lua index 55a0523a4..d55843c3e 100644 --- a/premake5.lua +++ b/premake5.lua @@ -211,6 +211,11 @@ workspace("xenia") architecture("x86_64") if os.istarget("linux") then platforms({"Linux"}) + elseif os.istarget("macosx") then + platforms({"Mac"}) + xcodebuildsettings({ + ["ARCHS"] = "x86_64" + }) elseif os.istarget("windows") then platforms({"Windows"}) -- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions. diff --git a/src/xenia/base/arena.cc b/src/xenia/base/arena.cc index 9b619cf56..66184f0f2 100644 --- a/src/xenia/base/arena.cc +++ b/src/xenia/base/arena.cc @@ -48,7 +48,7 @@ void Arena::DebugFill() { void* Arena::Alloc(size_t size, size_t align) { assert_true( - xe::bit_count(align) == 1 && align <= 16, + align > 0 && xe::is_pow2(align) && align <= 16, "align needs to be a power of 2 and not greater than Chunk alignment"); // for alignment diff --git a/src/xenia/base/atomic.h b/src/xenia/base/atomic.h index f1b8bb4b7..e34a6f1e6 100644 --- a/src/xenia/base/atomic.h +++ b/src/xenia/base/atomic.h @@ -16,46 +16,7 @@ namespace xe { -// These functions are modeled off of the Apple OSAtomic routines -// https://developer.apple.com/documentation/kernel/osatomic_h (?) -// Original link (dead): -// https://developer.apple.com/library/mac/#documentation/DriversKernelHardware/Reference/libkern_ref/OSAtomic_h/ - -#if XE_PLATFORM_MAC - -inline int32_t atomic_inc(volatile int32_t* value) { - return OSAtomicIncrement32Barrier(reinterpret_cast(value)); -} -inline int32_t atomic_dec(volatile int32_t* value) { - return OSAtomicDecrement32Barrier(reinterpret_cast(value)); -} - -inline int32_t atomic_exchange(int32_t new_value, volatile int32_t* value) { - return OSAtomicCompareAndSwap32Barrier(*value, new_value, value); -} -inline int64_t atomic_exchange(int64_t new_value, volatile int64_t* value) { - return OSAtomicCompareAndSwap64Barrier(*value, new_value, value); -} - -inline int32_t atomic_exchange_add(int32_t amount, volatile int32_t* value) { - return OSAtomicAdd32Barrier(amount, value) - amount; -} -inline int64_t atomic_exchange_add(int64_t amount, volatile int64_t* value) { - return OSAtomicAdd64Barrier(amount, value) - amount; -} - -inline bool atomic_cas(int32_t old_value, int32_t new_value, - volatile int32_t* value) { - return OSAtomicCompareAndSwap32Barrier( - old_value, new_value, reinterpret_cast(value)); -} -inline bool atomic_cas(int64_t old_value, int64_t new_value, - volatile int64_t* value) { - return OSAtomicCompareAndSwap64Barrier( - old_value, new_value, reinterpret_cast(value)); -} - -#elif XE_PLATFORM_WIN32 +#if XE_PLATFORM_WIN32 inline int32_t atomic_inc(volatile int32_t* value) { return _InterlockedIncrement(reinterpret_cast(value)); @@ -94,7 +55,7 @@ inline bool atomic_cas(int64_t old_value, int64_t new_value, old_value) == old_value; } -#elif XE_PLATFORM_LINUX +#elif XE_PLATFORM_LINUX || XE_PLATFORM_MAC inline int32_t atomic_inc(volatile int32_t* value) { return __sync_add_and_fetch(value, 1); @@ -132,7 +93,7 @@ inline bool atomic_cas(int64_t old_value, int64_t new_value, #error No atomic primitives defined for this platform/cpu combination. -#endif // OSX +#endif // XE_PLATFORM inline uint32_t atomic_inc(volatile uint32_t* value) { return static_cast( diff --git a/src/xenia/base/clock_x64.cc b/src/xenia/base/clock_x64.cc index e84d5baf2..14155303a 100644 --- a/src/xenia/base/clock_x64.cc +++ b/src/xenia/base/clock_x64.cc @@ -80,7 +80,7 @@ uint64_t Clock::host_tick_frequency_raw() { // For some CPUs, Crystal frequency is not reported. if (ratio_num && ratio_den && cryst_freq) { // If it is, calculate the TSC frequency - auto tsc_freq = cryst_freq * ratio_num / ratio_den; + return cryst_freq * ratio_num / ratio_den; } } diff --git a/src/xenia/base/logging.cc b/src/xenia/base/logging.cc index 1bf3870a0..ef04ec192 100644 --- a/src/xenia/base/logging.cc +++ b/src/xenia/base/logging.cc @@ -280,8 +280,7 @@ class Logger { // many blocks needed for at least one log line. auto next_range = dp::sequence_range(next_sequence, desired_count); - auto available_sequence = claim_strategy_.wait_until_published( - next_range.last(), last_sequence); + claim_strategy_.wait_until_published(next_range.last(), last_sequence); size_t read_count = 0; auto available_range = next_range; diff --git a/src/xenia/base/utf8.cc b/src/xenia/base/utf8.cc index 3f4775b7a..943e293b1 100644 --- a/src/xenia/base/utf8.cc +++ b/src/xenia/base/utf8.cc @@ -241,7 +241,6 @@ std::string_view::size_type find_any_of(const std::string_view haystack, auto [haystack_begin, haystack_end] = make_citer(haystack); auto [needle_begin, needle_end] = make_citer(needles); - auto needle_count = count(needles); auto it = find_needle(haystack_begin, haystack_end, needle_begin, needle_end); if (it == haystack_end) { @@ -261,7 +260,6 @@ std::string_view::size_type find_any_of_case(const std::string_view haystack, auto [haystack_begin, haystack_end] = make_citer(haystack); auto [needle_begin, needle_end] = make_citer(needles); - auto needle_count = count(needles); auto it = find_needle_case(haystack_begin, haystack_end, needle_begin, needle_end); diff --git a/third_party/FFmpeg b/third_party/FFmpeg index fb6c32ddf..e07c38c67 160000 --- a/third_party/FFmpeg +++ b/third_party/FFmpeg @@ -1 +1 @@ -Subproject commit fb6c32ddf75d818dce39e2f7d00401e9864a8d4d +Subproject commit e07c38c67578352e3f3e769cdac91650ea9575a9 diff --git a/third_party/SDL2.lua b/third_party/SDL2.lua index ab4c68d34..972aa1aa7 100644 --- a/third_party/SDL2.lua +++ b/third_party/SDL2.lua @@ -30,7 +30,7 @@ function sdl2_include() includedirs({ path.getrelative(".", third_party_path) .. "/SDL2/include", }) - filter("platforms:Linux") + filter("platforms:Linux or platforms:Mac") includedirs(sdl2_sys_includedirs) filter({}) end From 7e6cf349e21c2483ee9b12b8cb4543fa545c9c01 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sat, 30 Oct 2021 00:01:27 +0300 Subject: [PATCH 16/17] [Build] Use first-party premake-androidndk (#1878) --- .gitmodules | 6 +-- .../android_studio_project/app/build.gradle | 38 +++++++++++-------- android/android_studio_project/build.gradle | 6 +-- premake5.lua | 19 ++++++---- src/xenia/ui/premake5.lua | 4 ++ third_party/FFmpeg | 2 +- third_party/premake-androidmk | 1 - third_party/premake-androidndk | 1 + tools/build/scripts/build_paths.lua | 4 +- tools/build/scripts/platform_files.lua | 4 +- xenia-build | 2 +- 11 files changed, 52 insertions(+), 35 deletions(-) delete mode 160000 third_party/premake-androidmk create mode 160000 third_party/premake-androidndk diff --git a/.gitmodules b/.gitmodules index 630625cc5..5809e562b 100644 --- a/.gitmodules +++ b/.gitmodules @@ -64,9 +64,6 @@ [submodule "third_party/premake-cmake"] path = third_party/premake-cmake url = https://github.com/Enhex/premake-cmake.git -[submodule "third_party/premake-androidmk"] - path = third_party/premake-androidmk - url = https://github.com/Triang3l/premake-androidmk.git [submodule "third_party/date"] path = third_party/date url = https://github.com/HowardHinnant/date.git @@ -76,3 +73,6 @@ [submodule "third_party/FFmpeg"] path = third_party/FFmpeg url = https://github.com/xenia-project/FFmpeg.git +[submodule "third_party/premake-androidndk"] + path = third_party/premake-androidndk + url = https://github.com/Triang3l/premake-androidndk.git diff --git a/android/android_studio_project/app/build.gradle b/android/android_studio_project/app/build.gradle index d199beaa1..ff6acf7e2 100644 --- a/android/android_studio_project/app/build.gradle +++ b/android/android_studio_project/app/build.gradle @@ -4,23 +4,29 @@ plugins { android { compileSdkVersion 30 - buildToolsVersion "30.0.2" - ndkVersion '22.0.6917172 rc1' + buildToolsVersion '30.0.2' + ndkVersion '23.0.7599858' defaultConfig { - applicationId "jp.xenia.emulator" + applicationId 'jp.xenia.emulator' // 24 (7.0) - Vulkan. minSdkVersion 24 targetSdkVersion 30 versionCode 1 - versionName "Prototype" + versionName 'Prototype' externalNativeBuild { ndkBuild { - arguments "NDK_APPLICATION_MK:=../../../build/xenia_Application.mk" + arguments 'NDK_APPLICATION_MK:=../../../build/xenia.Application.mk', + 'PREMAKE_ANDROIDNDK_PLATFORMS:=Android-ARM64', + 'PREMAKE_ANDROIDNDK_PLATFORMS+=Android-x86_64', + // Work around "Bad file descriptor" on Windows on NDK r22+. + '--output-sync=none' } } ndk { - abiFilters 'arm64-v8a' + abiFilters 'arm64-v8a', 'x86_64' + jobs Runtime.runtime.availableProcessors() + stl 'c++_static' } } @@ -28,40 +34,40 @@ android { release { externalNativeBuild { ndkBuild { - arguments "PM5_CONFIG:=release_android" + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release' } } minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' } debug { - applicationIdSuffix ".debug" + applicationIdSuffix '.debug' debuggable true externalNativeBuild { ndkBuild { - arguments "PM5_CONFIG:=debug_android" + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug' } } } checked { - applicationIdSuffix ".checked" + applicationIdSuffix '.checked' debuggable true externalNativeBuild { ndkBuild { - arguments "PM5_CONFIG:=checked_android" + arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Checked' } } } } - flavorDimensions "distribution" + flavorDimensions 'distribution' productFlavors { github { - dimension "distribution" - applicationIdSuffix ".github" + dimension 'distribution' + applicationIdSuffix '.github' } googlePlay { - dimension "distribution" + dimension 'distribution' // TODO(Triang3l): Provide a signing config for core contributors only. } } @@ -73,7 +79,7 @@ android { externalNativeBuild { ndkBuild { - path file('../../../build/xenia_Android.mk') + path file('../../../build/xenia.wks.Android.mk') } } } \ No newline at end of file diff --git a/android/android_studio_project/build.gradle b/android/android_studio_project/build.gradle index 4d24be2c6..d51d77442 100644 --- a/android/android_studio_project/build.gradle +++ b/android/android_studio_project/build.gradle @@ -2,10 +2,10 @@ buildscript { repositories { google() - jcenter() + mavenCentral() } dependencies { - classpath "com.android.tools.build:gradle:4.1.1" + classpath 'com.android.tools.build:gradle:7.0.2' // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files @@ -15,7 +15,7 @@ buildscript { allprojects { repositories { google() - jcenter() + mavenCentral() } } diff --git a/premake5.lua b/premake5.lua index d55843c3e..f603b59af 100644 --- a/premake5.lua +++ b/premake5.lua @@ -1,9 +1,7 @@ include("tools/build") require("third_party/premake-export-compile-commands/export-compile-commands") +require("third_party/premake-androidndk/androidndk") require("third_party/premake-cmake/cmake") --- gmake required for androidmk. -require("gmake") -require("third_party/premake-androidmk/androidmk") location(build_root) targetdir(build_bin) @@ -138,11 +136,15 @@ filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp "-stdlib=libstdc++", }) -filter("platforms:Android") +filter("platforms:Android-*") system("android") + systemversion("24") + cppstl("c++") + staticruntime("On") links({ "android", "dl", + "log", }) filter("platforms:Windows") @@ -204,9 +206,12 @@ workspace("xenia") uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747") startproject("xenia-app") if os.istarget("android") then - -- Not setting architecture as that's handled by ndk-build itself. - platforms({"Android"}) - ndkstl("c++_static") + platforms({"Android-ARM64", "Android-x86_64"}) + filter("platforms:Android-ARM64") + architecture("ARM64") + filter("platforms:Android-x86_64") + architecture("x86_64") + filter({}) else architecture("x86_64") if os.istarget("linux") then diff --git a/src/xenia/ui/premake5.lua b/src/xenia/ui/premake5.lua index 93c012600..540c27154 100644 --- a/src/xenia/ui/premake5.lua +++ b/src/xenia/ui/premake5.lua @@ -14,3 +14,7 @@ project("xenia-ui") local_platform_files() removefiles({"*_demo.cc"}) removefiles({"windowed_app_main_*.cc"}) + + filter("platforms:Android-*") + -- Exports JNI functions. + wholelib("On") diff --git a/third_party/FFmpeg b/third_party/FFmpeg index e07c38c67..15ece0882 160000 --- a/third_party/FFmpeg +++ b/third_party/FFmpeg @@ -1 +1 @@ -Subproject commit e07c38c67578352e3f3e769cdac91650ea9575a9 +Subproject commit 15ece0882e8d5875051ff5b73c5a8326f7cee9f5 diff --git a/third_party/premake-androidmk b/third_party/premake-androidmk deleted file mode 160000 index 01a84c7ee..000000000 --- a/third_party/premake-androidmk +++ /dev/null @@ -1 +0,0 @@ -Subproject commit 01a84c7eee20980ea51961c956fb26caa6907298 diff --git a/third_party/premake-androidndk b/third_party/premake-androidndk new file mode 160000 index 000000000..e6132d3f7 --- /dev/null +++ b/third_party/premake-androidndk @@ -0,0 +1 @@ +Subproject commit e6132d3f7877f9ad361c634db35b708c41075e3a diff --git a/tools/build/scripts/build_paths.lua b/tools/build/scripts/build_paths.lua index e97bac0f4..5daa73a3f 100644 --- a/tools/build/scripts/build_paths.lua +++ b/tools/build/scripts/build_paths.lua @@ -7,7 +7,9 @@ build_tools = "tools/build" build_scripts = build_tools .. "/scripts" build_tools_src = build_tools .. "/src" -if os.istarget("windows") then +if os.istarget("android") then + platform_suffix = "android" +elseif os.istarget("windows") then platform_suffix = "win" else platform_suffix = "posix" diff --git a/tools/build/scripts/platform_files.lua b/tools/build/scripts/platform_files.lua index 5fffc8318..ec1579cf0 100644 --- a/tools/build/scripts/platform_files.lua +++ b/tools/build/scripts/platform_files.lua @@ -25,7 +25,7 @@ local function match_platform_files(base_path, base_match) base_path.."/"..base_match.."_win.h", base_path.."/"..base_match.."_win.cc", }) - filter("platforms:Linux or Android") + filter("platforms:Linux or Android-*") files({ base_path.."/"..base_match.."_posix.h", base_path.."/"..base_match.."_posix.cc", @@ -41,7 +41,7 @@ local function match_platform_files(base_path, base_match) base_path.."/"..base_match.."_gtk.h", base_path.."/"..base_match.."_gtk.cc", }) - filter("platforms:Android") + filter("platforms:Android-*") files({ base_path.."/"..base_match.."_android.h", base_path.."/"..base_match.."_android.cc", diff --git a/xenia-build b/xenia-build index ebcba477e..0104eac7d 100755 --- a/xenia-build +++ b/xenia-build @@ -514,7 +514,7 @@ def run_platform_premake(target_os_override=None, cc='clang', devenv=None): vs_version = os.environ['VSVERSION'] devenv = 'vs' + vs_version elif target_os == 'android': - devenv = 'androidmk' + devenv = 'androidndk' else: devenv = 'gmake2' if target_os != 'linux': From ddc3885795590325028233df15bdcb5d3ba950c6 Mon Sep 17 00:00:00 2001 From: Triang3l Date: Sun, 31 Oct 2021 16:04:46 +0300 Subject: [PATCH 17/17] [UI] Remove dtor lock as thread join will be done anyway --- src/xenia/ui/windowed_app_context_gtk.cc | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/src/xenia/ui/windowed_app_context_gtk.cc b/src/xenia/ui/windowed_app_context_gtk.cc index ea092929c..bd287c1eb 100644 --- a/src/xenia/ui/windowed_app_context_gtk.cc +++ b/src/xenia/ui/windowed_app_context_gtk.cc @@ -22,14 +22,8 @@ GTKWindowedAppContext::~GTKWindowedAppContext() { if (quit_idle_pending_) { g_source_remove(quit_idle_pending_); } - { - // Lock the mutex for a pending_functions_idle_pending_ access memory - // barrier, even though no other threads can access this object anymore. - std::lock_guard pending_functions_idle_pending_lock( - pending_functions_idle_pending_mutex_); - if (pending_functions_idle_pending_) { - g_source_remove(pending_functions_idle_pending_); - } + if (pending_functions_idle_pending_) { + g_source_remove(pending_functions_idle_pending_); } }