Merge branch 'master' into vulkan
This commit is contained in:
commit
ce68a09b0c
|
@ -6,3 +6,8 @@ SortIncludes: true
|
|||
|
||||
# Regroup causes unnecessary noise due to clang-format bug.
|
||||
IncludeBlocks: Preserve
|
||||
|
||||
---
|
||||
Language: Java
|
||||
DisableFormat: true
|
||||
SortIncludes: false
|
||||
|
|
|
@ -1 +1,2 @@
|
|||
patreon: xenia_project
|
||||
github: [gibbed, JoelLinn, Razzile]
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,12 +2,23 @@
|
|||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="jp.xenia.emulator">
|
||||
|
||||
<uses-feature android:name="android.hardware.vulkan.level" android:version="0" android:required="true" />
|
||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x400000" android:required="true" />
|
||||
<!-- Granted automatically - guest sockets -->
|
||||
<uses-feature
|
||||
android:name="android.hardware.vulkan.level"
|
||||
android:required="true"
|
||||
android:version="0" />
|
||||
|
||||
<uses-feature
|
||||
android:name="android.hardware.vulkan.version"
|
||||
android:required="true"
|
||||
android:version="0x400000" />
|
||||
|
||||
<!-- Granted automatically - guest sockets. -->
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<!-- Needs to be requested - loading games from outside the app data directory -->
|
||||
<!-- WRITE_EXTERNAL_STORAGE is not required to write to the external app data directory since API 19 -->
|
||||
|
||||
<!--
|
||||
Needs to be requested - loading games from outside the app data directory.
|
||||
WRITE_EXTERNAL_STORAGE is not required to write to the external app data directory since API 19.
|
||||
-->
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||
|
||||
<application
|
||||
|
@ -17,12 +28,14 @@
|
|||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:theme="@android:style/Theme.Material.Light">
|
||||
<activity android:name="jp.xenia.emulator.DemoActivity">
|
||||
|
||||
<activity android:name="jp.xenia.emulator.WindowDemoActivity">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
</application>
|
||||
|
||||
</manifest>
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,8 @@
|
|||
package jp.xenia.emulator;
|
||||
|
||||
public class WindowDemoActivity extends WindowedAppActivity {
|
||||
@Override
|
||||
protected String getWindowedAppIdentifier() {
|
||||
return "xenia_ui_window_vulkan_demo";
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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">
|
||||
|
||||
</RelativeLayout>
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
24
premake5.lua
24
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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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<volatile int32_t*>(value));
|
||||
}
|
||||
inline int32_t atomic_dec(volatile int32_t* value) {
|
||||
return OSAtomicDecrement32Barrier(reinterpret_cast<volatile int32_t*>(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<volatile int32_t*>(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<volatile int64_t*>(value));
|
||||
}
|
||||
|
||||
#elif XE_PLATFORM_WIN32
|
||||
#if XE_PLATFORM_WIN32
|
||||
|
||||
inline int32_t atomic_inc(volatile int32_t* value) {
|
||||
return _InterlockedIncrement(reinterpret_cast<volatile long*>(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<uint32_t>(
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<reg::RB_COPY_DEST_INFO>();
|
||||
xenos::TextureFormat dest_format;
|
||||
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -716,7 +716,7 @@ union alignas(uint32_t) RB_COPY_DEST_INFO {
|
|||
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
|
||||
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
|
||||
|
|
|
@ -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<uint32_t, OwnershipRange>::iterator it_next;
|
||||
|
|
|
@ -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];
|
||||
};
|
||||
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<RenderTargetKey&>(
|
||||
const_cast<const OwnershipRange*>(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;
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
@ -1195,7 +1301,7 @@ union alignas(uint32_t) xe_gpu_memexport_stream_t {
|
|||
uint32_t unused_0 : 5; // +3
|
||||
ColorFormat format : 6; // +8
|
||||
uint32_t unused_1 : 2; // +14
|
||||
SurfaceNumFormat num_format : 3; // +16
|
||||
SurfaceNumberFormat num_format : 3; // +16
|
||||
uint32_t red_blue_swap : 1; // +19
|
||||
uint32_t const_0x4b0 : 12; // +20
|
||||
|
||||
|
|
|
@ -52,7 +52,9 @@ KernelState::KernelState(Emulator* emulator)
|
|||
user_profile_ = std::make_unique<xam::UserProfile>();
|
||||
|
||||
auto content_root = emulator_->content_root();
|
||||
if (!content_root.empty()) {
|
||||
content_root = std::filesystem::absolute(content_root);
|
||||
}
|
||||
content_manager_ = std::make_unique<xam::ContentManager>(this, content_root);
|
||||
|
||||
assert_null(shared_kernel_state_);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 <string>
|
||||
#include <unordered_map>
|
||||
|
||||
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<std::string, WindowedApp::Creator>* WindowedApp::creators_;
|
||||
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||
|
||||
} // namespace ui
|
||||
} // namespace xe
|
|
@ -13,15 +13,17 @@
|
|||
#include <cstddef>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "xenia/base/assert.h"
|
||||
#include "xenia/base/platform.h"
|
||||
#include "xenia/ui/windowed_app_context.h"
|
||||
|
||||
#if XE_PLATFORM_ANDROID
|
||||
#include <android/native_activity.h>
|
||||
|
||||
#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::WindowedApp> (*)(
|
||||
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<std::string> 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<std::string, WindowedApp::Creator>;
|
||||
}
|
||||
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<std::unordered_map<std::string, Creator>::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<std::string, Creator>* 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<WindowedApp> (*GetWindowedAppCreator())(
|
||||
WindowedAppContext& app_context);
|
||||
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
||||
std::unique_ptr<xe::ui::WindowedApp> (*xe::ui::GetWindowedAppCreator())( \
|
||||
xe::ui::WindowedAppContext & app_context) { \
|
||||
#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
|
||||
|
|
|
@ -9,63 +9,358 @@
|
|||
|
||||
#include "xenia/ui/windowed_app_context_android.h"
|
||||
|
||||
#include <android/asset_manager_jni.h>
|
||||
#include <android/configuration.h>
|
||||
#include <android/native_activity.h>
|
||||
#include <android/log.h>
|
||||
#include <android/looper.h>
|
||||
#include <fcntl.h>
|
||||
#include <jni.h>
|
||||
#include <unistd.h>
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
|
||||
#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<WindowedApp> (*app_creator)(
|
||||
WindowedAppContext& app_context)) {
|
||||
// TODO(Triang3l): Pass the launch options from the Intent or the saved
|
||||
// instance state.
|
||||
AndroidWindowedAppContext* app_context =
|
||||
new AndroidWindowedAppContext(activity);
|
||||
// The pointer is now held by the Activity as its ANativeActivity::instance,
|
||||
// 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<jclass>(ui_thread_jni_env_->NewGlobalRef(
|
||||
reinterpret_cast<jobject>(activity_class_local_ref)));
|
||||
ui_thread_jni_env_->DeleteLocalRef(
|
||||
reinterpret_cast<jobject>(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<jobject>(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<AndroidWindowedAppContext*>(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<WindowedApp> (
|
||||
|
@ -82,3 +377,24 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
|
|||
|
||||
} // 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<jlong>(
|
||||
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<xe::ui::AndroidWindowedAppContext*>(app_context_ptr)
|
||||
->JniActivityOnDestroy();
|
||||
}
|
||||
|
||||
} // extern "C"
|
||||
|
|
|
@ -10,7 +10,11 @@
|
|||
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||
#define XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||
|
||||
#include <android/native_activity.h>
|
||||
#include <android/asset_manager.h>
|
||||
#include <android/configuration.h>
|
||||
#include <android/looper.h>
|
||||
#include <jni.h>
|
||||
#include <array>
|
||||
#include <memory>
|
||||
|
||||
#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<WindowedApp> (*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<WindowedApp> (*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<WindowedApp> 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<int, 2> 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<WindowedApp> app_;
|
||||
};
|
||||
|
||||
} // namespace ui
|
||||
|
|
|
@ -22,15 +22,9 @@ 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<std::mutex> pending_functions_idle_pending_lock(
|
||||
pending_functions_idle_pending_mutex_);
|
||||
if (pending_functions_idle_pending_) {
|
||||
g_source_remove(pending_functions_idle_pending_);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GTKWindowedAppContext::NotifyUILoopOfPendingFunctions() {
|
||||
|
|
|
@ -1 +1 @@
|
|||
Subproject commit 09eac851efa5886d82067c2cb3cc9fb789a85c7e
|
||||
Subproject commit 15ece0882e8d5875051ff5b73c5a8326f7cee9f5
|
|
@ -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
|
||||
|
|
|
@ -1 +0,0 @@
|
|||
Subproject commit 01a84c7eee20980ea51961c956fb26caa6907298
|
|
@ -0,0 +1 @@
|
|||
Subproject commit e6132d3f7877f9ad361c634db35b708c41075e3a
|
|
@ -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"
|
||||
|
|
|
@ -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",
|
||||
|
|
23
xenia-build
23
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',
|
||||
|
|
Loading…
Reference in New Issue