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/.github/FUNDING.yml b/.github/FUNDING.yml
index 3fc7e06c7..62bcd516c 100644
--- a/.github/FUNDING.yml
+++ b/.github/FUNDING.yml
@@ -1 +1,2 @@
patreon: xenia_project
+github: [gibbed, JoelLinn, Razzile]
diff --git a/.gitmodules b/.gitmodules
index 8e5e0dd34..280efab3a 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -55,9 +55,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
@@ -67,6 +64,9 @@
[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
[submodule "third_party/glslang"]
path = third_party/glslang
url = https://github.com/KhronosGroup/glslang.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/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 81%
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/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/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.
diff --git a/premake5.lua b/premake5.lua
index 3c2c9d49d..464502ed4 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,13 +206,21 @@ 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
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/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
}
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/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);
}
}
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/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/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;
}
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
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_);
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;
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/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 14eba9cb7..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::StartAppOnNativeActivityCreate( \
- 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 c1909d02e..5af4efad6 100644
--- a/src/xenia/ui/windowed_app_context_android.cc
+++ b/src/xenia/ui/windowed_app_context_android.cc
@@ -9,63 +9,358 @@
#include "xenia/ui/windowed_app_context_android.h"
+#include
#include
-#include
+#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(
- 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);
- // The pointer is now held by the Activity as its ANativeActivity::instance,
- // until the destruction.
- if (!app_context->InitializeApp(app_creator)) {
- delete app_context;
- ANativeActivity_finish(activity);
- }
-}
-
-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() {
- ANativeActivity_finish(activity_);
+ // All the shutdown will be done in onDestroy of the activity.
+ if (activity_ && activity_method_finish_) {
+ ui_thread_jni_env_->CallVoidMethod(activity_, activity_method_finish_);
+ }
}
-AndroidWindowedAppContext::AndroidWindowedAppContext(ANativeActivity* activity)
- : activity_(activity) {
- int32_t api_level;
+AndroidWindowedAppContext*
+AndroidWindowedAppContext::JniActivityInitializeWindowedAppOnCreate(
+ JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
+ jobject asset_manager) {
+ WindowedApp::Creator app_creator;
{
- AConfiguration* configuration = AConfiguration_new();
- AConfiguration_fromAssetManager(configuration, activity->assetManager);
- api_level = AConfiguration_getSdkVersion(configuration);
- AConfiguration_delete(configuration);
+ 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);
}
- xe::InitializeAndroidAppFromMainThread(api_level);
+ AndroidWindowedAppContext* app_context = new AndroidWindowedAppContext;
+ if (!app_context->Initialize(jni_env, activity, asset_manager)) {
+ delete app_context;
+ return nullptr;
+ }
- activity_->instance = this;
- // TODO(Triang3l): Register activity callbacks.
+ 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(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;
+ }
+ 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();
+ // 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;
+
+ 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 (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;
+ }
+
+ 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() {
+ // 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 (
@@ -82,3 +377,24 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr (
} // 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 cfdc16ed1..91cd10427 100644
--- a/src/xenia/ui/windowed_app_context_android.h
+++ b/src/xenia/ui/windowed_app_context_android.h
@@ -10,7 +10,11 @@
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
-#include
+#include
+#include
+#include
+#include
+#include
#include
#include "xenia/ui/windowed_app_context.h"
@@ -23,17 +27,6 @@ class WindowedApp;
class AndroidWindowedAppContext final : public WindowedAppContext {
public:
- // For calling from android.app.func_name exports.
- static void StartAppOnNativeActivityCreate(
- 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(); }
void NotifyUILoopOfPendingFunctions() override;
@@ -47,22 +40,76 @@ 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:
- 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 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(JNIEnv* ui_thread_jni_env, jobject activity,
+ jobject asset_manager);
+ 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));
- // 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_;
+ // 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
+ // threads in NotifyUILoopOfPendingFunctions.
+ std::array ui_thread_looper_callback_pipe_{-1, -1};
+ bool ui_thread_looper_callback_registered_ = false;
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
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_);
}
}
diff --git a/third_party/FFmpeg b/third_party/FFmpeg
index 09eac851e..15ece0882 160000
--- a/third_party/FFmpeg
+++ b/third_party/FFmpeg
@@ -1 +1 @@
-Subproject commit 09eac851efa5886d82067c2cb3cc9fb789a85c7e
+Subproject commit 15ece0882e8d5875051ff5b73c5a8326f7cee9f5
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
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 9dbb32c6d..945a545a5 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':
@@ -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',
@@ -1687,6 +1694,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()
@@ -1708,6 +1718,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',