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.
|
# Regroup causes unnecessary noise due to clang-format bug.
|
||||||
IncludeBlocks: Preserve
|
IncludeBlocks: Preserve
|
||||||
|
|
||||||
|
---
|
||||||
|
Language: Java
|
||||||
|
DisableFormat: true
|
||||||
|
SortIncludes: false
|
||||||
|
|
|
@ -1 +1,2 @@
|
||||||
patreon: xenia_project
|
patreon: xenia_project
|
||||||
|
github: [gibbed, JoelLinn, Razzile]
|
||||||
|
|
|
@ -55,9 +55,6 @@
|
||||||
[submodule "third_party/premake-cmake"]
|
[submodule "third_party/premake-cmake"]
|
||||||
path = third_party/premake-cmake
|
path = third_party/premake-cmake
|
||||||
url = https://github.com/Enhex/premake-cmake.git
|
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"]
|
[submodule "third_party/date"]
|
||||||
path = third_party/date
|
path = third_party/date
|
||||||
url = https://github.com/HowardHinnant/date.git
|
url = https://github.com/HowardHinnant/date.git
|
||||||
|
@ -67,6 +64,9 @@
|
||||||
[submodule "third_party/FFmpeg"]
|
[submodule "third_party/FFmpeg"]
|
||||||
path = third_party/FFmpeg
|
path = third_party/FFmpeg
|
||||||
url = https://github.com/xenia-project/FFmpeg.git
|
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"]
|
[submodule "third_party/glslang"]
|
||||||
path = third_party/glslang
|
path = third_party/glslang
|
||||||
url = https://github.com/KhronosGroup/glslang.git
|
url = https://github.com/KhronosGroup/glslang.git
|
||||||
|
|
|
@ -4,23 +4,29 @@ plugins {
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion 30
|
compileSdkVersion 30
|
||||||
buildToolsVersion "30.0.2"
|
buildToolsVersion '30.0.2'
|
||||||
ndkVersion '22.0.6917172 rc1'
|
ndkVersion '23.0.7599858'
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
applicationId "jp.xenia.emulator"
|
applicationId 'jp.xenia.emulator'
|
||||||
// 24 (7.0) - Vulkan.
|
// 24 (7.0) - Vulkan.
|
||||||
minSdkVersion 24
|
minSdkVersion 24
|
||||||
targetSdkVersion 30
|
targetSdkVersion 30
|
||||||
versionCode 1
|
versionCode 1
|
||||||
versionName "Prototype"
|
versionName 'Prototype'
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
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 {
|
ndk {
|
||||||
abiFilters 'arm64-v8a'
|
abiFilters 'arm64-v8a', 'x86_64'
|
||||||
|
jobs Runtime.runtime.availableProcessors()
|
||||||
|
stl 'c++_static'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,40 +34,40 @@ android {
|
||||||
release {
|
release {
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
arguments "PM5_CONFIG:=release_android"
|
arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Release'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
minifyEnabled true
|
minifyEnabled true
|
||||||
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
|
||||||
}
|
}
|
||||||
debug {
|
debug {
|
||||||
applicationIdSuffix ".debug"
|
applicationIdSuffix '.debug'
|
||||||
debuggable true
|
debuggable true
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
arguments "PM5_CONFIG:=debug_android"
|
arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Debug'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
checked {
|
checked {
|
||||||
applicationIdSuffix ".checked"
|
applicationIdSuffix '.checked'
|
||||||
debuggable true
|
debuggable true
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
ndkBuild {
|
||||||
arguments "PM5_CONFIG:=checked_android"
|
arguments 'PREMAKE_ANDROIDNDK_CONFIGURATIONS:=Checked'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions "distribution"
|
flavorDimensions 'distribution'
|
||||||
productFlavors {
|
productFlavors {
|
||||||
github {
|
github {
|
||||||
dimension "distribution"
|
dimension 'distribution'
|
||||||
applicationIdSuffix ".github"
|
applicationIdSuffix '.github'
|
||||||
}
|
}
|
||||||
googlePlay {
|
googlePlay {
|
||||||
dimension "distribution"
|
dimension 'distribution'
|
||||||
// TODO(Triang3l): Provide a signing config for core contributors only.
|
// TODO(Triang3l): Provide a signing config for core contributors only.
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -73,7 +79,7 @@ android {
|
||||||
|
|
||||||
externalNativeBuild {
|
externalNativeBuild {
|
||||||
ndkBuild {
|
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"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="jp.xenia.emulator">
|
package="jp.xenia.emulator">
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.vulkan.level" android:version="0" android:required="true" />
|
<uses-feature
|
||||||
<uses-feature android:name="android.hardware.vulkan.version" android:version="0x400000" android:required="true" />
|
android:name="android.hardware.vulkan.level"
|
||||||
<!-- Granted automatically - guest sockets -->
|
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" />
|
<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" />
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
@ -17,12 +28,14 @@
|
||||||
android:roundIcon="@mipmap/ic_launcher_round"
|
android:roundIcon="@mipmap/ic_launcher_round"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
android:theme="@android:style/Theme.Material.Light">
|
android:theme="@android:style/Theme.Material.Light">
|
||||||
<activity android:name="jp.xenia.emulator.DemoActivity">
|
|
||||||
|
<activity android:name="jp.xenia.emulator.WindowDemoActivity">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.MAIN" />
|
<action android:name="android.intent.action.MAIN" />
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
<category android:name="android.intent.category.LAUNCHER" />
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
|
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</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"
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
tools:context="jp.xenia.emulator.DemoActivity">
|
tools:context="jp.xenia.emulator.WindowDemoActivity">
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
|
@ -2,10 +2,10 @@
|
||||||
buildscript {
|
buildscript {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
google()
|
||||||
jcenter()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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
|
// NOTE: Do not place your application dependencies here; they belong
|
||||||
// in the individual module build.gradle files
|
// in the individual module build.gradle files
|
||||||
|
@ -15,7 +15,7 @@ buildscript {
|
||||||
allprojects {
|
allprojects {
|
||||||
repositories {
|
repositories {
|
||||||
google()
|
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'.
|
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)
|
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
|
`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
|
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
|
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
|
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
|
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.
|
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.
|
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")
|
include("tools/build")
|
||||||
require("third_party/premake-export-compile-commands/export-compile-commands")
|
require("third_party/premake-export-compile-commands/export-compile-commands")
|
||||||
|
require("third_party/premake-androidndk/androidndk")
|
||||||
require("third_party/premake-cmake/cmake")
|
require("third_party/premake-cmake/cmake")
|
||||||
-- gmake required for androidmk.
|
|
||||||
require("gmake")
|
|
||||||
require("third_party/premake-androidmk/androidmk")
|
|
||||||
|
|
||||||
location(build_root)
|
location(build_root)
|
||||||
targetdir(build_bin)
|
targetdir(build_bin)
|
||||||
|
@ -138,11 +136,15 @@ filter({"platforms:Linux", "language:C++", "toolset:clang", "files:*.cc or *.cpp
|
||||||
"-stdlib=libstdc++",
|
"-stdlib=libstdc++",
|
||||||
})
|
})
|
||||||
|
|
||||||
filter("platforms:Android")
|
filter("platforms:Android-*")
|
||||||
system("android")
|
system("android")
|
||||||
|
systemversion("24")
|
||||||
|
cppstl("c++")
|
||||||
|
staticruntime("On")
|
||||||
links({
|
links({
|
||||||
"android",
|
"android",
|
||||||
"dl",
|
"dl",
|
||||||
|
"log",
|
||||||
})
|
})
|
||||||
|
|
||||||
filter("platforms:Windows")
|
filter("platforms:Windows")
|
||||||
|
@ -204,13 +206,21 @@ workspace("xenia")
|
||||||
uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747")
|
uuid("931ef4b0-6170-4f7a-aaf2-0fece7632747")
|
||||||
startproject("xenia-app")
|
startproject("xenia-app")
|
||||||
if os.istarget("android") then
|
if os.istarget("android") then
|
||||||
-- Not setting architecture as that's handled by ndk-build itself.
|
platforms({"Android-ARM64", "Android-x86_64"})
|
||||||
platforms({"Android"})
|
filter("platforms:Android-ARM64")
|
||||||
ndkstl("c++_static")
|
architecture("ARM64")
|
||||||
|
filter("platforms:Android-x86_64")
|
||||||
|
architecture("x86_64")
|
||||||
|
filter({})
|
||||||
else
|
else
|
||||||
architecture("x86_64")
|
architecture("x86_64")
|
||||||
if os.istarget("linux") then
|
if os.istarget("linux") then
|
||||||
platforms({"Linux"})
|
platforms({"Linux"})
|
||||||
|
elseif os.istarget("macosx") then
|
||||||
|
platforms({"Mac"})
|
||||||
|
xcodebuildsettings({
|
||||||
|
["ARCHS"] = "x86_64"
|
||||||
|
})
|
||||||
elseif os.istarget("windows") then
|
elseif os.istarget("windows") then
|
||||||
platforms({"Windows"})
|
platforms({"Windows"})
|
||||||
-- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions.
|
-- 10.0.15063.0: ID3D12GraphicsCommandList1::SetSamplePositions.
|
||||||
|
|
|
@ -429,7 +429,7 @@ void EmulatorWindow::ShowCommitID() {
|
||||||
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
|
"https://github.com/xenia-project/xenia/pull/" XE_BUILD_PR_NUMBER);
|
||||||
#else
|
#else
|
||||||
LaunchWebBrowser(
|
LaunchWebBrowser(
|
||||||
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT "/");
|
"https://github.com/xenia-project/xenia/commit/" XE_BUILD_COMMIT);
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -48,7 +48,7 @@ void Arena::DebugFill() {
|
||||||
|
|
||||||
void* Arena::Alloc(size_t size, size_t align) {
|
void* Arena::Alloc(size_t size, size_t align) {
|
||||||
assert_true(
|
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");
|
"align needs to be a power of 2 and not greater than Chunk alignment");
|
||||||
|
|
||||||
// for alignment
|
// for alignment
|
||||||
|
|
|
@ -16,46 +16,7 @@
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
|
||||||
// These functions are modeled off of the Apple OSAtomic routines
|
#if XE_PLATFORM_WIN32
|
||||||
// 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
|
|
||||||
|
|
||||||
inline int32_t atomic_inc(volatile int32_t* value) {
|
inline int32_t atomic_inc(volatile int32_t* value) {
|
||||||
return _InterlockedIncrement(reinterpret_cast<volatile long*>(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;
|
old_value) == old_value;
|
||||||
}
|
}
|
||||||
|
|
||||||
#elif XE_PLATFORM_LINUX
|
#elif XE_PLATFORM_LINUX || XE_PLATFORM_MAC
|
||||||
|
|
||||||
inline int32_t atomic_inc(volatile int32_t* value) {
|
inline int32_t atomic_inc(volatile int32_t* value) {
|
||||||
return __sync_add_and_fetch(value, 1);
|
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.
|
#error No atomic primitives defined for this platform/cpu combination.
|
||||||
|
|
||||||
#endif // OSX
|
#endif // XE_PLATFORM
|
||||||
|
|
||||||
inline uint32_t atomic_inc(volatile uint32_t* value) {
|
inline uint32_t atomic_inc(volatile uint32_t* value) {
|
||||||
return static_cast<uint32_t>(
|
return static_cast<uint32_t>(
|
||||||
|
|
|
@ -80,7 +80,7 @@ uint64_t Clock::host_tick_frequency_raw() {
|
||||||
// For some CPUs, Crystal frequency is not reported.
|
// For some CPUs, Crystal frequency is not reported.
|
||||||
if (ratio_num && ratio_den && cryst_freq) {
|
if (ratio_num && ratio_den && cryst_freq) {
|
||||||
// If it is, calculate the TSC frequency
|
// 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.
|
// many blocks needed for at least one log line.
|
||||||
auto next_range = dp::sequence_range(next_sequence, desired_count);
|
auto next_range = dp::sequence_range(next_sequence, desired_count);
|
||||||
|
|
||||||
auto available_sequence = claim_strategy_.wait_until_published(
|
claim_strategy_.wait_until_published(next_range.last(), last_sequence);
|
||||||
next_range.last(), last_sequence);
|
|
||||||
|
|
||||||
size_t read_count = 0;
|
size_t read_count = 0;
|
||||||
auto available_range = next_range;
|
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
|
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=100801
|
||||||
// TODO(Joel Linn): Remove this when fixed GCC versions are common place.
|
// TODO(Joel Linn): Remove this when fixed GCC versions are common place.
|
||||||
#if XE_COMPILER_GNUC
|
#if XE_COMPILER_GNUC
|
||||||
#define XE_WORKAROUND_LOOP_KILL_MOD(x) \
|
#define XE_WORKAROUND_CONSTANT_RETURN_IF(x) \
|
||||||
if ((count % (x)) == 0) __builtin_unreachable();
|
if (__builtin_constant_p(x) && (x)) return;
|
||||||
#else
|
#else
|
||||||
#define XE_WORKAROUND_LOOP_KILL_MOD(x)
|
#define XE_WORKAROUND_CONSTANT_RETURN_IF(x)
|
||||||
#endif
|
#endif
|
||||||
void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
|
void copy_and_swap_16_aligned(void* dest_ptr, const void* src_ptr,
|
||||||
size_t count) {
|
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);
|
__m128i output = _mm_shuffle_epi8(input, shufmask);
|
||||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 8 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(8);
|
|
||||||
dest[i] = byte_swap(src[i]);
|
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);
|
__m128i output = _mm_shuffle_epi8(input, shufmask);
|
||||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 8 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(8);
|
|
||||||
dest[i] = byte_swap(src[i]);
|
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);
|
__m128i output = _mm_shuffle_epi8(input, shufmask);
|
||||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
|
||||||
dest[i] = byte_swap(src[i]);
|
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);
|
__m128i output = _mm_shuffle_epi8(input, shufmask);
|
||||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
|
||||||
dest[i] = byte_swap(src[i]);
|
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);
|
__m128i output = _mm_shuffle_epi8(input, shufmask);
|
||||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 2 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(2);
|
|
||||||
dest[i] = byte_swap(src[i]);
|
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);
|
__m128i output = _mm_shuffle_epi8(input, shufmask);
|
||||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 2 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(2);
|
|
||||||
dest[i] = byte_swap(src[i]);
|
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_or_si128(_mm_slli_epi32(input, 16), _mm_srli_epi32(input, 16));
|
||||||
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_store_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
|
||||||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
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_or_si128(_mm_slli_epi32(input, 16), _mm_srli_epi32(input, 16));
|
||||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
_mm_storeu_si128(reinterpret_cast<__m128i*>(&dest[i]), output);
|
||||||
}
|
}
|
||||||
|
XE_WORKAROUND_CONSTANT_RETURN_IF(count % 4 == 0);
|
||||||
for (; i < count; ++i) { // handle residual elements
|
for (; i < count; ++i) { // handle residual elements
|
||||||
XE_WORKAROUND_LOOP_KILL_MOD(4);
|
|
||||||
dest[i] = (src[i] >> 16) | (src[i] << 16);
|
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 [haystack_begin, haystack_end] = make_citer(haystack);
|
||||||
auto [needle_begin, needle_end] = make_citer(needles);
|
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);
|
auto it = find_needle(haystack_begin, haystack_end, needle_begin, needle_end);
|
||||||
if (it == haystack_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 [haystack_begin, haystack_end] = make_citer(haystack);
|
||||||
auto [needle_begin, needle_end] = make_citer(needles);
|
auto [needle_begin, needle_end] = make_citer(needles);
|
||||||
auto needle_count = count(needles);
|
|
||||||
|
|
||||||
auto it =
|
auto it =
|
||||||
find_needle_case(haystack_begin, haystack_end, needle_begin, needle_end);
|
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;
|
info_out.address.copy_sample_select = sample_select;
|
||||||
// Get the format to pass to the shader in a unified way - for depth (for
|
// 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
|
// which Direct3D 9 specifies the k_8_8_8_8 uint destination format), make
|
||||||
// shader won't try to do conversion - pass proper k_24_8 or k_24_8_FLOAT.
|
// 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>();
|
auto rb_copy_dest_info = regs.Get<reg::RB_COPY_DEST_INFO>();
|
||||||
xenos::TextureFormat dest_format;
|
xenos::TextureFormat dest_format;
|
||||||
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
auto rb_depth_info = regs.Get<reg::RB_DEPTH_INFO>();
|
||||||
|
|
|
@ -15,6 +15,14 @@ namespace xe {
|
||||||
namespace gpu {
|
namespace gpu {
|
||||||
using namespace ucode;
|
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(
|
void DxbcShaderTranslator::ExportToMemory_PackFixed32(
|
||||||
const uint32_t* eM_temps, uint32_t eM_count, const uint32_t bits[4],
|
const uint32_t* eM_temps, uint32_t eM_count, const uint32_t bits[4],
|
||||||
const dxbc::Src& is_integer, const dxbc::Src& is_signed) {
|
const dxbc::Src& is_integer, const dxbc::Src& is_signed) {
|
||||||
|
|
|
@ -712,14 +712,14 @@ static_assert_size(RB_COPY_CONTROL, sizeof(uint32_t));
|
||||||
|
|
||||||
union alignas(uint32_t) RB_COPY_DEST_INFO {
|
union alignas(uint32_t) RB_COPY_DEST_INFO {
|
||||||
struct {
|
struct {
|
||||||
xenos::Endian128 copy_dest_endian : 3; // +0
|
xenos::Endian128 copy_dest_endian : 3; // +0
|
||||||
uint32_t copy_dest_array : 1; // +3
|
uint32_t copy_dest_array : 1; // +3
|
||||||
uint32_t copy_dest_slice : 3; // +4
|
uint32_t copy_dest_slice : 3; // +4
|
||||||
xenos::ColorFormat copy_dest_format : 6; // +7
|
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
|
int32_t copy_dest_exp_bias : 6; // +16
|
||||||
uint32_t : 2; // +22
|
uint32_t : 2; // +22
|
||||||
uint32_t copy_dest_swap : 1; // +24
|
uint32_t copy_dest_swap : 1; // +24
|
||||||
};
|
};
|
||||||
uint32_t value;
|
uint32_t value;
|
||||||
static constexpr Register register_index = XE_GPU_REG_RB_COPY_DEST_INFO;
|
static constexpr Register register_index = XE_GPU_REG_RB_COPY_DEST_INFO;
|
||||||
|
|
|
@ -1342,7 +1342,7 @@ void RenderTargetCache::ChangeOwnership(
|
||||||
nullptr, resolve_clear_cutout)) {
|
nullptr, resolve_clear_cutout)) {
|
||||||
RenderTargetKey transfer_host_depth_source =
|
RenderTargetKey transfer_host_depth_source =
|
||||||
host_depth_encoding_different
|
host_depth_encoding_different
|
||||||
? it->second.host_depth_render_targets[dest.resource_format]
|
? it->second.GetHostDepthRenderTarget(dest.GetDepthFormat())
|
||||||
: RenderTargetKey();
|
: RenderTargetKey();
|
||||||
if (transfer_host_depth_source == transfer_source) {
|
if (transfer_host_depth_source == transfer_source) {
|
||||||
// Same render target, don't provide a separate host depth source.
|
// Same render target, don't provide a separate host depth source.
|
||||||
|
@ -1387,7 +1387,7 @@ void RenderTargetCache::ChangeOwnership(
|
||||||
// Claim the current range.
|
// Claim the current range.
|
||||||
it->second.render_target = dest;
|
it->second.render_target = dest;
|
||||||
if (host_depth_encoding_different) {
|
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.
|
// Check if can merge with the next range after claiming.
|
||||||
std::map<uint32_t, OwnershipRange>::iterator it_next;
|
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
|
// 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
|
// range hasn't been used yet (render_target.IsEmpty() == true), these are
|
||||||
// empty too.
|
// empty too.
|
||||||
union {
|
RenderTargetKey host_depth_render_target_unorm24;
|
||||||
struct {
|
RenderTargetKey host_depth_render_target_float24;
|
||||||
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,
|
OwnershipRange(uint32_t end_tiles, RenderTargetKey render_target,
|
||||||
RenderTargetKey host_depth_render_target_unorm24,
|
RenderTargetKey host_depth_render_target_unorm24,
|
||||||
RenderTargetKey host_depth_render_target_float24)
|
RenderTargetKey host_depth_render_target_float24)
|
||||||
|
@ -552,6 +547,22 @@ class RenderTargetCache {
|
||||||
render_target(render_target),
|
render_target(render_target),
|
||||||
host_depth_render_target_unorm24(host_depth_render_target_unorm24),
|
host_depth_render_target_unorm24(host_depth_render_target_unorm24),
|
||||||
host_depth_render_target_float24(host_depth_render_target_float24) {}
|
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 IsOwnedBy(RenderTargetKey key,
|
||||||
bool host_depth_encoding_different) const {
|
bool host_depth_encoding_different) const {
|
||||||
if (render_target != key) {
|
if (render_target != key) {
|
||||||
|
@ -561,7 +572,7 @@ class RenderTargetCache {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (host_depth_encoding_different && !key.is_depth &&
|
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.
|
// Depth encoding is the same, but different addressing is needed.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -185,7 +185,7 @@ enum class IndexFormat : uint32_t {
|
||||||
};
|
};
|
||||||
|
|
||||||
// SurfaceNumberX from yamato_enum.h.
|
// SurfaceNumberX from yamato_enum.h.
|
||||||
enum class SurfaceNumFormat : uint32_t {
|
enum class SurfaceNumberFormat : uint32_t {
|
||||||
kUnsignedRepeatingFraction = 0,
|
kUnsignedRepeatingFraction = 0,
|
||||||
// Microsoft-style, scale factor (2^(n-1))-1.
|
// Microsoft-style, scale factor (2^(n-1))-1.
|
||||||
kSignedRepeatingFraction = 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);
|
static_assert_size(xe_gpu_fetch_group_t, sizeof(uint32_t) * 6);
|
||||||
|
|
||||||
// GPU_MEMEXPORT_STREAM_CONSTANT from a game .pdb - float constant for memexport
|
// Shader memory export (memexport) allows for writing of arbitrary formatted
|
||||||
// stream configuration.
|
// data with random access / scatter capabilities. It provides functionality
|
||||||
// This is used with the floating-point ALU in shaders (written to eA using
|
// largely similar to resolving - format packing, supporting arbitrary color
|
||||||
// mad), so the dwords have a normalized exponent when reinterpreted as floats
|
// formats, from sub-dword ones such as k_8 in 58410B86, to 128-bit ones, with
|
||||||
// (otherwise they would be flushed to zero), but actually these are packed
|
// endian swap similar to how it's performed in resolves (up to 128-bit);
|
||||||
// integers. dword_1 specifically is 2^23 because
|
// specifying the number format, swapping red and blue channels - though with no
|
||||||
// powf(2.0f, 23.0f) + float(i) == 0x4B000000 | i
|
// exponent biasing. Unlike resolving, however, instead of writing to tiled
|
||||||
// so mad can pack indices as integers in the lower bits.
|
// 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 {
|
union alignas(uint32_t) xe_gpu_memexport_stream_t {
|
||||||
struct {
|
struct {
|
||||||
uint32_t base_address : 30; // +0 dword_0 physical address >> 2
|
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
|
uint32_t const_0x4b000000; // +0 dword_1
|
||||||
|
|
||||||
Endian128 endianness : 3; // +0 dword_2
|
Endian128 endianness : 3; // +0 dword_2
|
||||||
uint32_t unused_0 : 5; // +3
|
uint32_t unused_0 : 5; // +3
|
||||||
ColorFormat format : 6; // +8
|
ColorFormat format : 6; // +8
|
||||||
uint32_t unused_1 : 2; // +14
|
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 red_blue_swap : 1; // +19
|
||||||
uint32_t const_0x4b0 : 12; // +20
|
uint32_t const_0x4b0 : 12; // +20
|
||||||
|
|
||||||
uint32_t index_count : 23; // +0 dword_3
|
uint32_t index_count : 23; // +0 dword_3
|
||||||
uint32_t const_0x96 : 9; // +23
|
uint32_t const_0x96 : 9; // +23
|
||||||
|
|
|
@ -52,7 +52,9 @@ KernelState::KernelState(Emulator* emulator)
|
||||||
user_profile_ = std::make_unique<xam::UserProfile>();
|
user_profile_ = std::make_unique<xam::UserProfile>();
|
||||||
|
|
||||||
auto content_root = emulator_->content_root();
|
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);
|
content_manager_ = std::make_unique<xam::ContentManager>(this, content_root);
|
||||||
|
|
||||||
assert_null(shared_kernel_state_);
|
assert_null(shared_kernel_state_);
|
||||||
|
|
|
@ -290,6 +290,13 @@ int32_t format_core(PPCContext* ppc_context, FormatData& data, ArgList& args,
|
||||||
}
|
}
|
||||||
state = FS_Type;
|
state = FS_Type;
|
||||||
continue;
|
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') {
|
} else if (c == 'h') {
|
||||||
flags |= FF_IsShort;
|
flags |= FF_IsShort;
|
||||||
state = FS_Type;
|
state = FS_Type;
|
||||||
|
|
|
@ -14,3 +14,7 @@ project("xenia-ui")
|
||||||
local_platform_files()
|
local_platform_files()
|
||||||
removefiles({"*_demo.cc"})
|
removefiles({"*_demo.cc"})
|
||||||
removefiles({"windowed_app_main_*.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 <cstddef>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <unordered_map>
|
||||||
|
#include <utility>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
|
|
||||||
|
#include "xenia/base/assert.h"
|
||||||
#include "xenia/base/platform.h"
|
#include "xenia/base/platform.h"
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
|
||||||
#if XE_PLATFORM_ANDROID
|
#if XE_PLATFORM_ANDROID
|
||||||
#include <android/native_activity.h>
|
// Multiple apps in a single library instead of separate executables.
|
||||||
|
#define XE_UI_WINDOWED_APPS_IN_LIBRARY 1
|
||||||
#include "xenia/ui/windowed_app_context_android.h"
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
|
@ -36,6 +38,9 @@ class WindowedApp {
|
||||||
// initialization of platform-specific parts, should preferably be as simple
|
// initialization of platform-specific parts, should preferably be as simple
|
||||||
// as possible).
|
// as possible).
|
||||||
|
|
||||||
|
using Creator = std::unique_ptr<xe::ui::WindowedApp> (*)(
|
||||||
|
xe::ui::WindowedAppContext& app_context);
|
||||||
|
|
||||||
WindowedApp(const WindowedApp& app) = delete;
|
WindowedApp(const WindowedApp& app) = delete;
|
||||||
WindowedApp& operator=(const WindowedApp& app) = delete;
|
WindowedApp& operator=(const WindowedApp& app) = delete;
|
||||||
virtual ~WindowedApp() = default;
|
virtual ~WindowedApp() = default;
|
||||||
|
@ -101,27 +106,67 @@ class WindowedApp {
|
||||||
std::string name_;
|
std::string name_;
|
||||||
std::string positional_options_usage_;
|
std::string positional_options_usage_;
|
||||||
std::vector<std::string> positional_options_;
|
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
|
#if XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
// Multiple apps in a single library. ANativeActivity_onCreate chosen via
|
// Multiple apps in a single library.
|
||||||
// android.app.func_name of the NativeActivity of each app.
|
#define XE_DEFINE_WINDOWED_APP(identifier, creator) \
|
||||||
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
namespace xe { \
|
||||||
__attribute__((visibility("default"))) extern "C" void export_name( \
|
namespace ui { \
|
||||||
ANativeActivity* activity, void* saved_state, size_t saved_state_size) { \
|
namespace windowed_app_creator_registrations { \
|
||||||
xe::ui::AndroidWindowedAppContext::StartAppOnNativeActivityCreate( \
|
xe::ui::WindowedApp::CreatorRegistration identifier(#identifier, creator); \
|
||||||
activity, saved_state, saved_state_size, creator); \
|
} \
|
||||||
|
} \
|
||||||
}
|
}
|
||||||
#else
|
#else
|
||||||
// Separate executables for each app.
|
// Separate executables for each app.
|
||||||
std::unique_ptr<WindowedApp> (*GetWindowedAppCreator())(
|
std::unique_ptr<WindowedApp> (*GetWindowedAppCreator())(
|
||||||
WindowedAppContext& app_context);
|
WindowedAppContext& app_context);
|
||||||
#define XE_DEFINE_WINDOWED_APP(export_name, creator) \
|
#define XE_DEFINE_WINDOWED_APP(identifier, creator) \
|
||||||
std::unique_ptr<xe::ui::WindowedApp> (*xe::ui::GetWindowedAppCreator())( \
|
xe::ui::WindowedApp::Creator xe::ui::GetWindowedAppCreator() { \
|
||||||
xe::ui::WindowedAppContext & app_context) { \
|
return creator; \
|
||||||
return creator; \
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif // XE_UI_WINDOWED_APPS_IN_LIBRARY
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // namespace xe
|
||||||
|
|
|
@ -9,63 +9,358 @@
|
||||||
|
|
||||||
#include "xenia/ui/windowed_app_context_android.h"
|
#include "xenia/ui/windowed_app_context_android.h"
|
||||||
|
|
||||||
|
#include <android/asset_manager_jni.h>
|
||||||
#include <android/configuration.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 <cstdint>
|
||||||
|
|
||||||
#include "xenia/base/assert.h"
|
#include "xenia/base/assert.h"
|
||||||
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/main_android.h"
|
#include "xenia/base/main_android.h"
|
||||||
#include "xenia/ui/windowed_app.h"
|
#include "xenia/ui/windowed_app.h"
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace ui {
|
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() {
|
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() {
|
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)
|
AndroidWindowedAppContext*
|
||||||
: activity_(activity) {
|
AndroidWindowedAppContext::JniActivityInitializeWindowedAppOnCreate(
|
||||||
int32_t api_level;
|
JNIEnv* jni_env, jobject activity, jstring windowed_app_identifier,
|
||||||
|
jobject asset_manager) {
|
||||||
|
WindowedApp::Creator app_creator;
|
||||||
{
|
{
|
||||||
AConfiguration* configuration = AConfiguration_new();
|
const char* windowed_app_identifier_c_str =
|
||||||
AConfiguration_fromAssetManager(configuration, activity->assetManager);
|
jni_env->GetStringUTFChars(windowed_app_identifier, nullptr);
|
||||||
api_level = AConfiguration_getSdkVersion(configuration);
|
if (!windowed_app_identifier_c_str) {
|
||||||
AConfiguration_delete(configuration);
|
__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;
|
if (!app_context->InitializeApp(app_creator)) {
|
||||||
// TODO(Triang3l): Register activity callbacks.
|
// 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> (
|
bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
|
||||||
|
@ -82,3 +377,24 @@ bool AndroidWindowedAppContext::InitializeApp(std::unique_ptr<WindowedApp> (
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
} // namespace xe
|
} // 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_
|
#ifndef XENIA_UI_WINDOWED_APP_CONTEXT_ANDROID_H_
|
||||||
#define 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 <memory>
|
||||||
|
|
||||||
#include "xenia/ui/windowed_app_context.h"
|
#include "xenia/ui/windowed_app_context.h"
|
||||||
|
@ -23,17 +27,6 @@ class WindowedApp;
|
||||||
|
|
||||||
class AndroidWindowedAppContext final : public WindowedAppContext {
|
class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
public:
|
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(); }
|
WindowedApp* app() const { return app_.get(); }
|
||||||
|
|
||||||
void NotifyUILoopOfPendingFunctions() override;
|
void NotifyUILoopOfPendingFunctions() override;
|
||||||
|
@ -47,22 +40,76 @@ class AndroidWindowedAppContext final : public WindowedAppContext {
|
||||||
AndroidWindow* GetActivityWindow() const { return activity_window_; }
|
AndroidWindow* GetActivityWindow() const { return activity_window_; }
|
||||||
void SetActivityWindow(AndroidWindow* window) { activity_window_ = 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:
|
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)(
|
bool InitializeApp(std::unique_ptr<WindowedApp> (*app_creator)(
|
||||||
WindowedAppContext& app_context));
|
WindowedAppContext& app_context));
|
||||||
|
|
||||||
// TODO(Triang3l): Switch from ANativeActivity to the context itself being the
|
// Useful notes about JNI usage on Android within Xenia:
|
||||||
// object for communication with the Java code when NativeActivity isn't used
|
// - All static libraries defining JNI native functions must be linked to
|
||||||
// anymore as its functionality is heavily limited.
|
// shared libraries via LOCAL_WHOLE_STATIC_LIBRARIES.
|
||||||
ANativeActivity* activity_;
|
// - If method or field IDs are cached, a global reference to the class needs
|
||||||
std::unique_ptr<WindowedApp> app_;
|
// 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;
|
AndroidWindow* activity_window_ = nullptr;
|
||||||
|
|
||||||
// TODO(Triang3l): The rest of the context, including quit handler (and the
|
std::unique_ptr<WindowedApp> app_;
|
||||||
// destructor) calling `finish` on the activity, UI looper notification
|
|
||||||
// posting, etc.
|
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace ui
|
} // namespace ui
|
||||||
|
|
|
@ -22,14 +22,8 @@ GTKWindowedAppContext::~GTKWindowedAppContext() {
|
||||||
if (quit_idle_pending_) {
|
if (quit_idle_pending_) {
|
||||||
g_source_remove(quit_idle_pending_);
|
g_source_remove(quit_idle_pending_);
|
||||||
}
|
}
|
||||||
{
|
if (pending_functions_idle_pending_) {
|
||||||
// Lock the mutex for a pending_functions_idle_pending_ access memory
|
g_source_remove(pending_functions_idle_pending_);
|
||||||
// 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_);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
Subproject commit 09eac851efa5886d82067c2cb3cc9fb789a85c7e
|
Subproject commit 15ece0882e8d5875051ff5b73c5a8326f7cee9f5
|
|
@ -30,7 +30,7 @@ function sdl2_include()
|
||||||
includedirs({
|
includedirs({
|
||||||
path.getrelative(".", third_party_path) .. "/SDL2/include",
|
path.getrelative(".", third_party_path) .. "/SDL2/include",
|
||||||
})
|
})
|
||||||
filter("platforms:Linux")
|
filter("platforms:Linux or platforms:Mac")
|
||||||
includedirs(sdl2_sys_includedirs)
|
includedirs(sdl2_sys_includedirs)
|
||||||
filter({})
|
filter({})
|
||||||
end
|
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_scripts = build_tools .. "/scripts"
|
||||||
build_tools_src = build_tools .. "/src"
|
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"
|
platform_suffix = "win"
|
||||||
else
|
else
|
||||||
platform_suffix = "posix"
|
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.h",
|
||||||
base_path.."/"..base_match.."_win.cc",
|
base_path.."/"..base_match.."_win.cc",
|
||||||
})
|
})
|
||||||
filter("platforms:Linux or Android")
|
filter("platforms:Linux or Android-*")
|
||||||
files({
|
files({
|
||||||
base_path.."/"..base_match.."_posix.h",
|
base_path.."/"..base_match.."_posix.h",
|
||||||
base_path.."/"..base_match.."_posix.cc",
|
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.h",
|
||||||
base_path.."/"..base_match.."_gtk.cc",
|
base_path.."/"..base_match.."_gtk.cc",
|
||||||
})
|
})
|
||||||
filter("platforms:Android")
|
filter("platforms:Android-*")
|
||||||
files({
|
files({
|
||||||
base_path.."/"..base_match.."_android.h",
|
base_path.."/"..base_match.."_android.h",
|
||||||
base_path.."/"..base_match.."_android.cc",
|
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']
|
vs_version = os.environ['VSVERSION']
|
||||||
devenv = 'vs' + vs_version
|
devenv = 'vs' + vs_version
|
||||||
elif target_os == 'android':
|
elif target_os == 'android':
|
||||||
devenv = 'androidmk'
|
devenv = 'androidndk'
|
||||||
else:
|
else:
|
||||||
devenv = 'gmake2'
|
devenv = 'gmake2'
|
||||||
if target_os != 'linux':
|
if target_os != 'linux':
|
||||||
|
@ -822,9 +822,16 @@ class BaseBuildCommand(Command):
|
||||||
] + ([targets] if targets is not None else []) + pass_args,
|
] + ([targets] if targets is not None else []) + pass_args,
|
||||||
shell=False)
|
shell=False)
|
||||||
elif sys.platform == 'darwin':
|
elif sys.platform == 'darwin':
|
||||||
# TODO(benvanik): other platforms.
|
schemes = args['target'] if len(args['target']) else ['xenia-app']
|
||||||
print('ERROR: don\'t know how to build on this platform.')
|
nested_args = [['-scheme', scheme] for scheme in schemes]
|
||||||
result = 1
|
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:
|
else:
|
||||||
result = subprocess.call([
|
result = subprocess.call([
|
||||||
'make',
|
'make',
|
||||||
|
@ -1687,6 +1694,9 @@ class DevenvCommand(Command):
|
||||||
print('ERROR: Visual Studio is not installed.');
|
print('ERROR: Visual Studio is not installed.');
|
||||||
return 1
|
return 1
|
||||||
print('Launching Visual Studio...')
|
print('Launching Visual Studio...')
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
print('Launching Xcode...')
|
||||||
|
devenv = 'xcode4'
|
||||||
elif has_bin('clion') or has_bin('clion.sh'):
|
elif has_bin('clion') or has_bin('clion.sh'):
|
||||||
print('Launching CLion...')
|
print('Launching CLion...')
|
||||||
show_reload_prompt = create_clion_workspace()
|
show_reload_prompt = create_clion_workspace()
|
||||||
|
@ -1708,6 +1718,11 @@ class DevenvCommand(Command):
|
||||||
'devenv',
|
'devenv',
|
||||||
'build\\xenia.sln',
|
'build\\xenia.sln',
|
||||||
])
|
])
|
||||||
|
elif sys.platform == 'darwin':
|
||||||
|
shell_call([
|
||||||
|
'xed',
|
||||||
|
'build/xenia.xcworkspace',
|
||||||
|
])
|
||||||
elif has_bin('clion'):
|
elif has_bin('clion'):
|
||||||
shell_call([
|
shell_call([
|
||||||
'clion',
|
'clion',
|
||||||
|
|
Loading…
Reference in New Issue