Merge branch 'master' into vulkan

This commit is contained in:
Triang3l 2021-10-31 16:27:37 +03:00
commit ce68a09b0c
40 changed files with 897 additions and 239 deletions

View File

@ -6,3 +6,8 @@ SortIncludes: true
# Regroup causes unnecessary noise due to clang-format bug.
IncludeBlocks: Preserve
---
Language: Java
DisableFormat: true
SortIncludes: false

1
.github/FUNDING.yml vendored
View File

@ -1 +1,2 @@
patreon: xenia_project
github: [gibbed, JoelLinn, Razzile]

6
.gitmodules vendored
View File

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

View File

@ -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')
}
}
}

View File

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

View File

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

View File

@ -0,0 +1,8 @@
package jp.xenia.emulator;
public class WindowDemoActivity extends WindowedAppActivity {
@Override
protected String getWindowedAppIdentifier() {
return "xenia_ui_window_vulkan_demo";
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -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;
}
}

View File

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

View File

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

View File

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

View File

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

View File

@ -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) {

View File

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

View File

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

View File

@ -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<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;
}

View File

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

View File

@ -52,7 +52,9 @@ KernelState::KernelState(Emulator* emulator)
user_profile_ = std::make_unique<xam::UserProfile>();
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<xam::ContentManager>(this, content_root);
assert_null(shared_kernel_state_);

View File

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

View File

@ -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")

View File

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

View File

@ -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) { \
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

View File

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

View File

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

View File

@ -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<std::mutex> 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_);
}
}

2
third_party/FFmpeg vendored

@ -1 +1 @@
Subproject commit 09eac851efa5886d82067c2cb3cc9fb789a85c7e
Subproject commit 15ece0882e8d5875051ff5b73c5a8326f7cee9f5

View File

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

1
third_party/premake-androidndk vendored Submodule

@ -0,0 +1 @@
Subproject commit e6132d3f7877f9ad361c634db35b708c41075e3a

View File

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

View File

@ -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",

View File

@ -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',